动态列表分组

时间:2019-05-05 16:51:33

标签: c# list linq grouping

我想对包含整数List<int>的列表进行分组。

List<CNode> cNodes

并且CNode是

public class CNode
{
    public List<int> Elements;
    // ...
}

我可以像这样将cNodes分组

var groups = cNodes.GroupBy(node => node.Elements[0]);
foreach (var group in groups )
{
    // ...
}

但是您看到分组取决于第一个元素,我想按所有元素分组

例如,如果node.Elements.Count == 5的预期分组结果应与以下内容相同:

var groups = cNodes.GroupBy(node => new
{
    A = node.Elements[0],
    B = node.Elements[1],
    C = node.Elements[2],
    D = node.Elements[3],
    E = node.Elements[4]
});

我找不到解决方案。

谢谢。

2 个答案:

答案 0 :(得分:1)

您可以将node.Take(5)之类的内容与适当的IEqualityComparer一起使用,如下所示:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var cNodes = new List<CNode>
            {
                new CNode{Elements = new List<int>{ 0, 0, 1, 1, 1 } },
                new CNode{Elements = new List<int>{ 0, 0, 0, 1, 1 } },
                new CNode{Elements = new List<int>{ 0, 1, 1, 0 } },
                new CNode{Elements = new List<int>{ 0, 1, 1, 0, 0 } },
                new CNode{Elements = new List<int>{ 0, 0, 0, 0 } },
                new CNode{Elements = new List<int>{ 0, 0, 0, 0 } }
            };

            Console.WriteLine("\tGroup by 2:");
            foreach (var group in cNodes.GroupByElements(2))
                Console.WriteLine($"{string.Join("\n", group)}\n");

            Console.WriteLine("\tGroup by 3:");
            foreach (var group in cNodes.GroupByElements(3))
                Console.WriteLine($"{string.Join("\n", group)}\n");

            Console.WriteLine("\tGroup by all:");
            foreach (var group in cNodes.GroupByElements())
                Console.WriteLine($"{string.Join("\n", group)}\n");
        }
    }

    static class CNodeExtensions
    {
        public static IEnumerable<IGrouping<IEnumerable<int>, CNode>> GroupByElements(this IEnumerable<CNode> nodes) =>
            nodes.GroupByElements(nodes.Min(node => node.Elements.Count));

        public static IEnumerable<IGrouping<IEnumerable<int>, CNode>> GroupByElements(this IEnumerable<CNode> nodes, int count) =>
            nodes.GroupBy(node => node.Elements.Take(count), new SequenceCompare());

        private class SequenceCompare : IEqualityComparer<IEnumerable<int>>
        {
            public bool Equals(IEnumerable<int> x, IEnumerable<int> y) => x.SequenceEqual(y);

            public int GetHashCode(IEnumerable<int> obj)
            {
                unchecked
                {
                    var hash = 17;
                    foreach (var i in obj)
                        hash = hash * 23 + i.GetHashCode();
                    return hash;
                }
            }
        }
    }    

    internal class CNode
    {
        public List<int> Elements;

        public override string ToString() => string.Join(", ", Elements);
    }
}

输出为:

        Group by 2:
0, 0, 1, 1, 1
0, 0, 0, 1, 1
0, 0, 0, 0
0, 0, 0, 0

0, 1, 1, 0
0, 1, 1, 0, 0

        Group by 3:
0, 0, 1, 1, 1

0, 0, 0, 1, 1
0, 0, 0, 0
0, 0, 0, 0

0, 1, 1, 0
0, 1, 1, 0, 0

        Group by all:
0, 0, 1, 1, 1

0, 0, 0, 1, 1

0, 1, 1, 0
0, 1, 1, 0, 0

0, 0, 0, 0
0, 0, 0, 0

答案 1 :(得分:1)

您写道:

  

我想按所有元素分组

Alex提供的解决方案将仅按有限数量的元素分组。您说过,即使您有一个包含100个元素的CNode,也希望按所有元素对其进行分组。此外:如果其中一个CNode的属性Elements等于null,他的解决方案也会崩溃。

因此,让我们创建一个满足您要求的解决方案。

返回值将是一组序列,其中每个组都有一个Key,它是CNode的序列。该组中的所有元素都是具有属性Elements等于Key的所有源CNode。

等于,表示SequenceEqual。因此Elements [0] ==键[0]和Elements [1] ==键[1],等等。

当然,您想确定Elements [0]何时等于Key [0]:是否要按引用进行比较(同一对象)?还是两个具有相同属性值的CNode相等?还是要指定IEqualityComparer<CNode>,以便如果它们具有相同的名称或ID,就可以看到它们相等?

// overload without IEqualityComparer, calls the overload with IEqualityComparer:
IEnumerable<IGrouping<IEnumerable<Cnode>, CNode>> GroupBy(
    this IEnumerable<CNode> cNodes)
{
    return GroupBy(cNodes, null);
}

// overload with IEqualityComparer; use default CNode comparer if paramer equals null
IEnumerable<IGrouping<IEnumerable<Cnode>, CNode>> GroupBy(
    this IEnumerable<CNode> cNodes,
    IEqualityComparer<CNode> cNodeComparer)
{
    // TODO: check cNodes != null
    if (cNodeComparer == null) cNodeComparer = EqualityComparer<CNode>.Default;

    CNodeSequenceComparer nodeSequenceComparer = new CNodeSequenceComparer()
    {
        CNodeComparer = cNodeComparer,
    }
    return sequenceComparer.GroupBy(nodeSequenceComparer);
}

您已经注意到我已经将问题转移到新的EqualityComparer上:此比较使用两个CNode序列,并使用提供的SequenceEqual声明两个IEqualityComparer<CNode>是否相等:

class CNodeSequenceComparer : IEqualityComparer<IEnumerable<CNode>>
{
    public IEqualityComparer<CNode> CNodeComparer {get; set;}
    public bool Equals(IEnumerable<CNode> x, IEnumerable<CNode> y)
    {
        // returns true if same sequence, using CNodeComparer
        // TODO: implement
    }
}

我们要记住的一件事是,您的属性Elements的值可能为null(毕竟,您没有指定不是这种情况)

public bool Equals(IEnumerable<CNode> x, IEnumerable<CNode> y)
{
    if (x == null) return y == null; // true if both null
    if (y == null) return false;     // false because x not null

    // optimizations: true if x and y are same object; false if different types
    if (Object.ReferenceEquals(x, y) return true;
    if (x.GetType() != y.GetType()) return false;

    return x.SequenceEquals(y, this.CNodeComparer);
}