views:

221

answers:

5

Hi,

I have a city simulation game and try to find a way to check the flow of our power system. The basics: The map for the city is based on tiles (30 by 30 tiles = 900 tiles). Now i start at a power plant and do a recursive neighbor check (top, left, right, bottom) to check if there is something that will transport the power. If there is something, I start checking this tiles for neighbors, too. To prevent double checks and/or infinite recursive calls, I fill a ArrayList with processed tiles and check if a new tile was already processed and added to the ArrayList...

Recursively started:

public void updatePowerEnvironment(int id, ArrayList<Integer> elements) {
    Log.w("GT", "update env for id: " + id);
    int newId = id - GameMap.mMapSize;
    if (newId >= 0 && GameMap.mMapCells.get(newId).mPowerEnabled
            && !elements.contains(newId)) {
        elements.add(newId);
        updatePowerEnvironment(newId, elements);
    }
    newId = id + GameMap.mMapSize;
    if (newId < GameMap.mMapCells.size() && GameMap.mMapCells.get(newId).mPowerEnabled
            && !elements.contains(newId)) {
        elements.add(newId);
        updatePowerEnvironment(newId, elements);
    }
    newId = id - 1;
    if (newId >= 0 && GameMap.mMapCells.get(newId).mPowerEnabled
            && !elements.contains(newId)) {
        elements.add(newId);
        updatePowerEnvironment(newId, elements);
    }
    newId = id + 1;
    if (newId < GameMap.mMapCells.size()
            && GameMap.mMapCells.get(newId).mPowerEnabled
            && !elements.contains(newId)) {
        elements.add(newId);
        updatePowerEnvironment(newId, elements);
    }
}

If I can trust the log output, no tile was tried to processed twice. That means, that I have no errors in the recursive calls. Which also means, the stack is simply too small.

Does someone have an idea how to avoid the stack limit?

[Update and my code as a result of Erics answer]

public void updatePowerEnvironment(int id, ArrayList<Integer> elements) {
    Stack<Integer> toProcess = new Stack<Integer>();
    toProcess.push(id);
    int mapSize = GameMap.mMapCells.size();
    while (!toProcess.empty()) {
        id = toProcess.pop();
        Log.e("GT", "id to process: " + id);
        if (elements.contains(id)) {
            continue;
        }
        int[] neighborIds = computeNeighbors(id);
        for (int neighbor : neighborIds) {
            if (neighbor < 0 || neighbor >= mapSize) {
                continue;
            }
            if (!GameMap.mMapCells.get(neighbor).mPowerEnabled) {
                continue;
            }
            toProcess.push(neighbor);
        }
        elements.add(id);
    }
}

private int[] computeNeighbors(int id) {
    return new int[] {id + GameMap.mMapSize, id - GameMap.mMapSize, id + 1, id - 1};
}
+14  A: 

If I understand your problem correctly you are attempting to compute the transitive closure of the "is powered by" relation between two tiles. It is certainly possible to compute a transitive closure non-recursively.

Here's a non-recursive algorithm that computes the transitive closure of a relation in C#. You should be able to adapt that to the language of your choice.

http://blogs.msdn.com/b/ericlippert/archive/2010/02/08/making-the-code-read-like-the-spec.aspx

Note that basically what I'm doing here is avoiding the stack limit by allocating my own stack on the heap. That thing can grow as big as you like. (If you run out of heap memory then you've got bigger problems!)

Note also that it would be wise to choose a data structure that makes the "is a member of?" predicate extremely cheap. An array list of size n is usually O(n) to answer the question "is this element a member of this collection?" which means your algorithm is O(n^2) overall. Can you use a collection like a set or a hash table that has O(1) containment testing?

Also, on a purely "code quality" level, this method could use some work. The fact that there is so much duplicated code in there is a red flag. I would be inclined to write this method like this sketch:

Set<int> PoweredTiles(int powersource)
{
    Set<int> result = an empy set;
    Stack<int> stack = an empty stack;
    stack.Push(powersource);
    while (stack is not empty)
    {
        int current = stack.Pop();
        if (result.Contains(current)) continue;
        result.Add(current);
        int[] neighbours = { compute the neighbours }
        foreach(int neighbour in neighbours)
        {
            if (neighbour is not in range of grid) continue;
            if (neighbour is not a power carrier) continue;
            stack.Push(neighbour);
        }
    }
    return result;
}

Short, to the point, not recursive, no duplicated code, and O(n).

Eric Lippert
I have to admit that I am not very familiar with the theory of complexity but your code does the trick!I updated the question with the code I produced.
WarrenFaith
@WarrenFaith: Glad you like it. The point about complexity is that when you ask "does this array list of n elements contain x?" the way it answers that question is "is the first item x? No. Is the second item x? No. ... is the nth item x? No. OK, it's not in the list". If there are 400 items in that list then you are doing 400 comparisons *every time through the loop*. An arraylist is optimized for fast insertion at the end at the price of *slow searching*. You might consider using some "set" data type that is optimized for *fast searching* since *most of what you do is searching*.
Eric Lippert
@Eric: hm more than true. Thanks for this advice! Its always the obvious thats forgotten )
WarrenFaith
@WarrenFaith: As a programmer, you should probably spend some time gaining some understanding of complexity and Big O notation.
Brian
You first should ask what "not familiar" means :)
WarrenFaith
+3  A: 

