0

I have the following algorithm:

class CycleData : List<IntPoint>{

   public IntPoint startPoint;
   public Boolean ended=false;
    public CycleData(IntPoint startpoint) { startPoint = startpoint; base.Add(startpoint); }
}
class GeoDataGraphPoint
{
    private IntPoint point;
    private List<GeoDataGraphPoint> connected = new List<GeoDataGraphPoint>();
    private int generation=-9999;

    public void AddConnection(GeoDataGraphPoint c)
    {
        connected.Add(c);
        c.connected.Add(this);
    }
    public GeoDataGraphPoint(IntPoint point)
    {
        this.point = point;
    }

    public List<CycleData> GetCycles(int gen)
    {
        if (generation !=  -9999)
        {
            var r = new CycleData(point);
            return new List<CycleData> { r };
        }
        generation = gen;
        List<CycleData> res = new List<CycleData>();

        foreach (GeoDataGraphPoint p in connected)
        {
            if (p.generation != gen-1)
            {
                res.AddRange(p.GetCycles(gen + 1));
            }
        }
        foreach (CycleData list in res)
        {
            if (list.ended == false)
            {
                list.Add(point);
                if (list.startPoint == this.point)
                {
                    list.ended = false;
                }
            }          
        }
        gen = -9999;
        return res;
    }
}

Now in principle this should return every cycle in the graph (for polygon detection). However it seems to fail to return anything in some occasions, I suspect that there is some kind of memory problem as removing parts of the graph sometimes causes new cycles to be found.

Here is a part of the input where it fails:

connection:(2282,3) to (2282,-192)
connection:(2282,3) to (2085,3)
connection:(2282,-192) to (2282,3)
connection:(2282,-192) to (2466,-192)
connection:(2466,-192) to (2282,-192)
connection:(2466,-192) to (2466,581)
connection:(2466,581) to (2466,-192)
connection:(2466,581) to (1494,581)
connection:(1494,581) to (2466,581)
connection:(1494,581) to (1494,397)
connection:(1494,397) to (1494,581)
connection:(1494,397) to (2282,397)
connection:(2282,397) to (1494,397)
connection:(2282,397) to (2282,187)
connection:(2282,187) to (2282,397)
connection:(2282,187) to (2085,187)
connection:(2085,187) to (2282,187)
connection:(2085,187) to (2085,3)
connection:(2085,3) to (2085,187)
connection:(2085,3) to (2282,3)
connection:(2085,3) to (2085,187)
connection:(2085,3) to (2282,3)
connection:(2085,187) to (2282,187)
connection:(2085,187) to (2085,3)
connection:(2282,187) to (2282,397)
connection:(2282,187) to (2085,187)
connection:(2282,397) to (1494,397)

The above code is for two triangles arranged to form a square (in coordinates) where both sides touch one another so like this:

triangleception

Where I can the function as following:

    class GeoDataGraph : Dictionary<IntPoint, GeoDataGraphPoint>
    {
        public void resetGens()
        {
            foreach(var v in base.Values)
            {
                v.generation = -9999;
            }
        }
        public static Island GetHolesInIsland(Island input)
        {
            GeoDataGraph graph = new GeoDataGraph();
            for (int i = 0; i < input.area.Count-1; i = i + 2)
            {
                var p1 = new IntPoint(input.area[i].X, input.area[i].Y);
                var p2 = new IntPoint(input.area[i + 1].X, input.area[i + 1].Y);
                if (!graph.ContainsKey(p1)) graph.Add(p1, new GeoDataGraphPoint(p1));
                if (!graph.ContainsKey(p2)) graph.Add(p2, new GeoDataGraphPoint(p2));
                graph[p1].AddConnection(graph[p2]);
            }
            IntPoint min = new IntPoint(int.MaxValue, int.MaxValue);
            List<IntPoint> minCycle = null;
            List<List<IntPoint>> cycles = new List<List<IntPoint>>();
            while (graph.Count != 0)
            {
                var first = graph.First();
                var NewCycles = first.Value.GetCycles(1);
                graph.resetGens();
                if (NewCycles.Count == 0)
                {
                    graph.Remove(first.Key);
                    Console.WriteLine("point" + first.Key + "is uncycled");
                }
                cycles.AddRange(NewCycles);
                foreach (var cycle in NewCycles)
                {
                    foreach (var cycleNode in cycle)                                                                                                                
                    {
                        graph.Remove(cycleNode);
                        if (min.X > cycleNode.X || min.Y > cycleNode.Y)
                        {
                            minCycle = cycle;
                            min = cycleNode;
                        }
                    }
                }
            }

            cycles.Remove(minCycle);
            if (minCycle == null) { minCycle = new List<IntPoint>();
            foreach(IntPoint a in input.area) {
                    Console.Write(a);
                } }

            input.holes = cycles;
            input.area = minCycle;
            return input;
        }
    }
}

