0

I need to extract the best path in terms of its length from a rectangular array like this array:

enter image description here

The pathfinding rules:

  • Start from the indexes provided in the method signature where the rowIndex and colIndex are the positions of the starting point.`
  • The ones must be able to connect with each other either horizontally, vertically, or diagonally.
  • The last point on the path is the cell with a value of one that has no path to any other surrounding cells with a value of one.

I tried the following recursion algorithm, but it does not work for me and generate wrong output i.e. not as expected!!.

Here are the results: Results

        using System;
        using System.IO;
        using System.Text;
        using System.Drawing;
        using System.Collections;
        using System.ComponentModel;
        using System.Windows.Forms;
        using System.Data;
        using System.Threading;
        using AUV_Topology;
        using System.Collections.Generic;
        using System.Media;
        using System.Linq;

        namespace AUVtopology
        {
            public partial class Form1 : Form
            {        
              static int[,] array;
              static List<int[]> path;

// *******************************************************************************************************************************//
        //This method is used to make sure the coordinate array 
        //is contained in the list. List.contains(new int[] {val1,val2}) was not enough.
        static Boolean containsArray(List<int[]> list, int[] array)
        {
            if (array == null || array.Length == 0)
            {
                return false;
            }
            foreach (var listArray in list)
            {
                if (listArray != null && listArray.Length == array.Length)
                {
                    for (int i = 0; i < listArray.Length; i++)
                    {
                        if (i != listArray.Length - 1)
                        {
                            if (array[i] != listArray[i] && array[i + 1] != listArray[i + 1])
                            {
                                continue;
                            }


                            return true;

                        }

                    }

                }
            }
            return false;
        }    



// *******************************************************************************************************************************//

        //This is the recursive method of the algorithm. It finds the 
        //maximum path of 1 cells in a matrix of 0/1 cells
        static List<int[]> getMaxPath(int[,] array, List<int[]> maxPath, int rowIndex, int colIndex)
        {

            //Add the current cell to the path.
            maxPath.Add(new int[] { rowIndex, colIndex });

            //Get the array limits.
            int rowLength = array.GetLength(0);
            int colLength = array.GetLength(1);

            //If the path contains all the cells in the matrix, stop
            if (maxPath.Count >= rowLength * colLength)
            {
                return maxPath;
            }

            //remove one from lengths to make it the maximum index
            colLength = colLength - 1;
            rowLength = rowLength - 1;

            //We'll use this variable to see which of the 
            //potential 7 paths are the longest.
            List<int[]> futurePath;

            //Go over all 8 possible adjoining cells:
            //If we can go one down, one right, and it's a spot we 
            //have not yet visited
            if (colIndex < colLength && rowIndex < rowLength &&
                array[rowIndex + 1, colIndex + 1] == 1 &&
                !containsArray(maxPath, new int[] { rowIndex + 1, colIndex + 1 }))
            {

                //We use maxPath first, since this is the first 
                //direction and by default is the longest
                maxPath = getMaxPath(array, maxPath, rowIndex + 1, colIndex + 1);
            }

            //If we can go one down, and it's a spot we have not
            //yet visited
            if (colIndex < colLength &&
              array[rowIndex, colIndex + 1] == 1 &&
              !containsArray(maxPath, new int[] { rowIndex, colIndex + 1 }))
            {

                //We use futurePath now, since this is a second
                //direction and a potential contender
                futurePath = getMaxPath(array, maxPath, rowIndex, colIndex + 1);

                //We only need the maximum path.
                if (futurePath.Count > maxPath.Count)
                {
                    maxPath = futurePath;
                }
            }

            //If we can go one down and one left, and it's a spot
            //we have not yet visited
            if (rowIndex > 0 && colIndex < colLength &&
               array[rowIndex - 1, colIndex + 1] == 1 &&
               !containsArray(maxPath, new int[] { rowIndex - 1, colIndex + 1 }))
            {

                futurePath = getMaxPath(array, maxPath, rowIndex - 1, colIndex + 1);
                if (futurePath.Count > maxPath.Count)
                {
                    maxPath = futurePath;
                }
            }

            //If we can go one left, and it's a spot we have not
            //yet visited
            if (rowIndex > 0 &&
               array[rowIndex - 1, colIndex] == 1 &&
               !containsArray(maxPath, new int[] { rowIndex - 1, colIndex }))
            {

                futurePath = getMaxPath(array, maxPath, rowIndex - 1, colIndex);
                if (futurePath.Count > maxPath.Count)
                {
                    maxPath = futurePath;
                }
            }
            //If we can go one left and one up, and it's a spot we
            //have not yet visited
            if (rowIndex > 0 && colIndex > 0 &&
              array[rowIndex - 1, colIndex - 1] == 1 &&
              !containsArray(maxPath, new int[] { rowIndex - 1, colIndex - 1 }))
            {

                futurePath = getMaxPath(array, maxPath, rowIndex - 1, colIndex - 1);
                if (futurePath.Count > maxPath.Count)
                {
                    maxPath = futurePath;
                }
            }
            //If we can go one up, and it's a spot we have not yet
            //visited
            if (colIndex > 0 &&
                array[rowIndex, colIndex - 1] == 1 &&
                !containsArray(maxPath, new int[] { rowIndex, colIndex - 1 }))
            {

                futurePath = getMaxPath(array, maxPath, rowIndex, colIndex - 1);
                if (futurePath.Count > maxPath.Count)
                {
                    maxPath = futurePath;
                }
            }
            //If we can go one up and one right, and it's a spot we
            //have not yet visited
            if (colIndex > 0 && rowIndex < rowLength &&
              array[rowIndex + 1, colIndex - 1] == 1 &&
              !containsArray(maxPath, new int[] { rowIndex + 1, colIndex - 1 }))
            {

                futurePath = getMaxPath(array, maxPath, rowIndex + 1, colIndex - 1);
                if (futurePath.Count > maxPath.Count)
                {
                    maxPath = futurePath;
                }
            }
            //If we can go one right, and it's a spot we have not
            //yet visited
            if (rowIndex < rowLength &&
              array[rowIndex + 1, colIndex] == 1 &&
              !containsArray(maxPath, new int[] { rowIndex + 1, colIndex }))
            {

                futurePath = getMaxPath(array, maxPath, rowIndex + 1, colIndex);
                if (futurePath.Count > maxPath.Count)
                {
                    maxPath = futurePath;
                }
            }

            //We return the max path. Note: If none of the directions around 
            //us was applicable, we simply return the path we started 
            //with our cell included.
            return maxPath;
        }

Is prim algorithm the best choice ?

Rose
  • 349
  • 3
  • 17
  • You need to use Dijkstra's algorithm so you do no loop. See my solution at : https://stackoverflow.com/questions/39561207/shortest-path-finder-from-csv-in-c-sharp – jdweng Jul 02 '17 at 20:47
  • What you are doing won't work. It doesn't matter with Dijkstra's algorithm which cell you start at. The important issue is that you make sure you do not end up in a endless loop. Also the best path may not be the shortest path. You may need to go up and left to solve the problem. – jdweng Jul 02 '17 at 21:39
  • I need the longest path not the shortest!!. Could you show me how to use this algorithm to solve the example ? – Rose Jul 02 '17 at 21:56
  • The request was for the BEST PATH. The longest is the best? Isn't it the shortest the best? – jdweng Jul 03 '17 at 03:32

2 Answers2

1

I finally had some time to work this out. I did it with an enumerator to simplify the if loop. Did like having 8 if statements. I've been doing code like this for 40 years and know all the pitfalls.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace AUV_Topology
{
    class Program
    {
        public static object[] PopulationChromosomes = {
                                    new int[,] {
                                        { 1,0,1,1},
                                        { 1,1,1,0},
                                        { 1,0,1,0},
                                        { 0,1,0,0}
                  },                 
                                      new int[,] {
                                        { 1,1,0,1},
                                        { 1,1,0,0},
                                        { 0,1,0,0},
                                        { 1,0,1,0}
                  },
                                    new int[,] {
                                        { 1,0,0,0},
                                        { 1,1,1,1},
                                        { 1,0,0,1},
                                        { 0,0,0,1}
                  },
                                    new int[,] {
                                        { 0,1,0,0},
                                        { 1,1,1,0},
                                        { 1,0,0,1},
                                        { 0,1,0,1}
                  },
                                    new int[,] {
                                        { 0,1,0,1},
                                        { 0,1,1,0},
                                        { 1,1,0,1},
                                        { 0,0,0,1}
                  },
                                    new int[,] {
                                        { 0,1,1,0},
                                        { 1,1,0,1},
                                        { 0,1,0,1},
                                        { 1,0,0,0}
                  }





        };
        public static int[] auvChromosomeLocationsIndexesX = { 3, 0, 1, 3, 2, 2 };
        public static int[] auvChromosomeLocationsIndexesY = { 2, 1, 0, 1, 3, 0 };

        const string FILENAME = @"C:/temp/TreeParser.txt";
        static void Main(string[] args)
        {
            StreamWriter sw = new StreamWriter(FILENAME);
            int tt = 0;

            foreach (int[,] chromosome in PopulationChromosomes)
            {


                new SpanningTree(auvChromosomeLocationsIndexesY[tt], auvChromosomeLocationsIndexesX[tt], chromosome);
                SpanningTree.OrderHighLow(SpanningTree.root, 0);
                SpanningTree.Print(SpanningTree.root, 0, tt, sw);
                tt++;
            }
            sw.Flush();
            sw.Close();
            Console.ReadLine();

        }
    }
    /* Class Cell */
    /*****************************************************************************************************************************/

    public class Cell
    {

        public int row { get; set; }
        public int col { get; set; }
        public int value { get; set; }
        public Boolean visited { get; set; }

    }


    /* Class EnumCell */
    /*****************************************************************************************************************************/

    public class EnumCell : IEnumerator<Cell>
    {
        public EnumCell() { }

        public EnumCell(int startRow, int startCol, List<List<Cell>> graph)
        {
            row = startRow;
            col = startCol;
            numberOfRows = graph.Count;
            numberOfCols = graph[0].Count;
            EnumCell.graph = graph;
            this.position = LOCATIONS.RESET;
        }
        public enum XY
        {
            Y = 0, //row 
            X = 1 //col 
        }

        public enum LOCATIONS : byte
        {
            RESET = 0xFF,
            TOP_LEFT = 0,
            TOP_MIDDLE,
            TOP_RIGHT,
            LEFT,
            RIGHT,
            BOTTOM_LEFT,
            BOTTOM_MIDDLE,
            BOTTOM_RIGHT,
            END,
            INVALID
        }

        public static List<List<Cell>> graph { get; set; }
        public static int row { get; set; }
        public static int col { get; set; }
        public static int numberOfRows { get; set; }
        public static int numberOfCols { get; set; }


        //offsets are in same order as enum location as y-offset(row), x-offset (col) 
        private static List<List<int>> offsets = new List<List<int>>() { 
        new List<int>() { -1, -1 }, 
        new List<int>() { -1, 0 }, 
        new List<int>() { -1, +1 }, 
        new List<int>() { 0, -1 }, 
        new List<int>() { 0, +1 }, 
        new List<int>() { +1, -1 }, 
        new List<int>() { +1, 0 }, 
        new List<int>() { +1, +1 } 
        };
        public LOCATIONS position { get; set; }

        public EnumCell GetEnumerator()
        {
            return new EnumCell(row, col, graph);
        }

        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        /* Class Current Cell */
        /*****************************************************************************************************************************/
        public Cell Current
        {
            get
            {
                try
                {
                    // move to first valid postion
                    if (position == LOCATIONS.RESET) position = LOCATIONS.TOP_LEFT;
                    for (LOCATIONS location = position; location < LOCATIONS.END; location++)
                    {
                        if ((row + offsets[(byte)location][(int)XY.Y] >= 0) && (row + offsets[(byte)location][(int)XY.Y] < numberOfRows) &&
                        (col + offsets[(byte)location][(int)XY.X] >= 0) && (col + offsets[(byte)location][(int)XY.X] < numberOfCols))
                        {
                            position = (LOCATIONS)location;
                            int newRow = row + offsets[(byte)location][(int)XY.Y];
                            int newCol = col + offsets[(byte)location][(int)XY.X];
                            return graph[newRow][newCol];
                        }
                    }
                    throw new InvalidOperationException();
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }

        public Boolean MoveNext()
        {
            Boolean results = false;
            for (LOCATIONS location = ++position; location < LOCATIONS.END; location++)
            {
                int y = offsets[(byte)location][(int)XY.Y];
                int x = offsets[(byte)location][(int)XY.X];
                if ((row + y >= 0) && (row + y < numberOfRows) &&
                (col + x >= 0) && (col + x < numberOfCols))
                {
                    if (graph[row + y][col + x].value == 1)
                    {
                        position = (LOCATIONS)location;
                        return true;
                    }
                }
            }

            return results;
        }

        public void Reset()
        {
            position = LOCATIONS.RESET;
        }
        public void Dispose()
        {
        }
    }


    /* Class Graph */
    /*****************************************************************************************************************************/

    public class Graph
    {
        public Graph(int[,] graph)
        {

            this.graph = new List<List<Cell>>();
            for (int row = 0; row < graph.GetLength(0); row++)
            {
                List<Cell> newRow = new List<Cell>();
                this.graph.Add(newRow);
                for (int col = 0; col < graph.GetLength(1); col++)
                {
                    Cell newCell = new Cell();
                    newRow.Add(newCell);

                    newCell.row = row;
                    newCell.col = col;
                    newCell.value = graph[row, col];
                    newCell.visited = false;
                }
            }

        }
        public List<List<Cell>> graph;

    }


    /* Class SpanningTree */
    /*****************************************************************************************************************************/

    class SpanningTree
    {
        public static Graph graph = null;
        public static SpanningTree root = null;

        public static int counter = 0;

        public int row { get; set; }
        public int col { get; set; }
        public int length { get; set; }
        public List<SpanningTree> children { get; set; }

        public SpanningTree() { }
        public SpanningTree(int startRow, int startCol, int[,] graph)
        {
            root = new SpanningTree();
            SpanningTree.graph = new Graph(graph);
            RecursiveTree(root, SpanningTree.graph.graph[startRow][startCol]);


        }
        public int RecursiveTree(SpanningTree parent, Cell currentCell)
        {
            int length = 0;
            int maxLength = 0;
            parent.row = currentCell.row;
            parent.col = currentCell.col;

            graph.graph[currentCell.row][currentCell.col].visited = true;
            EnumCell enumCell = new EnumCell(currentCell.row, currentCell.col, graph.graph);


            foreach (Cell cell in enumCell)
            {
                if (!cell.visited)
                {
                    SpanningTree newBranch = new SpanningTree();
                    if (parent.children == null) parent.children = new List<SpanningTree>();
                    length = RecursiveTree(newBranch, SpanningTree.graph.graph[cell.row][cell.col]);

                    if (length > maxLength)
                    {
                        if ((EnumCell.numberOfRows > 7) || (EnumCell.numberOfCols > 7))
                        {
                            if (parent.children.Count == 0)
                            {
                                parent.children.Add(newBranch);
                            }
                            else
                            {
                                parent.children[0] = newBranch;
                            }
                        }
                        else
                        {
                            parent.children.Add(newBranch);
                        }
                        maxLength = length;
                    }
                }
            }
            graph.graph[currentCell.row][currentCell.col].visited = false;

            parent.length = maxLength;
            return maxLength + 1;
        }
        public static void OrderHighLow(SpanningTree parent, int level)
        {
            if (parent.children != null)
            {
                parent.children = parent.children.OrderByDescending(x => x.length).ToList();
                foreach (SpanningTree child in parent.children)
                {
                    OrderHighLow(child, level + 1);
                }
            }
        }



        public static void Print(SpanningTree parent, int level, int chromosomeNum, StreamWriter sw)
        {
            sw.WriteLine("------------------- Chromosome : {0} -------------------", chromosomeNum);
            //Print(parent, level, sw); 
            sw.WriteLine("---------Longest----------");
            PrintLongest(parent, level, sw);
            counter = 0;
        }

        private static void Print(SpanningTree parent, int level, StreamWriter sw)
        {
            ////////////////////////////////////////////////////////////////////// 
            sw.WriteLine("{0}Level : '{1}', Row : '{2}', Col : '{3}', Length : '{4}'", new string(' ', 4 * level), level, parent.row, parent.col, parent.length);
            ////////////////////////////////////////////////////////////////////// 

            if (parent.children != null)
            {
                foreach (SpanningTree child in parent.children)
                {

                    Print(child, level + 1, sw);

                    if (child.length == 0)
                    {

                        sw.WriteLine("||,,,,,,Branch {0},,,,,,||", counter);
                        sw.WriteLine("{0}Level : '{1}', Row : '{2}', Col : '{3}', Length : '{4}'", new string(' ', 4 * level), level, root.row, root.col, root.length);
                        counter += 1;

                    }
                }
            }

        }


        public static void PrintLongest(SpanningTree parent, int level, StreamWriter sw)
        {
            ////////////////////////////////////////////////////////////////////// 
            sw.WriteLine("{0}Level : '{1}', Row : '{2}', Col : '{3}', Length : '{4}'", new string(' ', 4 * level), level, parent.row, parent.col, parent.length);
            ////////////////////////////////////////////////////////////////////// 

            if (parent.children != null)
            {
                PrintLongest(parent.children[0], level + 1, sw);
            }
        }
    }

}// end name space
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • Time complexity in Log(N) which is the maximum number of children from root to leaf. The code is just a normal tree parser. Since each cell has 8 surrounding neighbor cells I just created an enumerator to get only the valid neighbors. Not all neighbors are valid when a cell is on the boarder of the graph and when a neighbor is zero. The enumerator is just the statement : foreach (Cell cell in enumCell). So I'm just using a standard recursive method to enumerate through all the paths using Dijsksta (added a visited property) to prevent looping. – jdweng Jul 04 '17 at 18:33
  • The two algorithms are very similar. The only real different is the codeproject is returning a List<> object for children while I'm putting results into the SpanningTree class. The other differences is just programming style. – jdweng Jul 04 '17 at 18:44
  • Look at my code. I create class cell with property "visited" as what Dijkstra's requires. – jdweng Jul 04 '17 at 19:24
  • 1
    The enumeration I created has statement : if(graph[row + y][col + x].value == 1) which only return adjacent cell(s) with value == 1 and only cells within graph size (no cells with negative values or cells larger than number of rows/columns). So all the dirty work is done inside the enumeration which make rest of code simple.That is the beauty of the statement : foreach (Cell cell in enumCell). I wrote code like your code 40 years ago while in college with punch cards. That is why it took me a couple of days to get the better enumeration solution working. – jdweng Jul 05 '17 at 04:48
  • 1
    I modified code above to Order Highest To Lowest. Then added method that only prints Longest Path. – jdweng Jul 13 '17 at 10:59
  • Just found a couple of typos. Fixed. – jdweng Jul 13 '17 at 11:13
  • The arrays and the results don't seem to be for the same arrays. If you look at the cells that contain 1's the printed results are showing cells that contain zeroes. The last one matches and works perfectly. – jdweng Jul 14 '17 at 02:07
  • I fixed code above. Found a number of issues that I changed. – jdweng Jul 15 '17 at 15:07
  • I generate different arrays at each run (randomly filled by zeros and ones) using another method then I use them in foreach, does this algorithm suitable for this dynamic generation ?? – Rose Jul 15 '17 at 15:18
  • Yes you can use random arrays. It the 2nd problem the 9x9 array where you are getting the memory error? if so you can't save all the results, just the longest. So change code near :if (length > maxLength) maxLength = length; Only save child[0]. So if the length of newBranch is longer then child[0] then replace child[0] with new branch. – jdweng Jul 15 '17 at 15:28
1

Here is code using a Class Project that works without compiler errors

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Auv_Topology
{
    public class Cell
    {

        public int row { get; set; }
        public int col { get; set; }
        public int value { get; set; }
        public Boolean visited { get; set; }

    }

    public class EnumCell : IEnumerator<Cell>
    {
        public EnumCell() { }

        public EnumCell(int startRow, int startCol, List<List<Cell>> graph)
        {
            this.row = startRow;
            this.col = startCol;
            this.numberOfRows = graph.Count;
            this.numberOfCols = graph[0].Count;
            this.graph = graph;
        }
        public enum XY
        {
            Y = 0,  //row
            X = 1   //col
        }

        public enum LOCATIONS : byte
        {
            TOP_LEFT = 0,
            TOP_MIDDLE,
            TOP_RIGHT,
            LEFT,
            RIGHT,
            BOTTOM_LEFT,
            BOTTOM_MIDDLE,
            BOTTOM_RIGHT,
            END,
            INVALID
        }

        public List<List<Cell>> graph { get; set; }
        public int row { get; set; }
        public int col { get; set; }
        public int numberOfRows { get; set; }
        public int numberOfCols { get; set; }


        //offsets are in same order as enum location as y-offset(row), x-offset (col)
        private List<List<int>> offsets = new List<List<int>>() {
        new List<int>() { -1, -1 },
        new List<int>() { -1, 0 },
        new List<int>() { -1, +1 },
        new List<int>() { 0, -1 },
        new List<int>() { 0, +1 },
        new List<int>() { +1, -1 },
        new List<int>() { +1, 0 },
        new List<int>() { +1, +1 }
    };
        public LOCATIONS position { get; set; }

        public EnumCell GetEnumerator()
        {
            return new EnumCell(row, col, graph);
        }

        object IEnumerator.Current
        {
            get
            {
                return Current;
            }
        }

        public Cell Current
        {
            get
            {
                try
                {
                    // move to first valie postion
                    for (LOCATIONS location = position; location < LOCATIONS.END; location++)
                    {
                        if ((row + offsets[(byte)location][(int)XY.Y] >= 0) && (row + offsets[(byte)location][(int)XY.Y] < numberOfRows) &&
                            (col + offsets[(byte)location][(int)XY.X] > 0) && (col + offsets[(byte)location][(int)XY.X] < numberOfCols))
                        {
                            position = (LOCATIONS)location;
                            int newRow = row + offsets[(byte)location][(int)XY.Y];
                            int newCol = col + offsets[(byte)location][(int)XY.X];
                            return graph[newRow][newCol];
                        }
                    }
                    throw new InvalidOperationException();
                }
                catch (IndexOutOfRangeException)
                {
                    throw new InvalidOperationException();
                }
            }
        }

        public Boolean MoveNext()
        {
            Boolean results = false;
            for (LOCATIONS location = ++position; location < LOCATIONS.END; location++)
            {
                int y = offsets[(byte)location][(int)XY.Y];
                int x = offsets[(byte)location][(int)XY.X];
                if ((row + y >= 0) && (row + y < numberOfRows) &&
                    (col + x > 0) && (col + x < numberOfCols))
                {
                    if (graph[row + y][col + x].value == 1)
                    {
                        position = (LOCATIONS)location;
                        return true;
                    }
                }
            }

            return results;
        }

        public void Reset()
        {
            position = LOCATIONS.TOP_LEFT;
        }
        public void Dispose()
        {
        }
    }
}
jdweng
  • 33,250
  • 2
  • 15
  • 20
  • 1
    If it is in a different namespace/class you have to use entire path. This is typical of breaking code into different cs modules. – jdweng Jul 07 '17 at 13:11
  • 1
    You can have the same namespace in more than one cs module. You can also have the same class in more than one cs module by using partial. A windows form project will automatically put the form into a partial class. – jdweng Jul 07 '17 at 21:47
  • 1
    You should not have any issues using the code from another class like a form class. You will have to reference the class using the namespace like AUV_Topology.EnumCell – jdweng Jul 08 '17 at 06:40