简体   繁体   中英

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

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

So, I need separate all consecutive LineId in List ...

Example :

List<MyObject> :

LineId =1
LineId =1
LineId =1
LineId =2
LineId =1
LineId =2

The result must be 4 separeted lists:

LineId =1
LineId =1
LineId =1
-------------
LineId =2
-------------
LineId =1
-------------
LineId =2

So, I must separate all consecutive LineId ...

Is there an easy way to do that using Linq?

Thanks

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.

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();
    }
}

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.

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

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());
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 };
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM