Dominik Amrein Dominik Amrein - 1 month ago 11
C# Question

Rearrange of a nested List with Linq

I'm currently working on a project where I've read line data from a spreadsheet. The structure of the base data can be seen in the attached image on the left side. It can also be seen that I would like to rearrange the data in two steps.

Image1: Steps to sort the list


  • Firstly, I combine the element with the same
    PartName
    and combine the two coordinate pairs on one line. I store the coords in a nested list.

  • Secondly, I would like to combine the elements with the same
    ID
    and combine the coords again. Important is that the List keeps its order and that no duplicates are existing in the end. Something like this:
    {x1y1,x2y2}{x2y2,x3y3}=>{x1y1,x2y2,x3y3}
    .



The number of element parts is generic and therefore also the number of coordinate pairs.

Here are my simplified object classes I'm currently using:

public class ElmLine
{
public int Id{ get; set; }
public string PartName {get; set;}
public List<CoordList> CoordList { get; set; }
}

public class CoordList
{
public decimal XCord { get; set; }
public decimal YCord { get; set; }
}


This is my approach for step 1. I rearranged the base list and stored the coordination pairs in a nested list named
CoordList
.

public void BuildLinesFormSegments(IList<ElmLine> filteredLine)
{
// Merge line parts and add FROM and TO to each line part
var combinedLineParts = filteredLine
.GroupBy(c => new { c.Fid, c.FidPart }).Select(g => new ElmLine()
{
Id = g.Select(c => c.Id).First(),
PartName = g.Select(c => c.PartName).First(),
CoordList = g.Select(c => new CoordList()
{ XCord = c.CoordX, YCord = c.CoordY }).ToList(),
}).ToList();
}


The resulting list
combinedLineParts
looks like this:

var list = new LineParts<ElmLine>
{
new ElmLine {Name = 1, CoordList = new List<CoordList>
{new CoordList {XCord = x1, YCord = y1}, new CoordList { XCord = x2, YCord = y2 }} },
new ElmLine {Name = 1, CoordList = new List<CoordList>
{new CoordList {XCord = x2, YCord = y2}, new CoordList { XCord = x3, YCord = y3 }} },
new ElmLine {Name = 2, CoordList = new List<CoordList>
{new CoordList {XCord = x11, YCord = y11}, new CoordList { XCord = x12, YCord = y12 }} },
new ElmLine {Name = 2, CoordList = new List<CoordList>
{new CoordList {XCord = x12, YCord = y12}, new CoordList { XCord = x13, YCord = y13 }} },
};


Unfortunately I'm stuck at this point and I have no good idea to proceed with step2. It would be great if someone could give me a hint what I could do next.

Thank you in advance.

Answer

With a lot of thinking and the hints of StriplingWarrior I was able to solve my problem. To complete this post, I would like to show how I made it.

The ### Parts ###in the comment line are references to the image1 in the initial post. I solved my problem in these three steps and it can be seen what I make with my data in each step.

To give a better understanding of what I'm doing you can imagine domino stones. Each stone chain consists of a generic number of stones which are in the base data not in a line.

Base Data: {1,4} {1,3} {2,9} {2,7} {3,6} {3,3} {4,4} {4,7} (a List with {StoneNr,SideValue} each line is Half a Stone)

Result of Part 1: [4|3] [9|7] [6|3] [4|7] (a list with the stones combined)

Result of Part 2: [9|7] [7|4] [4|3] [3|6] (a rearranged list where all pairs are next to each other like a domino chain)

Result of Part 3: {9,7,4,3,6} (a string with the numbers in the necessary order)

In fact the stone values are coordinates and therefore pairs of numbers and the stone chains are lines in a 2D-plane but the principle keeps the same.

        // ### Part 1 ###
        var combinedLineParts = filteredLine.GroupBy(c => new { c.Fid, c.FidPart})
                .Select(g => new ElmLine()
        {
            Fid = g.Select(c => c.Fid).First(),
            FidPart = g.Select(c => c.FidPart).First(),
            CoordList = g.Select(c => new CoordList() 
                                          {XCord = c.CoordX, YCord = c.CoordY}).ToList()
                         .GroupBy(x => new {x.XCord, x.YCord})
                         .Select(x => x.First()).ToList(),
        }).ToList();

        // ### Part 2 ###
        foreach (var group in combinedLineParts.GroupBy(c => c.Fid))
        {
            List<List<CoordList>> coordList = group.Select(c => c.CoordList).ToList();

            if (coordList.Count > 2)
            {
                int[] startPoint = FindStartPoint(coordList);

                // if start point is not on top of the list, 
                // move it to the top (to: {0,0})
                if (startPoint[0] > 0 || startPoint[1] > 0)
                {
                    SwapElements(coordList, startPoint, new int[] { 0, 0 });
                }

                // Rearange List to sort the lineparts
                int groupNumb = 0;
                while (groupNumb < coordList.Count - 1)
                {
                    RearrangeList(coordList, groupNumb);
                    groupNumb++;
                }
            }
        // ### Part 3 ###
            // create a new list with the sorted lineparts
            combinedLines.Add( new ElmLine()
                {
                    Fid = group.Key,
                    CoordList = coordList.SelectMany(d => d)
                                         .Select(c => new {c.XCord, c.YCord})
                                         .Distinct()
                                         .Select(c => new CoordList() 
                                            {XCord = c.XCord, YCord = c.YCord}).ToList(), 
                });
        }
        return combinedLines;

FindStartPoint(), SwapElements(), RearrangeList() are my own methodes I made to solve my specific sorting problem.