通过序列元素相等的集合集合

时间:2016-02-03 17:05:04

标签: c# linq

如果我有一些集合,例如:整数集合:

1 1 2 2 3 3 1 2 3 4 4 1 1 2 3

我只需要对后续元素进行分组,在此示例中为:

1 1
2 2
3 3
1
2
3
4 4
1 1
2
3

我构建了一个LINQ表达式,它创建了一个匿名集合,它在集合中有元素索引,它是相等标记,然后我加入集合,它向前移动了一个位置,然后是组元素。但我觉得这个算法太过分了。

有更优雅的解决方案吗?

4 个答案:

答案 0 :(得分:3)

编辑:我找到了使用聚合扩展方法执行此操作的Linq方法。此方法允许您使用Func lambda中的accumulator参数查看集合中的先前项。这是:

static List<List<int>> Group2(List<int> data)
{
    return data.Aggregate(new List<List<int>>(), (list, item) => 
    {
        if (list.Count == 0 || list[list.Count - 1][0] != item)
        {
            list.Add(new List<int> { item });
        }
        else
        {
            list[list.Count - 1].Add(item);
        }
        return list;
    });
}

我无法想到Linq的方法,因为我不认为它的任何方法都允许你以这种方式查看以前的项目。这是一种通用的方式:

static IEnumerable<List<T>> Group<T>(IEnumerable<T> list, IEqualityComparer<T> comp)
{
    T previous = default(T);
    bool previousExists = false;
    var eee = list.GetEnumerator();
    List<T> result = null;

    while(eee.MoveNext())
    {
        T current = eee.Current;
        if (previousExists && comp.Equals(current, previous))
        {
            result.Add(current);
        }
        else
        {
            if (result != null)
                yield return result;
            result = new List<T> { current };
        }
        previous = current;
        previousExists = true;
    }

    if (result != null)
        yield return result;
}

答案 1 :(得分:1)

好吧,如果你真的不需要LINQ - 只需简单的循环即可完成任务:

var res = new List<List<int>>();
foreach(int i in data)
{
    var c = res.Count;
    if (c == 0 || res[c - 1][0] != i)
        res.Add(new List<int>() { i });
    else
        res[c - 1].Add(i);
}

您也可以将linq与外部变量一起使用,但这可能更难阅读

答案 2 :(得分:1)

这是使用LINQ:

的实现
//Input sequence
int[] input = new int[] { 1, 1, 2, 2, 3, 3, 1, 2, 3, 4, 4, 1, 1, 2, 3 };
//Group number
int i = 0;
//Result array [<group number>][]
int[][] values = 
    //Select new anonymous object, which contains the source value from input and its group number
    input.Select((item, index) => new { Key = index > 0 ? (item == input[index - 1] ? i : ++i) : 0, Value = item })
    //Group anonymous objects by group number
    .GroupBy(pair => pair.Key)
    //Select values for each group
    .Select(g => g.Select(x => x.Value).ToArray())
    .ToArray();

答案 3 :(得分:1)

问题是LINQ缺少Select方法,可以让您点击上一个结果。

如果确实如此,你只需要写下这样的东西:

var sequences = items
    .SelectWithPreviousResult(
        new { Item = -1, GroupNumber = 0 }, // default result (used for first item)
        (item, previous) => new
        {
            Item = item,
            GroupNumber = previous.Item == x
                ? previous.GroupNumber
                : previous.GroupNumber + 1 })
    .GroupBy(x => x.GroupNumber, x => x.Item);

这样做是选择当前项目以及从0开始的GroupNumber,仅在当前项目与上一项目不同时才增加。然后按GroupNumber进行分组,并将组成员简化为项目。

当然,由于SelectWithPrevious尚不存在,因此无法编译。但是,关于LINQ的好处是你可以很容易地编写自己的扩展方法。 SelectWithPreviousResult方法可以像这样实现:

public static class LinqExtensions
{
    public static IEnumerable<TResult> SelectWithPreviousResult<TSource, TResult>(
        this IEnumerable<TSource> items,
        TResult defaultResult,
        Func<TSource, TResult, TResult> func)
    {
        var previousResult = defaultResult;
        foreach (var item in items)
        {
            var result = func(item, previousResult);
            previousResult = result;
            yield return result;
        }
    }
}

以这种方式实现它的优点是你可以重用扩展方法来解决类似的问题,并且你的代码比使用纯循环或复杂的Aggregate表达式获得了一些可读性。