9

有一个对象的集合。示意图:

[
    { A = 1, B = 1 }
    { A = 1, B = 2 }
    { A = 2, B = 3 }
    { A = 2, B = 4 }
    { A = 1, B = 5 }
    { A = 3, B = 6 }
]

需要:

[
    { A = 1, Bs = [ 1, 2 ] }
    { A = 2, Bs = [ 3, 4 ] }
    { A = 1, Bs = [ 5 ] }
    { A = 3, Bs = [ 6 ] }
]

有可能这样的LINQ吗?

注意:订购很重要。所以Bs = [5]不能合并Bs = [1, 2]

4

7 回答 7

2

鉴于这些简单的类:

class C {
  public int A;
  public int B;
}
class R {
  public int A;
  public List<int> Bs = new List<int>();
}

你可以这样做:

var cs = new C[] {
  new C() { A = 1, B = 1 },
  new C() { A = 1, B = 2 },
  new C() { A = 2, B = 3 },
  new C() { A = 2, B = 4 },
  new C() { A = 1, B = 5 },
  new C() { A = 3, B = 6 }
};

var rs = cs.
  OrderBy(o => o.B).
  ThenBy(o => o.A).
  Aggregate(new List<R>(), (l, o) => {
    if (l.Count > 0 && l.Last().A == o.A) {
      l.Last().Bs.Add(o.B);
    }
    else {
      l.Add(new R { A = o.A, Bs = { o.B } });
    }
    return l;
  });

注意:在上面我假设必须对 Bs 和 As 进行排序。如果不是这种情况,只需删除排序指令即可:

var rs = cs.
  Aggregate(new List<R>(), (l, o) => {
    if (l.Count > 0 && l.Last().A == o.A) {
      l.Last().Bs.Add(o.B);
    }
    else {
      l.Add(new R { A = o.A, Bs = { o.B } });
    }
    return l;
  });
于 2012-07-16T22:22:08.880 回答
2

所以基本上你想将具有相同A-value 并且是连续的组合在一起。

您需要将对象列表转换为包含上一个/下一个元素的匿名类型。我用了两个Selects让它更红。然后您需要检查这两个元素是否是连续的(相邻索引)。现在,您拥有所需的一切GroupBy、价值和bool.

你的对象:

var list = new System.Collections.Generic.List<Foo>(){
    new Foo(){ A = 1, B = 1 },
    new Foo(){ A = 1, B = 2 },
    new Foo(){ A = 2, B = 3 },
    new Foo(){ A = 2, B = 4 },
    new Foo(){ A = 1, B = 5 },
    new Foo(){ A = 3, B = 6 }
};

查询:

var groups = list
    .Select((f, i) => new
    {
        Obj = f,
        Next = list.ElementAtOrDefault(i + 1),
        Prev = list.ElementAtOrDefault(i - 1)
    })
    .Select(x => new
    {
        A = x.Obj.A,
        x.Obj,
        Consecutive = (x.Next != null && x.Next.A == x.Obj.A)
                   || (x.Prev != null && x.Prev.A == x.Obj.A)
    })
    .GroupBy(x => new { x.Consecutive, x.A });

输出结果:

foreach (var abGroup in groups)
{
    int aKey = abGroup.Key.A;
    var bList = string.Join(",", abGroup.Select(x => x.Obj.B));
    Console.WriteLine("A = {0}, Bs = [ {1} ] ", aKey, bList);
}

这是工作演示:http : //ideone.com/fXgQ3

于 2012-07-16T22:57:08.593 回答
2

您可以使用GroupAdjacent 扩展方法

然后,你只需要

var grps = objects.GroupAdjacent(p => new { p.A });

我认为这是实现它的最简单方法。

编辑:

这是我的测试代码。

class Program
{
    static void Main(string[] args)
    {
       var ia = new Dummycls[] { 
           new Dummycls{ A = 1, B = 1 },
           new Dummycls{ A = 1, B = 2 },
           new Dummycls{ A = 2, B = 3 },
           new Dummycls{ A = 2, B = 4 },
           new Dummycls{ A = 1, B = 5 },
           new Dummycls{ A = 3, B = 6 },

       };
        var groups = ia.GroupAdjacent(i => i.A);
        foreach (var g in groups)
        {
            Console.WriteLine("Group {0}", g.Key);
            foreach (var i in g)
                Console.WriteLine(i.ToString());
            Console.WriteLine();
        }

        Console.ReadKey();
    }
}

class Dummycls
{
    public int A { get; set; }
    public int B { get; set; }

