【问题标题】:What is the best way to group groupings of groupings?分组分组的最佳方法是什么?
【发布时间】:2019-10-25 18:56:21
【问题描述】:

所以最近我遇到了一个问题,我和我的团队需要获取一个对象列表,并按条件分组,然后按更多条件分组,然后按更多条件分组,以此类推 7 或所以水平。经过几天的思考,我终于想出了一种树状结构,尽管每个级别都是手动定义的(主要是为了便于阅读,因为一旦编程它就会一成不变)。处理此问题的最佳方法是什么,如果可能,为什么? 到目前为止,这是我使用随机整数列表的结果。检查是:可被 2 整除、被 3 整除和被 5 整除的顺序(尽管条件的顺序与要求无关):

这是整数随机列表加上 TopNode 类的代码

Random rand = new Random();
List<int> ints = new List<int>();
for (int i = 0; i < 10; i++)
{
   ints.Add(rand.Next(0, 10000001));
}

TopNode node = new TopNode(ints);

这是顶层节点类的其余代码

public class TopNode 
{ 
    public Even Even { get; set; }
    public Odd Odd { get; set; }
    public TopNode(List<int> ints)
    {
        var even = ints.Where(x => x % 2 == 0).ToList();
        var odd = ints.Where(x => x % 2 != 0).ToList();
        if (even.Count > 0)
        {
            Even = new Even(even);
        }
        if (odd.Count > 0)
        {
            Odd = new Odd(odd);
        }
    }
} 

public class Even {
    public Mulitple3 Mulitple3 { get; set; }
    public NotMulitple3 NotMulitple3 { get; set; }
    public Even(List<int> ints)
    {
        var multiple = ints.Where(x => x % 3 == 0).ToList();
        var not = ints.Where(x => x % 3 != 0).ToList();
        if (multiple.Count > 0)
        {
            Mulitple3 = new Mulitple3(multiple);
        }
        if (not.Count > 0)
        {
            NotMulitple3 = new NotMulitple3(not);
        }
    }
} 

public class Odd {
    public Mulitple3 Mulitple3 { get; set; }
    public NotMulitple3 NotMulitple3 { get; set; }
    public Odd(List<int> ints)
    {
        var multiple = ints.Where(x => x % 3 == 0).ToList();
        var not = ints.Where(x => x % 3 != 0).ToList();
        if (multiple.Count > 0)
        {
            Mulitple3 = new Mulitple3(multiple);
        }
        if (not.Count > 0)
        {
            NotMulitple3 = new NotMulitple3(not);
        }
    }
}

public class Mulitple3
{
    public Multiple5 Multiple5 { get; set; }
    public NotMultiple5 NotMultiple5 { get; set; }
    public Mulitple3(List<int> ints)
    {
        var multiple = ints.Where(x => x % 5 == 0).ToList();
        var not = ints.Where(x => x % 5 != 0).ToList();
        if (multiple.Count > 0)
        {
            Multiple5 = new Multiple5(multiple);
        }
        if (not.Count > 0)
        {
            NotMultiple5 = new NotMultiple5(not);
        }
    }
}

public class NotMulitple3
{
    public Multiple5 Multiple5 { get; set; }
    public NotMultiple5 NotMultiple5 { get; set; }
    public NotMulitple3(List<int> ints)
    {
        var multiple = ints.Where(x => x % 5 == 0).ToList();
        var not = ints.Where(x => x % 5 != 0).ToList();
        if (multiple.Count > 0)
        {
            Multiple5 = new Multiple5(multiple);
        }
        if (not.Count > 0)
        {
            NotMultiple5 = new NotMultiple5(not);
        }
    }
}

public class Multiple5
{
    public List<int> ints { get; set; }
    public Multiple5(List<int> ints)
    {
        this.ints = ints;
    }
}

public class NotMultiple5
{
    public List<int> ints { get; set; }
    public NotMultiple5(List<int> ints)
    {
        this.ints = ints;
    }
}

