How to separate all consecutive Objects using Linq (C#)

后端 未结 6 624
长发绾君心
长发绾君心 2021-01-14 10:37

I have a List ... MyObject has a propert called LineId (integer)...

So, I need separate all consecutive LineId in List

相关标签:
6条回答
  • 2021-01-14 11:17

    This looks ugly but the idea is clear and I think it works:

    var agg = list.Aggregate(
        new List<List<Line>>(),
        (groupedLines, line) => {
            if (!groupedLines.Any()) {
                groupedLines.Add(new List<Line>() { line });
            }
            else {
                List<Line> last = groupedLines.Last();
                if (last.First().LineId == line.LineId) {
                    last.Add(line);
                }
                else {
                    List<Line> newGroup = new List<Line>();
                    newGroup.Add(line);
                    groupedLines.Add(newGroup);
                }
            }
            return groupedLines;
        }
    );
    

    Here I am assuming that you have:

    class Line { public int LineId { get; set; } }
    

    and

    List<Line> list = new List<Line>() {
        new Line { LineId = 1 },
        new Line { LineId = 1 },
        new Line { LineId = 1 },
        new Line { LineId = 2 },
        new Line { LineId = 1 },
        new Line { LineId = 2 }
    };
    

    Now if I execute

    foreach(var lineGroup in agg) {
        Console.WriteLine(
            "Found {0} consecutive lines with LineId = {1}",
            lineGroup.Count,
            lineGroup.First().LineId
        );
        foreach(var line in lineGroup) {
            Console.WriteLine("LineId = {0}", line.LineId);
        }
    }
    

    I see:

    Found 3 consecutive lines with LineId = 1
    LineId = 1
    LineId = 1
    LineId = 1
    Found 1 consecutive lines with LineId = 2
    LineId = 2
    Found 1 consecutive lines with LineId = 1
    LineId = 1
    Found 1 consecutive lines with LineId = 2
    LineId = 2
    

    printed on the console.

    0 讨论(0)
  • 2021-01-14 11:28

    Hmm, I don't think linq is the cleanest solution to this problem. I think that the following code is probably far simpler and easier to read than any LINQ.

                List<MyObject> currentList = new List<MyObject>();
                List<List<MyObject>> finalList = new List<List<MyObject>>();
                for (int i = 0; i < myObjects.Count; i++)
                {
                    //If the list is empty, or has the same LineId, add to it.
                    if (currentList.Count == 0 || currentList[0].LineId == myObjects[i].LineId)
                    {
                        currentList.Add(myObjects[i]);
                    }
                    //Otherwise, create a new list.
                    else
                    {
                        finalList.Add(currentList);
                        currentList = new List<MyObject>();
                        currentList.Add(myObjects[i]);
                    }
                }
                finalList.Add(currentList);
    

    I know you asked for linq, but I think this is probably a better solution.

    0 讨论(0)
  • 2021-01-14 11:29

    A slightly smaller solution, but probably (read: definitely) less clear:

    var lst = new [] {
        new { LineID = 1 },
        new { LineID = 1 },
        new { LineID = 1 },
        new { LineID = 2 },
        new { LineID = 1 },
        new { LineID = 2 },
    };
    
    var q = lst
        .Select((x,i) => new {Item = x, Index = i})
        .GroupBy(x => x.Item)
        .SelectMany(x => x.Select((y,i) => new { Item = y.Item, Index = y.Index, Sort = i - y.Index }))
        .OrderBy(x => x.Index)
        .GroupBy(x => x.Sort)
        .Select(x => x.Select(y => y.Item));
    

    Returns an IEnumerable<IEnumerable<int>> :

    1,1,1
    2
    1
    2
    

    Note, if you don't care about order, you can remove the 'OrderBy' line and the 'Index = y.Index' property in the anonymous object and it will return:

    1,1,1
    1
    2
    2
    
    0 讨论(0)
  • 2021-01-14 11:32

    Here is a solution where the lines are grouped by the index of the last item in each consecutive sub-sequence.

    In your example, these are the boundary indexes:

    LineId    Index     Boundary Index
      1         0           2
      1         1           2
      1         2           2
      2         3           3
      1         4           4
      2         5           5
    

    Here is the Linq query:

    var lines = new List<MyObject>
                    {
                        new MyObject { LineId = 1 },
                        new MyObject { LineId = 1 },
                        new MyObject { LineId = 1 },
                        new MyObject { LineId = 2 },
                        new MyObject { LineId = 1 },
                        new MyObject { LineId = 2 },
                    };
    
    IEnumerable<List<int>> consecutiveLineIds = lines
        .Select((line, i) => new
        {
            Line = line,
            Boundary = i + lines.Skip(i)
                                .TakeWhile(l => l.LineId == line.LineId).Count() - 1
        })
        .GroupBy(item => item.Boundary, item => item.Line.LineId)
        .Select(g => g.ToList());
    
    0 讨论(0)
  • 2021-01-14 11:37
    void Main()
    {
        var list = new []{
            new { LineId = 1 },
            new { LineId = 1 },
            new { LineId = 1 },
            new { LineId = 2 },
            new { LineId = 2 },
            new { LineId = 1 },
            new { LineId = 1 },
            new { LineId = 3 },
            new { LineId = 3 },
            new { LineId = 1 }
        };
    
        var groups =    
            from i in list
            group i by i.LineId into g
            select new { LineId = g.Key, MyObject = g };
    }
    
    0 讨论(0)
  • 2021-01-14 11:39

    For good measure, how about a LINQ solution not implemented using LINQ:

    public static IEnumerable<IEnumerable<TSource>> GroupConsecutive<TSource,TKey>(
            this IEnumerable<TSource> source, Func<TSource,TKey> keySelector) {
        if (source == null) throw new ArgumentNullException("source");
        if (keySelector == null) throw new ArgumentNullException("keySelector");
    
        var comparer = EqualityComparer<TKey>.Default;
        var grouped = new List<TSource>();
        using (var iter = source.GetEnumerator()) {
            if (!iter.MoveNext()) yield break;
            grouped.Add(iter.Current);
            var last = iter.Current;
            while (iter.MoveNext()) {
                if (!comparer.Equals(keySelector(iter.Current), keySelector(last))) {
                    yield return grouped.AsReadOnly();
                    grouped = new List<TSource>();
                }
                grouped.Add(iter.Current);
                last = iter.Current;
            }
            yield return grouped.AsReadOnly();
        }
    }
    
    0 讨论(0)
提交回复
热议问题