    public override string ToString()
    {
        return string.Format("A={0};B={1}" , A , B);
    }
}

结果是

Group 1
A=1;B=1
A=1;B=2

Group 2
A=2;B=3
A=2;B=4

Group 1
A=1;B=5

Group 3
A=3;B=6
于 2012-07-17T00:00:14.623 回答
1

这是执行您想要的方法的结构:

public static IEnumerable<IGrouping<TKey, TElement>> GroupWithKeyBreaks<T, TKey, TElement>(IEnumerable<T> enumerable,
        Func<T, TKey> keySelector,
        Func<T, TElement> itemSelector)    
{
    // Error handling goes here
    TKey currentKey = default(TKey);
    List<TElement> elements = new List<TElement>();

    foreach (T element in enumerable)
    {
        TKey thisKey = keySelector(element);
        if (thisKey == null)
        {
            continue;
        }

        if (!thisKey.Equals(currentKey) && elements.Count > 0)
        {
            yield return new SimpleGrouping<TKey, TElement>(currentKey, elements);
            elements = new List<TElement>();
        }

        elements.Add(itemSelector(element));
        currentKey = thisKey;
     }

    // Add the "last" item
    if (elements.Count > 0)
    {
        yield return new SimpleGrouping<TKey, TElement>(currentKey, elements);
    }
}

它使用以下帮助类:

private class SimpleGrouping<T, U> : IGrouping<T, U>
{
    private T key;
    private IEnumerable<U> grouping;

    T IGrouping<T, U>.Key
    {
        get { return key; }
    }

    IEnumerator<U> IEnumerable<U>.GetEnumerator()
    {
        return grouping.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return grouping.GetEnumerator();
    }

    public SimpleGrouping(T k, IEnumerable<U> g)
    {
        this.key = k;
        this.grouping = g;
    }
}

这是一个示例用法:

foreach (var grouping in data.GroupWithKeyBreaks(x => x.A, x => x.B))
{
    Console.WriteLine("Key: " + grouping.Key);
    foreach (var element in grouping)
    {
        Console.Write(element);
    }
}
于 2012-07-16T22:01:08.267 回答
1
var result = list.ToKeyValuePairs(x => x.A)
                 .Select(x => new { A = x.Key, Bs = x.Value.Select(y => y.B) }); 

foreach (var item in result)
{
    Console.WriteLine("A = {0} Bs=[{1}]",item.A, String.Join(",",item.Bs));
}

-

public static class MyExtensions
{
    public static IEnumerable<KeyValuePair<S,IEnumerable<T>>> ToKeyValuePairs<T,S>(
            this IEnumerable<T> list, 
            Func<T,S> keySelector)
    {
        List<T> retList = new List<T>();
        S prev = keySelector(list.FirstOrDefault());
        foreach (T item in list)
        {
            if (keySelector(item).Equals(prev))
                retList.Add(item);
            else
            {
                yield return new KeyValuePair<S, IEnumerable<T>>(prev, retList);
                prev = keySelector(item);
                retList = new List<T>();
                retList.Add(item);
            }
        }
        if(retList.Count>0)
            yield return new KeyValuePair<S, IEnumerable<T>>(prev, retList);
    }
}

输出:

A = 1 Bs=[1,2]
A = 2 Bs=[3,4]
A = 1 Bs=[5]
A = 3 Bs=[6]
于 2012-07-16T22:48:48.823 回答
1
var groupCounter = 0;
int? prevA = null;

collection
    .Select(item => { 
        var groupId = item.A == prevA ? groupCounter : ++groupCounter; 
        prevA = item.A;
        return new { groupId, item.A, item.B }; 
    })
    .GroupBy(item => item.groupId)
    .Select(grp => new { A = grp.First().A, Bs = grp.Select(g => g.B) });
于 2012-07-16T23:08:17.850 回答
1

如果您的收藏在 中o,则:

    var trans = o.Aggregate
    (
            new {
                List = new List<Tuple<int, List<int>>>(),
                            LastSeed = (int?)0
            },
            (acc, item) =>
            {
                if (acc.LastSeed == null || item.A != acc.LastSeed)
                    acc.List.Add(Tuple.Create(item.A, new List<int>()));
                acc.List[acc.List.Count - 1].Item2.Add(item.B);
                return new { List = acc.List, LastSeed = (int?)item.A};
            },
            acc => acc.List.Select(
                  z=>new {A = z.Item1,
                          B = z.Item2 as IEnumerable<int>
                         })
       );

这会产生IEnumerable<int, IEnumerable<int>>所需的表格。

于 2012-07-16T23:52:42.043 回答