【问题讨论】:

  • 在树中拥有单独的子组而不只是对象上的属性的目的是什么?
  • @Madmmoore,对我来说看起来不错。由于一切都是通过构造函数初始化的,所以我会将数据结构设为只读:使用{get;} 属性而不是{get;set;},以及public IReadOnlyList&lt;int&gt; ints 而不是public List&lt;int&gt; ints
  • 除非您拥有数百万个对象,否则“最佳”方式可能只是 List&lt;T&gt; 并使用 LINQ GroupBy 对它们进行分组。这是最容易编写、最容易理解、最容易处理不断变化的需求,可以处理内存中的大型集合,并且可以在需要时转换为数据库查询。永远不要低估优化代码扫描对简单数据结构的蛮力速度与围绕一些“聪明”指针网络进行内存跳跃的蛮力速度。
  • @KyleW 因为每个组都需要是其上一个组的一组。所以 {1,2,3,4,5,6,7,8} -> {1,2,3,4} -> {1,3} {2,4} 和 {5,6,7,8 } -> {5,7} {6,8} 等等。我试图想出一种方法来使用属性来做到这一点,但我所有的解决方案最终都会一遍又一遍地进行相同的计算,并且很快就变得非常笨拙
  • 如果我们能看到更接近实际需要的东西可能会有所帮助。使用 LINQ 执行 ints.Where(i =&gt; i.IsEven).Where(i =&gt; i.IsAMultipleOf3) 非常容易使用、修改和理解。如果我们更好地理解您的用例,那么为您推荐更好的东西会更容易。这也很容易摆脱层次结构,所以我很难了解层次结构的目的是什么。

标签: c# linq data-structures


【解决方案1】:

最简单的树只是IEnumerable&lt;IEnumerable&lt;...&gt;&gt;,您可以使用GroupBy 形成它。

这是一个简单的例子,它根据 2、3 和 5 的可分性将一些整数分组为一棵树。它打印:

{{{{1,7,23,29},{5}},{{3,9,87,21}}},{{{4,8,34,56}},{{78},{30}}}}

.

public static void Main()
{
    int[] input = new int[]{1, 3, 4, 5, 7, 8, 9, 23, 34, 56, 78, 87, 29, 21, 2*3*5};

    // TREE
    var groupedTree = input.GroupBy(x => x % 2 == 0)
        .Select(g => g.GroupBy(x => x % 3 == 0)
        .Select(h => h.GroupBy(x => x % 5 == 0)));

    Console.WriteLine(Display(groupedTree));
}

// Hack code to dump the tree
public static string DisplaySequence(IEnumerable items) => "{" + string.Join(",", items.Cast<object>().Select(x => Display(x))) + "}";
public static string Display(object item) => item is IEnumerable seq ? DisplaySequence(seq) : item.ToString();

