使用LINQ进行“智能”分组

时间:2011-02-02 23:56:30

标签: c# .net linq list dictionary

我有一个字符串列表,我想将其转换为某种分组列表,其中值将按列表中的位置进行分组(不是正常分组,但在某种程度上,相同的项目在只有当他们在一起时才成为一个团体)。请考虑以下示例:

LinkedList<string> myList = new LinkedList<string>();
myList.AddLast("aaa");
myList.AddLast("aaa");
myList.AddLast("bbb");
myList.AddLast("bbb");
myList.AddLast("aaa");
myList.AddLast("aaa");
myList.AddLast("aaa");

LinkedList<MyTuple> groupedList = new LinkedList<MyTuple>();
groupedList.AddLast(new MyTuple("aaa", 2));
groupedList.AddLast(new MyTuple("bbb", 2));
groupedList.AddLast(new MyTuple("aaa", 3));

这个转换可以用LINQ完成,还是应该用循环编写算法?

2 个答案:

答案 0 :(得分:4)

来自this answer的扩展方法几乎与您提出的要求相同(Microsoft也提供了implementation to group contiguous items in a sequence):

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 = (predicate(e, next)) ? k : k++
                 group e by key into g
                 select g;
    return ranges;
}

您可以按如下方式使用它:

void Main()
{
    LinkedList<string> myList = new LinkedList<string>();
    myList.AddLast("aaa");
    myList.AddLast("aaa");
    myList.AddLast("bbb");
    myList.AddLast("bbb");
    myList.AddLast("aaa");
    myList.AddLast("aaa");
    myList.AddLast("aaa");
    IGrouping<int,string> ggg;

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

    ILookup<string,int> lookup=groups.ToLookup(g=>g.First(),g=>g.Count());

    foreach(var x in lookup["aaa"])
    {
        Console.WriteLine(x); //outputs 2 then 3
    }
    foreach(var x in lookup["bbb"])
    {
        Console.WriteLine(x); //outputs 2
    }

}

请注意,最终容器是ILookup,其行为有点像Dictionary,但允许一个容器针对单个键存储多个值。

答案 1 :(得分:1)

“词典”无法做到这一点。字典是关联的(即:每个键必须指向一个和一个),并且本质上是无序的。您需要为该数据结构使用其他东西。虽然这不会太难!

修改

List<Tuple<string, int>>应该可以解决问题:

List<KeyValuePair<string, int>> structure = new List<KeyValuePair<string, int>>();
structure.Add(new KeyValuePair<string, int>(myList[0], 1);
for(int i = 0; i < myList.Count; i++ )
{
    if( myList[i] == structure[structure.Count-1].Key )
    {
        structure[structure.Count-1].Value += 1;
    }
    else
    {
        structure.Add(new KeyValuePair<string, int>(myList[i], 1);
    }
}

之后你应该(未经测试!)拥有你想要的东西。

编辑(再多想一想)

虽然有可能使用linq(使用TakeWhile并计算...),但我仍然认为在这里使用循环更有意义,这很简单。比我更有能力尝试和Linq一起工作的人。