You just need to convert your recursive implementation into an iterative one (as theory tells us is always possible).

For example, you could:

  • maintain a queue of cells-to-be-checked
  • while this queue is not empty, process the front element
    • to process a cell, do whatever you have to do to the cell itself, then for each of its four nneighbours
    • if they are not already in the queue, add them to the queue
  • repeat until the queue is empty
AakashM
One minor point is that if you are using *immutable* data structures then it is almost always more efficient to use a *stack* than a *queue*. The result will be the same, only the order in which things are processed will be different.
Eric Lippert
@Eric are you referring to the *objects in the collection* being immutable, or to the FP-style immutable *containers* such as you have blogged about recently?
AakashM
I meant the containers. An immutable stack is cheaper than an immutable queue.
Eric Lippert
A: 

An efficient, recursive algorithm should work provided you do clear the flags (I assume you're simply setting flags on whether a tile has power or not) before doing the recursion. Something like this:

void updateCell(position)
{
  for each direction (north, south, east, west) do the following:
  -- is there a cell there? (test for edges), if not, exit now;
  -- can it be powered? 
     false: exit now;
     true: set powered=true, call updateCell(this position);
}

void updatePowerGrid(start)
{
  clearPowerFlags();
  set powered=true for start;
  updateCell(start);
}

This should work well enough until you use really huge grid sizes.

vij
A: 

You can make it iterative. Have two lists, one that keeps track of where you have been, and one that keeps track of where you are currently checking.

Psuedo Code with your code:

While(ToBeChecked is not empty) {
    //Note In python i'd be using a copy of the list so I could edit it without 
    //concequence during the iteration. ie for a in b[:]
    for each element in ToBeChecked
         updatePowerEnvironment(...);
         //Remove element you are checking        
         removeElementFromToBeChecked(...);
}

public void updatePowerEnvironment(int id, ArrayList<Integer> elements) {
    Log.w("GT", "update env for id: " + id);
    int newId = id - GameMap.mMapSize;
    if (newId >= 0 && GameMap.mMapCells.get(newId).mPowerEnabled
            && !elements.contains(newId)) {
        elements.add(newId);
        //call addElementToBeChecked instead and I beleive the above already 
        //makes sure it has not already been checked
        addElementToBeChecked(newId, elements);
    }
    newId = id + GameMap.mMapSize;
    if (newId < GameMap.mMapCells.size() && GameMap.mMapCells.get(newId).mPowerEnabled
            && !elements.contains(newId)) {
        elements.add(newId);
        addElementToBeChecked(newId, elements);
    }
    newId = id - 1;
    if (newId >= 0 && GameMap.mMapCells.get(newId).mPowerEnabled
            && !elements.contains(newId)) {
        elements.add(newId);
        addElementToBeChecked(newId, elements);
    }
    newId = id + 1;
    if (newId < GameMap.mMapCells.size()
            && GameMap.mMapCells.get(newId).mPowerEnabled
            && !elements.contains(newId)) {
        elements.add(newId);
        addElementToBeChecked(newId, elements);
    }
}

addElementToBeChecked(...) {
    ToBeChecked.add();
    //Some other stuff if needed
}
removeElemenToBeChecked(...) {
    ToBeChecked.remove();
    //Some other stuff if needed
}
Derek Litz
A: 

The very first thing I would try is just to change the search order from North-South-West-East to North-East-South-West. Like this:

public void updatePowerEnvironment(int id, ArrayList<Integer> elements) { 
    if (!GameMap.ValidCellId(id))
        return;
    if (!GameMap.mMapCells.get(id).mPowerEnabled)
        return;
    if (elements.Contains(id))
        return;
    elements.Add(id);
    updatePowerEnvironment(id - GameMap.mMapSize, elements);
    updatePowerEnvironment(id + 1, elements);
    updatePowerEnvironment(id + GameMap.mMapSize, elements);
    updatePowerEnvironment(id - 1, elements);
}

This might reduce the recursion depth, depeding on the maps involved.

Jeffrey L Whitledge
The least possible recursion depth is a function of the shape of the graph, not of the order in which the recursion is done. The minimum possible is equal to the length of the *shortest* path to the tile that is *farthest* from the power source. On a 30x30 grid we can easily construct a subset which is a single path 450 nodes long, so we need to be able to handle at least 450 recursions, and for an n x n graph we need to be able to handle n^2 / 2 recursions. A recursive solution simply does not scale; we need to go to a non-recursive solution here.
Eric Lippert
@Eric Lippert - Yes, of course you're right. When I wrote this answer I imagined that the order in which they are processed might make a difference in a graph consisting of a large contiguous grid area, but I hadn't considered the fact that it makes no difference in a depth-first search. Spirals are just as bad as sweeps. I also figured that pathological configurations designed to create the longest paths were not an issue, but, of course, in a game that's something that everyone is going to try!
Jeffrey L Whitledge