4

我一直在寻找这个,我确信我只是想念它,因为我对 Linq 不是很好。

我有一个看起来像的列表:

type=a, value=aaaa
type=a, value=bbbb
type=b, value=cccc
type=d, value=dddd
type=d, value=eeee
type=d, value=ffff
type=a, value=gggg
type=b, value=hhhh
type=b, value=iiii
type=b, value=jjjj

我想将其分解为子列表,而不进行排序(我需要保持原始顺序)。我想按列表或类似列表中的顺序取回这些列表:

List 1
type=a, value=aaaa
type=a, value=bbbb

List2
type=b, value=cccc

List 3
type=d, value=dddd
type=d, value=eeee
type=d, value=ffff

List 4
type=a, value=gggg

List 5
type=b, value=hhhh
type=b, value=iiii
type=b, value=jjjj

我想循环不是最好的答案。

任何想法都非常感谢。

stackoverflow.com 万岁!

克里斯

四个答案后编辑:

我检查了以下答案: * Enigmativity * Bert Evans * Risky Martin * david.s

他们都工作得很好。Bert Evans 提出了性能问题,在这种情况下这对我来说并不是一个大问题,但为了这篇文章,我做了一些快速检查。

我没有修改任何人的代码,只是在相当短的列表上安排了 4,000 次这样的操作。

Risky的回答是最快的。伯特的回答只是慢了一点。

david's 和 Enigmativity 的速度几乎没有变慢,真的。

由于性能和早期指向相关帖子,我将 Risky 的答案标记为已接受,然后回来提供答案。

不过,我同意 Bert 的可读性最强。

老实说,我不知道我将使用哪一个...实际上,我将使用 Enigmativity 的解决方案,因为它已经考虑到我只需要每个子组的值和一个键。

4

4 回答 4

4

从这个答案和这个答案中使用稍微修改的扩展方法:

public static IEnumerable<IGrouping<int, T>> GroupConsecutive<T>(this IEnumerable<T> set, Func<T, T, bool> predicate)
{
    var i = 0;
    var k = 0;
    var ranges = from e in set
                    let idx = ++i
                    let next = set.ElementAtOrDefault(idx)
                    let key = next == null ? k : predicate(e, next) ? k : k++
                    group e by key into g
                    select g;
    return ranges;
}

并给出一个类:

public class Foo
{
    public string Type { get; set; }
    public string Value { get; set; }
}

你可以这样做:

List<Foo> list = new List<Foo>()
{
    new Foo() { Type = "a", Value = "aaaa" },
    new Foo() { Type = "a", Value = "bbbb" },
    new Foo() { Type = "b", Value = "cccc" },
    new Foo() { Type = "d", Value = "dddd" },
    new Foo() { Type = "d", Value = "eeee" },
    new Foo() { Type = "d", Value = "ffff" },
    new Foo() { Type = "a", Value = "gggg" },
    new Foo() { Type = "b", Value = "hhhh" },
    new Foo() { Type = "b", Value = "iiii" },
    new Foo() { Type = "b", Value = "jjjj" }
};

var groups = list.GroupConsecutive((a, b) => a.Type == b.Type);

foreach (var group in groups)
{
    Console.WriteLine("List " + group.Key);
    foreach (var item in group)
    {
        Console.WriteLine("Type=" + item.Type + "    Value=" + item.Value);
    }
    Console.WriteLine();
}

结果如下所示:

List 0
Type=a    Value=aaaa
Type=a    Value=bbbb

List 1
Type=b    Value=cccc

List 2
Type=d    Value=dddd
Type=d    Value=eeee
Type=d    Value=ffff

List 3
Type=a    Value=gggg

List 4
Type=b    Value=hhhh
Type=b    Value=iiii
Type=b    Value=jjjj
于 2012-07-21T23:16:20.910 回答
3

这可以推广或制成扩展方法,但你明白了:

public static IEnumerable<List<Item>> GroupConsecutive(IEnumerable<Item> items)
{
    if (items.Any())
    {
        string firstType = items.Select(i => i.Type).First();
        var adjacents = items.TakeWhile(i => i.Type == firstType).ToList();
        yield return adjacents;
        foreach (var group in GroupConsecutive(items.Skip(adjacents.Count)))
        {
            yield return group;
        }
    }
}

使用这个类:

public class Item
{
    public string Type { get; set; }
    public string Value { get; set; }
}

编辑:以下是此解决方案的权衡:

优点:

  • 返回一个惰性求值的集合
  • 不改变任何变量
  • 简洁

缺点:

  • 迭代items两次。items如果是一个列表,这没什么大不了的,但如果items是一个为每个项目执行昂贵计算的 IEnumerable,则此方法可能比其他方法慢。

如果您想items通过惰性评估进行一次迭代,我建议使用答案中提到的GroupAdjacent扩展方法或使用. 如果您想要一次没有惰性评估的迭代,我建议使用循环或方法。yield returnAggregate

于 2012-07-22T00:00:28.790 回答
2

这是一个仅使用 LINQ 的答案Aggregate

我从这个开始:

var list = new []
{
    new { type="a", value="aaaa", },
    new { type="a", value="bbbb", },
    new { type="b", value="cccc", },
    new { type="d", value="dddd", },
    new { type="d", value="eeee", },
    new { type="d", value="ffff", },
    new { type="a", value="gggg", },
    new { type="b", value="hhhh", },
    new { type="b", value="iiii", },
    new { type="b", value="jjjj", },
};

然后这样做:

var accumulator = new List<KeyValuePair<string, List<string>>>()
{
    new KeyValuePair<string, List<string>>(
        list.First().type,
        new List<string>()),
};

var results = list.Aggregate(accumulator, (a, x) =>
{
    if (a.Last().Key == x.type)
    {
        a[a.Count - 1].Value.Add(x.value);
    }
    else
    {
        a.Add(new KeyValuePair<string, List<string>>(
            x.type,
            new List<string>(new [] { x.value, })));
    }
    return a;
});

然后results看起来像这样:

结果

让我知道这是否适合您。

于 2012-07-21T23:57:35.917 回答
2

没有 LINQ、普通的旧 c# 和每个初级开发人员都希望遵循的单个循环。更别提更快了。

public class Foo
{
    public string Type { get; set; }
    public string Value { get; set; }
}

List<Foo> list = new List<Foo>()
{
    new Foo() { Type = "a", Value = "aaaa" },
    new Foo() { Type = "a", Value = "bbbb" },
    new Foo() { Type = "b", Value = "cccc" },
    new Foo() { Type = "d", Value = "dddd" },
    new Foo() { Type = "d", Value = "eeee" },
    new Foo() { Type = "d", Value = "ffff" },
    new Foo() { Type = "a", Value = "gggg" },
    new Foo() { Type = "b", Value = "hhhh" },
    new Foo() { Type = "b", Value = "iiii" },
    new Foo() { Type = "b", Value = "jjjj" }
};

Foo previous = null;
List<List<Foo>> separateLists = new List<List<Foo>>();
List<Foo> currentList = null;
foreach (var foo in list)
{
    if (null == previous || previous.Type != foo.Type)
    {
        currentList = new List<Foo>();
        separateLists.Add(currentList);
    }
    currentList.Add(foo);
    previous = foo;
}
于 2012-07-22T03:54:20.443 回答