where Island contains.area contains a list of points ordered by connected pairs.

The basic algorithm is simple: take a node recursively visit every node connected to it until you detect a cycle, then return that node and append any node on the way out until you find the start of the cycle again. Once you have found every node connected to the start node remove the cycles (they since we checked every node connected to the cycle we shouldn't be deleting the once we already did) and start again on the next node, if a node contains no cycles remove it. I suspect something might be going wrong at this step but am unsure. Any idea what I'm doing wrong that causes a weird interdependence that causes seemingly unrelated polygons to go wrong?

eddie
  • 1,252
  • 3
  • 15
  • 20
Thijser
  • 2,625
  • 1
  • 36
  • 71
  • I'm also open to advice on how to debug this more effectively. – Thijser Dec 22 '15 at 10:17
  • Also note that some information may be missing so feel free to ask for it in comments, as well as if anything is unclear or if you have any other suggestions towards solving this problem. – Thijser Dec 22 '15 at 10:45
  • Hoho, it doesn't seem to be easy to debug your code... – Ian Dec 22 '15 at 10:58
  • Yes this code grew sort of organically which is part of the problem and part of the problem is a recursive algoritm which makes debugging hard. Maybe I should crosspost this to code review? – Thijser Dec 22 '15 at 11:01
  • You get someone vote for you here, maybe you can just wait for a while. Who knows there are people who can answer your question satisfactorily. – Ian Dec 22 '15 at 11:02
  • @Thijser Code Review is only for code that is **already working as intended**. Once you've got it working though, feel free to bring it over. – Kaz Dec 22 '15 at 11:14

1 Answers1

1

I think that one problem is the way you ignore connected nodes using p.generation != gen-1. As you use depth first search you will tag all the nodes until the most depth and when backtracking I think it can miss some nodes or explore nodes twice.

As a general advise I can say: Don't reinvent the wheel yourself but use a known algorithm.

First ask yourself what you want to do. The number of cycles can be exponential. So the question is if you really need all the cycles.

If the answer is yes and you want to find all the cycles in a undirected graph you can get more information here.

If you don't really need all the cyles maybe what your are looking for is to find strongly connected components.

Community
  • 1
  • 1
Fabian
  • 1,886
  • 14
  • 13
  • The reason I use p.generation!=gen-1 is that I try to avoid the situation where a node is visited and then the algoritm inmidetly finds the cycle that returns back to the node, I'm only interested in simple cycles. Also the data is such that they should be only a very small number of cycles avaible if the algoritm isn't allowed to take the same edge twice. As for the need for this algoritm I'm trying to create polygons created by a set of lines, that might not require all cycles but I don't think strongly connected components is the answer either. – Thijser Dec 22 '15 at 18:15
  • This condition doesn't prevent exploring an edge twice.imagine a graph with edges a-b, b-c, c-a. When you start with a =1, b gets tagged 2 and c tagged 3. So when you are back to a you still explore the edge a-c again (as 1 !=3) even though you already already explored c-a. – Fabian Dec 22 '15 at 18:27
  • That should trigger the line if (generation != -9999) and return this cycle. – Thijser Dec 22 '15 at 18:28
  • Yes you get the cycle a-b-c. But additionally you will explore c-a in backtracking.try your algorithm with simple graphs like this to see what it is doing. – Fabian Dec 22 '15 at 18:34