所以最近我遇到了一个问题,我的团队和我需要列出一个对象列表,并按条件对它们进行分组,然后按更多的条件对该组进行分组,然后按甚至更多的条件对该组进行分组,依此类推,持续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;
}
}
答案 0 :(得分:1)
最简单的树只是一个IEnumerable<IEnumerable<...>>
,您可以使用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();
答案 1 :(得分:0)
我还创建了一个树类,但是我使用了一个类来保存每个条件,并使用条件数组来处理分组。每个条件都应返回一个ruletotal[list_->name_]:={name,Total[list]};
Map[ruletotal,InputSamples]
来创建分组。然后,树类可以逐步检查条件以对每个级别进行分组。为了使树统一,我在每个级别上保留了一个成员列表,然后将其分成下一个级别。
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);
答案 2 :(得分:0)
我的建议是创建一个可以过滤您的对象的收集类,并返回其自身的实例,以便过滤可以继续进行下去。例如,假设您的对象的类型为MyObject
:
class MyObject
{
public int Number { get; }
public MyObject(int number) => this.Number = number;
public override string ToString() => this.Number.ToString();
}
这里是过滤集合MyCollection
的示例,它支持对Odd
,Even
,Multiple3
和NonMultiple3
进行过滤。创建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.NonMultiple3
和myObjects.NonMultiple3.Even
。这两个查询都返回相同的结果,但是会导致创建多余的查询。
答案 3 :(得分:0)
NetMage和Theodor的答案正是我根据问题所寻找的。但是,由于示例代码的位置过大,我忽略了提及有时答案返回的结果不只是true或false,而是返回3或4个值(在极少数情况下,其中一个返回值需要是分组并遍历)。这不是他们自己的错,他们的工作实际上非常出色并且可以很好地使用,但是对我而言这是一个现场工作。因此,我决定根据评论选择Ian和Kyles的答案,并提出了以下建议:
虽然它并不完美,但是它确实允许我返回想要的任意多个值,如果需要(在case语句中定义),并且仅需要按2而不是全部3进行过滤,则按分组要更改顺序,我可以根据需要将它们添加到conditions数组中。
再次感谢您的帮助,对不起,我对问题的理解不够清楚。
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;
}
}