简体   繁体   English

分组分组的最佳方法是什么?

[英]What is the best way to group groupings of groupings?

So recently I ran into a problem, my team and I need to take a list of objects, and group them by conditions, then that group by more conditions, then that group by even more conditions, and so on for 7 or so levels.所以最近我遇到了一个问题,我和我的团队需要获取一个对象列表,然后按条件对它们进行分组,然后按更多条件分组,然后按更多条件分组,以此类推,共 7 个左右的级别。 After thinking on it for a few days I finally came up with sort of a tree structure, although each level is manually defined (mainly for ease of reading, because once it is programed it will be set in stone).经过几天的思考,我终于想出了一个树形结构,虽然每个级别都是手动定义的(主要是为了便于阅读,因为一旦编程它就会一成不变)。 What is the best method to handle for this, and if possible, why?处理此问题的最佳方法是什么,如果可能,为什么? Here's what I have so far using a list of random integers.到目前为止,这是我使用随机整数列表的内容。 The checks are: divisible by 2, divisible by 3, and divisible by 5 in that order (although the order of conditions don't matter for the requirements):检查是:可被 2 整除、被 3 整除和被 5 整除的顺序(尽管条件的顺序与要求无关):

Here's the code for the random list of integers plus the TopNode class这是整数随机列表加上 TopNode class 的代码

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);

Here's the rest of the code for the top node class这是顶部节点 class 的代码的 rest

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;
    }
}

The simplest tree is just an IEnumerable<IEnumerable<...>> and you can form it using GroupBy .最简单的树只是一个IEnumerable<IEnumerable<...>> ,您可以使用GroupBy形成它。

Here's a simple example that groups some integers into a tree based on divisibility by 2, 3 and 5. It prints:这是一个简单的示例,它根据 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();

I also created a tree class, but I used a class to hold each condition, and an array of conditions to handle the grouping.我还创建了一个树 class,但我使用 class 来保存每个条件,并使用一组条件来处理分组。 Each condition is expected to return an int to create the grouping.每个条件都应返回一个int来创建分组。 Then the tree class can step through the conditions to group each level.然后树 class 可以逐步通过条件对每个级别进行分组。 To make the tree uniform, I kept a list of members at each level which is then split into the next level.为了使树统一,我在每个级别保留了一个成员列表,然后将其拆分为下一个级别。

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;
    }
}

For your example, you can use this like:对于您的示例,您可以像这样使用:

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);

And you can lookup a particular part of the tree with:您可以使用以下命令查找树的特定部分:

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

My suggestion is to create a collection class that can filter your objects, and returns instances of itself so that the filtering can continue deeper.我的建议是创建一个集合 class 可以过滤您的对象,并返回自身的实例,以便过滤可以继续深入。 For example lets assume that your objects are of type MyObject :例如,假设您的对象是MyObject类型:

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

Here is an example of the filtering collection MyCollection , that supports filtering for Odd , Even , Multiple3 and NonMultiple3 .这是过滤集合MyCollection的示例,它支持OddEvenMultiple3NonMultiple3的过滤。 The lookups required are created lazily , to avoid allocating memory for searches that will never be requested:所需的查找惰性创建的,以避免为永远不会请求的搜索分配 memory:

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();
}

Usage example:使用示例:

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));

Output: Output:

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

A possible drawback of this approach is that it allows calling both myObjects.Even.NonMultiple3 and myObjects.NonMultiple3.Even .这种方法的一个可能缺点是它允许调用myObjects.Even.NonMultiple3myObjects.NonMultiple3.Even Both queries return the same results, but cause the creation of redundant lookups.两个查询都返回相同的结果,但会导致创建冗余查找。

NetMage and Theodor's answers were exactly what I was looking for as per the question. NetMage 和 Theodor 的答案正是我根据问题所寻找的。 However, due to an oversite in my example code, I neglected to mention that sometimes the answer would return with more than just true or false and instead would return 3 or 4 values (and in very rare occasions one of the return values needs to be grouped and iterated over).但是,由于我的示例代码中的过度网站,我没有提到有时答案会返回不仅仅是真或假,而是会返回 3 或 4 个值(在极少数情况下需要返回值之一分组和迭代)。 This is not a fault of their own, and their work is actually very good and serves good use, but it was an oversite on my part.这不是他们自己的错,他们的工作实际上非常好并且很有用,但对我来说这是一个过度的地方。 Due to this of this I decided to go with Ian's and Kyles answers based on the comments and came up with this:由于这个原因,我决定 go 与基于评论的伊恩和凯尔斯的答案,并想出了这个:

While it's not perfect, it does allow me to return as many values as I want, group by if I need to (defined in the case statements), and if I only need to filter by 2 and not all 3 or need to change the order, I can add them to the conditions array as I need them.虽然它并不完美,但它确实允许我返回任意数量的值,如果需要(在 case 语句中定义)分组,如果我只需要过滤 2 而不是全部 3 或需要更改顺序,我可以根据需要将它们添加到条件数组中。

Again thanks for the help and I'm sorry I wasn't clear enough in the question.再次感谢您的帮助,很抱歉我的问题不够清楚。

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;
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM