Nindalf
Nindalf

Reputation: 59

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

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.

Upvotes: 2

Views: 219

Answers (2)

Nindalf
Nindalf

Reputation: 59

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.

Upvotes: 0

StriplingWarrior
StriplingWarrior

Reputation: 156654

Step 3 really doesn't need to rely on Step 2. You're just grouping by ID and keeping the distinct coordinate values:

var result = filteredLine
        .GroupBy(c => c.Fid)
        .Select(g => new ElmLine()
        {
            Id = g.Key,
            CoordList = g
                .Select(c => { c.CoordX, c.CoordY })
                .Distinct()
                .Select(c => new CoordList() 
                        { XCord = c.CoordX, YCord = c.CoordY })
                .ToList(),
        }).ToList();

Upvotes: 2

Related Questions