【讨论】:

    【解决方案2】:

    我还创建了一个树类,但我使用了一个类来保存每个条件,并使用一组条件来处理分组。每个条件都应返回一个int 来创建分组。然后树类可以逐步通过条件对每个级别进行分组。为了使树统一,我在每个级别保留了一个成员列表,然后将其拆分为下一个级别。

    public class Condition<T> {
        public string[] Values;
        public Func<T, int> Test;
    
        public Condition(string[] values, Func<T, int> test) {
            Values = values;
            Test = test;
        }
    }
    
    public class Level {
        public static Level<T> MakeTree<T>(IEnumerable<T> src, Condition<T>[] conditions) => new Level<T>(src, conditions);
        public static IEnumerable<int> MakeKey<T>(Condition<T>[] conditions, params string[] values) {
            for (int depth = 0; depth < values.Length; ++depth)
                yield return conditions[depth].Values.IndexOf(values[depth]);
        }
    }
    
    public class Level<T> {
        public string Value;
        public Level<T>[] NextLevels;
        public List<T> Members;
    
        public Level(string value, List<T> members) {
            Value = value;
            Members = members;
            NextLevels = null;
        }
    
        public Level(IEnumerable<T> src, Condition<T>[] conditions) : this("ALL", src.ToList()) => GroupOneLevel(this, 0, conditions);
    
        public void GroupOneLevel(Level<T> parent, int depth, Condition<T>[] conditions) {
            var condition = conditions[depth];
            var nextLevels = new Level<T>[condition.Values.Length];
            for (int j2 = 0; j2 < condition.Values.Length; ++j2) {
                nextLevels[j2] = new Level<T>(condition.Values[j2], new List<T>());
            }
    
            for (int j2 = 0; j2 < parent.Members.Count; ++j2) {
                var member = parent.Members[j2];
                nextLevels[condition.Test(member)].Members.Add(member);
            }
            parent.NextLevels = nextLevels;
            if (depth + 1 < conditions.Length)
                for (int j3 = 0; j3 < condition.Values.Length; ++j3)
                    GroupOneLevel(nextLevels[j3], depth + 1, conditions);
        }
    
        public List<T> MembersForKey(IEnumerable<int> values) {
            var curLevel = this;
            foreach (var value in values)
                curLevel = curLevel.NextLevels[value];
    
            return curLevel.Members;
        }
    }
    

    对于您的示例,您可以这样使用:

    var conditions = new[] {
            new Condition<int>(new[] { "Even", "Odd" }, n => n & 1),
            new Condition<int>(new[] { "Div3", "NOTDiv3" }, n => n % 3 == 0 ? 0 : 1),
            new Condition<int>(new[] { "Div5", "NOTDiv5" }, n => n % 5 == 0 ? 0 : 1)
        };
    
    var ans = Level.MakeTree(ints, conditions);
    

    您可以使用以下命令查找树的特定部分:

    var evenDiv3 = ans.MembersForKey(Level.MakeKey(conditions, "Even", "Div3"));
    

    【讨论】:

      【解决方案3】:

      我的建议是创建一个可以过滤您的对象的集合类,并返回其自身的实例,以便过滤可以继续深入。例如,假设您的对象是MyObject 类型:

      class MyObject
      {
          public int Number { get; }
          public MyObject(int number) => this.Number = number;
          public override string ToString() => this.Number.ToString();
      }
      

      这里是过滤集合MyCollection 的示例,它支持对OddEvenMultiple3NonMultiple3 的过滤。所需的lookups 是创建lazily,以避免为永远不会请求的搜索分配内存:

      class MyCollection : IEnumerable<MyObject>
      {
          private readonly IEnumerable<MyObject> _source;
      
          private readonly Lazy<ILookup<bool, MyObject>> _multiple2Lookup;
          private readonly Lazy<MyCollection> _even;
          private readonly Lazy<MyCollection> _odd;
      
          private readonly Lazy<ILookup<bool, MyObject>> _multiple3Lookup;
          private readonly Lazy<MyCollection> _multiple3;
          private readonly Lazy<MyCollection> _nonMultiple3;
      
          public MyCollection Even => _even.Value;
          public MyCollection Odd => _odd.Value;
      
          public MyCollection Multiple3 => _multiple3.Value;
          public MyCollection NonMultiple3 => _nonMultiple3.Value;
      
          public MyCollection(IEnumerable<MyObject> source)
          {
              _source = source;
      
              _multiple2Lookup = new Lazy<ILookup<bool, MyObject>>(
                  () => _source.ToLookup(o => o.Number % 2 == 0));
              _even = new Lazy<MyCollection>(
                  () => new MyCollection(_multiple2Lookup.Value[true]));
              _odd = new Lazy<MyCollection>(
                  () => new MyCollection(_multiple2Lookup.Value[false]));
      
              _multiple3Lookup = new Lazy<ILookup<bool, MyObject>>(
                  () => _source.ToLookup(o => o.Number % 3 == 0));
              _multiple3 = new Lazy<MyCollection>(
                  () => new MyCollection(_multiple3Lookup.Value[true]));
              _nonMultiple3 = new Lazy<MyCollection>(
                  () => new MyCollection(_multiple3Lookup.Value[false]));
          }
      
          public IEnumerator<MyObject> GetEnumerator() => _source.GetEnumerator();
          IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
      }
      

      使用示例:

      var source = Enumerable.Range(1, 20).Select(i => new MyObject(i));
      var myObjects = new MyCollection(source);
      var filtered = myObjects.Even.NonMultiple3;
      Console.WriteLine(String.Join(", ", filtered));
      

      输出:

      2、4、8、10、14、16、20

      这种方法的一个可能缺点是它允许同时调用myObjects.Even.NonMultiple3myObjects.NonMultiple3.Even。两个查询都返回相同的结果,但会导致创建冗余查找。

      【讨论】:

        【解决方案4】:

        NetMage 和 Theodor 的答案正是我根据问题所寻找的。但是,由于我的示例代码中的过度网站,我没有提到有时答案会返回不仅仅是真或假,而是会返回 3 或 4 个值(并且在极少数情况下需要返回值之一分组和迭代)。这不是他们自己的错,他们的工作实际上非常好并且很好用,但对我来说这是一个过度的地方。因此,我决定根据 cmets 采用 Ian 和 Kyles 的答案,并提出以下建议:

        虽然它并不完美,但它确实允许我返回任意数量的值,根据需要分组(在 case 语句中定义),如果我只需要过滤 2 而不是全部 3 或需要要更改顺序,我可以根据需要将它们添加到条件数组中。

        再次感谢您的帮助,很抱歉我的问题不够清楚。

        Random rand = new Random();
        List<int> ints = new List<int>();
        for (int i = 0; i < 10000000; i++)
        {
           ints.Add(rand.Next(0, 10000001));
        }
        string[] conditions = new string[] { "even", "div3", "div5" };
        var dynamicSort = new Sorted(ints);
        
        public class Sorted
        {
            public List<List<int>> returnVal { get; set; }
            public static List<int> Odd(List<int> ints)
            {
                return ints.Where(x => x % 2 != 0).ToList();
            }
            public static List<int> Even(List<int> ints)
            {
                return ints.Where(x => x % 2 == 0).ToList();
            }
            public static List<int> DivThree(List<int> ints)
            {
                return ints.Where(x => x % 3 == 0).ToList();
            }
            public static List<int> NotDivThree(List<int> ints)
            {
                return ints.Where(x => x % 3 != 0).ToList();
            }
            public static List<int> DivFive(List<int> ints)
            {
                return ints.Where(x => x % 5 == 0).ToList();
            }
            public static List<int> NotDivFive(List<int> ints)
            {
                return ints.Where(x => x % 5 != 0).ToList();
            }
        
            public Sorted(List<int> ints, string[] conditions)
            {
                returnVal = GetSorted(ints, conditions, 0);
            }
            public List<List<int>> GetSorted(List<int>ints, string[] conditions, int index)
            {
                var sortReturn = new List<List<int>>();
                switch (conditions[index].ToLower()) 
                {
                    case "even":
                    case "odd":
                        {
                            if (index == conditions.Length - 1)
                            {
                                sortReturn.Add(Odd(ints));
                                sortReturn.Add(Even(ints));
                            }
                            else
                            {
                                var i = ++index;
                                sortReturn.AddRange(GetSorted(Odd(ints), conditions, i));
                                sortReturn.AddRange(GetSorted(Even(ints), conditions, i));
                            }
                            break;
                        }
                    case "div3":
                    case "notdiv3":
                        {
                            if (index == conditions.Length - 1)
                            {
                                sortReturn.Add(DivThree(ints));
                                sortReturn.Add(NotDivThree(ints));
                            }
                            else
                            {
                                var i = ++index;
                                sortReturn.AddRange(GetSorted(DivThree(ints), conditions, i));
                                sortReturn.AddRange(GetSorted(NotDivThree(ints), conditions, i));
                            }
                            break;
                        }
                    case "div5":
                    case "notdiv5":
                        {
                            if (index == conditions.Length - 1)
                            {
                                sortReturn.Add(DivFive(ints));
                                sortReturn.Add(NotDivFive(ints));
                            }
                            else
                            {
                                var i = ++index;
                                sortReturn.AddRange(GetSorted(DivFive(ints), conditions, i));
                                sortReturn.AddRange(GetSorted(NotDivFive(ints), conditions, i));
                            }
                            break;
                        }
                }
                return sortReturn;
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-11-20
          • 2018-10-06
          • 2019-04-18
          • 2015-09-25
          • 1970-01-01
          • 1970-01-01
          • 2020-11-21
          • 2015-12-29
          相关资源
          最近更新 更多