What is the best way to group groupings of groupings? - c#

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. 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):
Here's the code for the random list of integers plus the 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
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.
Here's a simple example that groups some integers into a tree based on divisibility by 2, 3 and 5. It prints:
{{{{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. Each condition is expected to return an int to create the grouping. Then the tree class can step through the conditions to group each level. 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. For example lets assume that your objects are of type 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. The lookups required are created lazily, to avoid allocating memory for searches that will never be requested:
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:
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. 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. 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). 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:
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.
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;
}
}

Related

Linq Aggregate gives null value rather than expected return List

I'm attempting to utilize linq aggregate in order to mimic a reduce behavior. I need GroupingMethod to return a List of arrays.For some reason GroupingMethod returns null no matter what. I appreciate any help.
List<Group> groupings = GroupingMethod(new int[] {9,6,9,3,5,3,6,6,3,5,5,1,6,9,8});
static List<Group> GroupingMethod(int[] ValueList)
{
int groupTotal = 0;
Group newGroup = new Group();
var put = ValueList.Aggregate(new List<Group>(), (acc, x) =>
{
List<Group> polp = acc;
groupTotal = groupTotal + x;
if (groupTotal < 10)
{
newGroup.AddInt(x);
} else
{
polp.Add(newGroup);
newGroup.Clear();
groupTotal = 0;
}
return polp;
});
return put;
}
class Group
{
List<int> iGrouping = new List<int>();
public Group(int AddInt)
{
iGrouping.Add(AddInt);
}
public Group()
{
}
public void AddInt(int IntToAdd)
{
iGrouping.Add(IntToAdd);
}
public void Clear()
{
iGrouping.Clear();
}
public int CombinedGroupValue { get{return iGrouping.Sum();}}
}

Insertion sort in one method using class object

I am building my lab for college and I am new to .net framwork. I wanna to sort the data but I need to follow certain rule for this lab one of them is I can not use list it has to be array.
Here is my menu
1 - Sort by Employee Name (ascending)
2 - Sort by Employee Number (ascending)
3 - Sort by Employee Pay Rate (descending)
4 - Sort by Employee Hours (descending)
5 - Sort by Employee Gross Pay (descending)
6 - Exit
I have a data in model class and I store data in my employees array. I shorted my menu 2 but I need global function for all menu.
Sorting code
for (int i = 1; i < employees.Length; i++)
{
Employee current = employees[i];
int j = i - 1;
for (; j >= 0 && current.GetNumber() > employees[j].GetNumber(); j--)
{
employees[j + 1] = employees[j];
}
employees[j + 1] = current;
}
foreach (Employee employee in employees)
{
Console.WriteLine(employee);
}
Question : Is there any way that I can make one function that gives me
different output for each menu because It is criteria that i can use
one function to complete the all menu.
If need anything from my side please comment.
You can use delegates to solve your problem. So you will have a common method to sort your array that will also accept the delegate on how to compare 2 Employees:
public Employee[] Sort(Employee[] employees, Func<Employee, Employee, bool> comparer)
{
for (int i = 1; i < employees.Length; i++)
{
Employee current = employees[i];
int j = i - 1;
for (; j >= 0 && comparer(current,employees[j]); j--)
{
employees[j + 1] = employees[j];
}
employees[j + 1] = current;
}
return employees;
}
And an example of usage will be
Sort(employees, (e1, e2) => e1.GetNumber() > e2.GetNumber())
or
Sort(employees, (e1, e2) => string.Compare(e1.Name, e2.Name) < 0)
I changed your code. One can retrieve property and then use it:
public void Sort(Employee[] employees, string propertyName)
{
var desiredProperty = typeof(Employee).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
PropertyInfo info = typeof(Employee).GetProperty(propertyName);
if (info == null) { return; }
for (int i = 1; i < employees.Length; i++)
{
Employee current = employees[i];
int j = i - 1;
int curValue = Convert.ToInt32(info.GetValue(current));
int prevValue = Convert.ToInt32(info.GetValue(employees[j]));
for (; j >= 0 && curValue > prevValue; j--)
{
employees[j + 1] = employees[j];
}
employees[j + 1] = current;
}
foreach (Employee employee in employees)
{
Console.WriteLine(info.GetValue(employee));
}
}
create extention class
public static class OrderedEnumerable
{
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
{
if (source == null)
{
throw new ArgumentNullException();
}
if (keySelector == null)
{
throw new ArgumentNullException();
}
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2 = (x, y) => comparer.Compare(keySelector(x), keySelector(y));
return new OrderedEnumerableImpl<TSource>(source, comparer2);
}
public static IOrderedEnumerable<TSource> OrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
{
if (source == null)
{
throw new ArgumentNullException();
}
if (keySelector == null)
{
throw new ArgumentNullException();
}
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2 = (x, y) => comparer.Compare(keySelector(y), keySelector(x));
return new OrderedEnumerableImpl<TSource>(source, comparer2);
}
private class OrderedEnumerableImpl<TSource> : IOrderedEnumerable<TSource>
{
private readonly IEnumerable<TSource> Source;
private readonly Comparison<TSource> Comparer;
public OrderedEnumerableImpl(IEnumerable<TSource> source, Comparison<TSource> comparer)
{
Source = source;
Comparer = comparer;
}
public IOrderedEnumerable<TSource> CreateOrderedEnumerable<TKey>(Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending)
{
if (comparer == null)
{
comparer = Comparer<TKey>.Default;
}
Comparison<TSource> comparer2;
if (descending)
{
comparer2 = (x, y) =>
{
int result = Comparer(x, y);
if (result == 0)
{
result = comparer.Compare(keySelector(y), keySelector(x));
}
return result;
};
}
else
{
comparer2 = (x, y) =>
{
int result = Comparer(x, y);
if (result == 0)
{
result = comparer.Compare(keySelector(x), keySelector(y));
}
return result;
};
}
return new OrderedEnumerableImpl<TSource>(Source, comparer2);
}
public IEnumerator<TSource> GetEnumerator()
{
var source = Source.ToArray();
// ** Here you do the sorting! **
Array.Sort(source, Comparer);
for (int i = 0; i < source.Length; i++)
{
yield return source[i];
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
and Use like as follow
public class Employee
{
public int Number { get; set; }
public int GrossPay { get; set; }
public int Hours { get; set; }
public int PayRate { get; set; }
public string Name { get; set; }
}
void Main()
{
var Employee = new Employee[] { new Employee { Name = "acb", Number = 123 }, new Employee { Name = "nmo", Number = 456 }, new Employee { Name = "xyz", Number = 789 } };
var sortedEmpByNum = Employee.OrderBy(x => x.Number);// by number asc
var sortedEmpByNubDesc = Employee.OrderByDescending(x => x.Number); //by number desc
var sortedEmpByName = Employee.OrderBy(x => x.Name); //by name asc
//you shoud use this one
var finalResult = Employee.OrderBy(x => x.Name)
.ThenBy(x => x.Number)
.ThenByDescending(x => x.PayRate)
.ThenByDescending(x => x.Hours)
.ThenByDescending(x => x.GrossPay);
}
credit=> custom-lambda-sort-extension

linq Groupby a list by "sublist"

I have a List<Meb> (a bar nesting), each of these nestings have a list of details inside.
All of these bars are unique, because each of element inside is unique by its ID.
Now I want to add a checkbox, in order to group or not all bars that have the same list of details inside (the list of items inside are identical, except their ID, and some parameters I first set to -1 or ""). Here is the function I made in order to do that :
private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
List<Meb> retour = new List<Meb>();
foreach(Meb mebOri in mebInput)
{
Meb meb = new Meb();
meb.ID = -1;
meb.Number = mebOri.Number;
meb.Length = mebOri.Length;
meb.Quantity=mebOri.Quantity;
foreach(Repere repOri in mebOri.ListReperes)
{
Repere rep = new Repere();
rep.Name = repOri.Name;
rep.Quantite = repOri.Quantite;
rep.ID = -1;
meb.ListReperes.Add(rep);
}
retour.Add(meb);
}
retour = retour.GroupBy(l => l.ListReperes)
.Select(cl => new Meb
{
ID=-1,
Number = cl.First().Number,
Length = cl.First().Length,
Quantity=cl.Sum(c => c.Quantity),
ListReperes = cl.First().ListReperes,
}).ToList();
return retour;
}
The idea is that:
1st: I create a new List<Meb> that copies the original List<Meb>, for the List<Repere>, I also copy it, but setting the ID to "-1", as others properties that could differ between them.
2nd: I make a group by on the List<Repere>
But on the end no groupby is done, and the output remains the same as the input.
Edit :
I explain better the structure of my objects because it seems it was not clear enough :
Each Meb object represents a beam, each beams contains Repere objects(details), these details have a lot of parameters, most importants are ID, Name, Quantity, concrete example :
ID Name Quantity
Meb1(Quantity1) contains : 11 Repere1 2
20 Repere2 1
25 Repere3 1
Meb2(Quantity2) contains : 12 Repere1 2
24 Repere2 2
28 Repere3 1
Meb3(Quantity3) contains : 31 Repere1 2
18 Repere2 1
55 Repere3 1
So I import my List<Meb>, and I want to group all my Mebs, comparing their details list.
In that case the result would be :
Meb1(Quantity4) contains : 0 Repere1 2
0 Repere2 1
0 Repere3 1
Meb2(Quantity2) contains : 0 Repere1 2
0 Repere2 2
0 Repere3 1
I would recommend that you add some sort of property in your Meb class that hashes all of your ListReperes items, and then group off that.
You can have a look at this link: How to generate a unique hash for a collection of objects independent of their order
IE then you would do:
retour = retour.GroupBy(l => l.HashReperes) and this would provide you a unique grouped list of your lists.
where HashReperes is the property that provides the Hash of the Reperes List.
Use IEquatable. Then you can use the standard linq GroupBy(). See code below
public class Meb : IEquatable<Meb>, INotifyPropertyChanged
{
public int ID { get; set; }
public int Number { get; set; }
public int Length { get; set; }
public int Quantity { get; set;}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
return mebInput.GroupBy(x => x).Select(x => new Meb() {
ID = x.First().ID,
Number = x.First().Number,
Length = x.First().Length,
Quantity = x.Sum(y => y.Quantity)
}).ToList();
}
public bool Equals(Meb other)
{
if ((this.Number == other.Number) && (this.Length == other.Length) && (this.Quantity == other.Quantity))
{
return true;
}
else
{
return false;
}
}
public override int GetHashCode()
{
return ID;
}
}
If you don't want to use IEquatable then use this
private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
return mebInput.GroupBy(x => new { number = x.Number, len = x.Length }).Select(x => new Meb()
{
ID = x.First().ID,
Number = x.Key.number,
Length = x.Key.len,
Quantity = x.Sum(y => y.Quantity)
}).ToList();
}
For comparing a List use something like this
public class MyClassA : IEquatable<List<MyClassB>>
{
public List<MyClassB> myClassB { get; set; }
public bool Equals(List<MyClassB> other)
{
if(other == null) return false;
if (this.myClassB.Count() != other.Count()) return false;
var groupThis = this.myClassB.OrderBy(x => x.propertyA).ThenBy(x => x.propertyB).GroupBy(x => x).ToList();
var groupOther = other.OrderBy(x => x.propertyA).ThenBy(x => x.propertyB).GroupBy(x => x).ToList();
if (groupThis.Count() != groupOther.Count) return false;
for (int i = 0; i < groupThis.Count(); i++)
{
if (groupThis[i].Count() != groupOther[i].Count()) return false;
}
return true;
}
public override int GetHashCode()
{
return 0;
}
}
public class MyClassB : IEquatable<MyClassB>
{
public int propertyA { get; set; }
public string propertyB { get; set; }
public bool Equals(MyClassB other)
{
if (other == null) return false;
if ((this.propertyA == other.propertyA) && (this.propertyB == other.propertyB))
{
return true;
}
else
{
return false;
}
}
public override int GetHashCode()
{
return 0;
}
}
On the end, here is the way I could solve the problem :
private List<Meb> GroupIdenticalMeb(List<Meb> mebInput)
{
List<Meb> retour = new List<Meb>();
foreach(Meb mebOri in mebInput)
{
Meb meb = new Meb();
meb.ID = -1;
meb.Number = mebOri.Number;
meb.Length = mebOri.Length;
meb.Quantity=mebOri.Quantity;
foreach(Repere repOri in mebOri.ListReperes)
{
Repere rep = new Repere();
rep.Name = repOri.Name;
rep.Quantite = repOri.Quantite;
rep.ID = -1;
meb.ListReperes.Add(rep);
}
retour.Add(meb);
// Here I added a string property, in which I concatenate
//name and quantity of each Repere in my List<Repere>,
//so on the end the "SomeString" parameters will be identical
//for all Meb that have the same List<Repere> (ignoring the IDs).
foreach(Meb meb in retour)
{
meb.SomeString = "";
foreach(RepereNest rep in meb.ListReperes)
{
meb.SomeString += rep.Name + rep.Quantite;
}
}
}
retour = retour.GroupBy(l => l.SomeString)
.Select(cl => new Meb
{
ID=-1,
Number = cl.First().Number,
SomeString=cl.First().SomeString,
Length = cl.First().Length,
Quantity=cl.Sum(c => c.Quantity),
ListReperes = cl.First().ListReperes,
}).ToList();
return retour;
}
Well for now ths is the only way I could find to group not on my parameters(for this no problem), but on parameters inside a List of my object. And I think this method is not so bad, because I also have Lists inside of Repere objects, so I could use the same tip in future. On the end I just don't understand why it is not possible to check when Lists of my objects are equals?

Linq Recursive Sum

I have the following data strucure:
List<Item> Items = new List<Item>
{
new Item{ Id = 1, Name = "Machine" },
new Item{ Id = 3, Id_Parent = 1, Name = "Machine1"},
new Item{ Id = 5, Id_Parent = 3, Name = "Machine1-A", Number = 2, Price = 10 },
new Item{ Id = 9, Id_Parent = 3, Name = "Machine1-B", Number = 4, Price = 11 },
new Item{ Id = 100, Name = "Item" } ,
new Item{ Id = 112, Id_Parent = 100, Name = "Item1", Number = 5, Price = 55 }
};
I want to build a query that gets the sum of all children price in its parent (items are related by Id_Parent).
For example, for Item Id = 100, I have 55, because thats the value of the its child .
For Item Id = 3 I have 21, becaue Item Id = 5 and Id = 9 all sum to that.
So far soo good.
What I am strugling to get is for Item Id = 1 I should also have the sum = 21, because Id = 3 is a child of Id = 1 and it has a sum of 21.
Here is my code:
var result = from i in items
join item in item on i.Id_Parent equals item.Id
select new
{
Name = prod.Nome,
Sum =
(from it in items
where it.Id_Parent == item.Id
group it by new
{
it.Id_Parent
}
into g
select new
{
Sum = g.Sum(x => x.Price)
}
).First()
};
Help appreciated.
Create a recursive function to find all the children of a parent:
public static IEnumerable<Item> ItemDescendents(IEnumerable<Item> src, int parent_id) {
foreach (var item in src.Where(i => i.Id_Parent == parent_id)) {
yield return item;
foreach (var itemd in ItemDescendents(src, item.Id))
yield return itemd;
}
}
Now you can get the price for any parent:
var price1 = ItemDescendants(Items, 1).Sum(i => i.Price);
Note if you know that the children of an item are always greater in id value than their parent, you don't need recursion:
var descendents = Items.OrderBy(i => i.Id).Aggregate(new List<Item>(), (ans, i) => {
if (i.Id_Parent == 1 || ans.Select(a => a.Id).Contains(i.Id_Parent))
ans.Add(i);
return ans;
});
For those that prefer to avoid recursion, you can use an explicit stack instead:
public static IEnumerable<Item> ItemDescendentsFlat(IEnumerable<Item> src, int parent_id) {
void PushRange<T>(Stack<T> s, IEnumerable<T> Ts) {
foreach (var aT in Ts)
s.Push(aT);
}
var itemStack = new Stack<Item>(src.Where(i => i.Id_Parent == parent_id));
while (itemStack.Count > 0) {
var item = itemStack.Pop();
PushRange(itemStack, src.Where(i => i.Id_Parent == item.Id));
yield return item;
}
}
I included PushRange helper function since Stack doesn't have one.
Finally, here is a variation that doesn't use any stack, implicit or explicit.
public IEnumerable<Item> ItemDescendantsFlat2(IEnumerable<Item> src, int parent_id) {
var children = src.Where(s => s.Id_Parent == parent_id);
do {
foreach (var c in children)
yield return c;
children = children.SelectMany(c => src.Where(i => i.Id_Parent == c.Id)).ToList();
} while (children.Count() > 0);
}
You can replace the multiple traversals of the source with a Lookup as well:
public IEnumerable<Item> ItemDescendantsFlat3(IEnumerable<Item> src, int parent_id) {
var childItems = src.ToLookup(i => i.Id_Parent);
var children = childItems[parent_id];
do {
foreach (var c in children)
yield return c;
children = children.SelectMany(c => childItems[c.Id]).ToList();
} while (children.Count() > 0);
}
I optimized the above based on the comments about too much nested enumeration, which improved performance vastly, but I was also inspired to attempt to remove SelectMany which can be slow, and collect IEnumerables as I've seen suggested elsewhere to optimize Concat:
public IEnumerable<Item> ItemDescendantsFlat4(IEnumerable<Item> src, int parent_id) {
var childItems = src.ToLookup(i => i.Id_Parent);
var stackOfChildren = new Stack<IEnumerable<Item>>();
stackOfChildren.Push(childItems[parent_id]);
do
foreach (var c in stackOfChildren.Pop()) {
yield return c;
stackOfChildren.Push(childItems[c.Id]);
}
while (stackOfChildren.Count > 0);
}
#AntonínLejsek's GetDescendants is still fastest, though it is very close now, but sometimes simpler wins out for performance.
The easy way would be to use a local function, like this:
int CalculatePrice(int id)
{
int price = Items.Where(item => item.Id_Parent == id).Sum(child => CalculatePrice(child.Id));
return price + Items.First(item => item.Id == id).Price;
}
int total = CalculatePrice(3); // 3 is just an example id
Another, cleaner solution instead would be to use the Y combinator to create a closure that can be called inline. Assuming you have this
/// <summary>
/// Implements a recursive function that takes a single parameter
/// </summary>
/// <typeparam name="T">The Type of the Func parameter</typeparam>
/// <typeparam name="TResult">The Type of the value returned by the recursive function</typeparam>
/// <param name="f">The function that returns the recursive Func to execute</param>
/// <returns>The recursive Func with the given code</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f)
{
Func<T, TResult> g = null;
g = f(a => g(a));
return g;
}
Then you can just get your result like so:
int total = Y<int, int>(x => y =>
{
int price = Items.Where(item => item.Id_Parent == y).Sum(child => x(child.Id));
return price + Items.First(item => item.Id == y).Price;
})(3);
What's nice about this is that it allows you to quickly declare and call a recursive function in a functional-fashion, which is especially handy in situations like this one, where you only need "throwaway" functions that you'll use just once. Also, since this function is quite small, using the Y combinator further reduces the boilerplate of having to declare a local function and call it on another line.
There are so many solutions that it is worth to make a benchmark. I added my solution to the mix too, it is the last function. Some functions include the root node and some not, but apart from this they return the same result. I tested wide tree with 2 children per parent and narrow tree with just one children per parent (depth is equal to number of items). And the results are:
---------- Wide 100000 3 ----------
ItemDescendents: 9592ms
ItemDescendentsFlat: 9544ms
ItemDescendentsFlat2: 45826ms
ItemDescendentsFlat3: 30ms
ItemDescendentsFlat4: 11ms
CalculatePrice: 23849ms
Y: 24265ms
GetSum: 62ms
GetDescendants: 19ms
---------- Narrow 3000 3 ----------
ItemDescendents: 100ms
ItemDescendentsFlat: 24ms
ItemDescendentsFlat2: 75948ms
ItemDescendentsFlat3: 1004ms
ItemDescendentsFlat4: 1ms
CalculatePrice: 69ms
Y: 69ms
GetSum: 915ms
GetDescendants: 0ms
While premature optimalization is bad, it is important to know what the asymptotic behaviour is. Asymptotic behaviour determines if the algorithm would scale or would die.
And the code follows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
public class Test
{
public static IEnumerable<Item> ItemDescendents(IEnumerable<Item> src, int parent_id)
{
foreach (var item in src.Where(i => i.Id_Parent == parent_id))
{
yield return item;
foreach (var itemd in ItemDescendents(src, item.Id))
yield return itemd;
}
}
public static IEnumerable<Item> ItemDescendentsFlat(IEnumerable<Item> src, int parent_id)
{
void PushRange<T>(Stack<T> s, IEnumerable<T> Ts)
{
foreach (var aT in Ts)
s.Push(aT);
}
var itemStack = new Stack<Item>(src.Where(i => i.Id_Parent == parent_id));
while (itemStack.Count > 0)
{
var item = itemStack.Pop();
PushRange(itemStack, src.Where(i => i.Id_Parent == item.Id));
yield return item;
};
}
public IEnumerable<Item> ItemDescendantsFlat2(IEnumerable<Item> src, int parent_id)
{
var children = src.Where(s => s.Id_Parent == parent_id);
do
{
foreach (var c in children)
yield return c;
children = children.SelectMany(c => src.Where(i => i.Id_Parent == c.Id));
} while (children.Count() > 0);
}
public IEnumerable<Item> ItemDescendantsFlat3(IEnumerable<Item> src, int parent_id)
{
var childItems = src.ToLookup(i => i.Id_Parent);
var children = childItems[parent_id];
do
{
foreach (var c in children)
yield return c;
children = children.SelectMany(c => childItems[c.Id]);
} while (children.Count() > 0);
}
public IEnumerable<Item> ItemDescendantsFlat4(IEnumerable<Item> src, int parent_id)
{
var childItems = src.ToLookup(i => i.Id_Parent);
var stackOfChildren = new Stack<IEnumerable<Item>>();
stackOfChildren.Push(childItems[parent_id]);
do
foreach (var c in stackOfChildren.Pop())
{
yield return c;
stackOfChildren.Push(childItems[c.Id]);
}
while (stackOfChildren.Count > 0);
}
public static int GetSum(IEnumerable<Item> items, int id)
{
// add all matching items
var itemsToSum = items.Where(i => i.Id == id).ToList();
var oldCount = 0;
var currentCount = itemsToSum.Count();
// it nothing was added we skip the while
while (currentCount != oldCount)
{
oldCount = currentCount;
// find all matching items except the ones already in the list
var matchedItems = items
.Join(itemsToSum, item => item.Id_Parent, sum => sum.Id, (item, sum) => item)
.Except(itemsToSum)
.ToList();
itemsToSum.AddRange(matchedItems);
currentCount = itemsToSum.Count;
}
return itemsToSum.Sum(i => i.Price);
}
/// <summary>
/// Implements a recursive function that takes a single parameter
/// </summary>
/// <typeparam name="T">The Type of the Func parameter</typeparam>
/// <typeparam name="TResult">The Type of the value returned by the recursive function</typeparam>
/// <param name="f">The function that returns the recursive Func to execute</param>
/// <returns>The recursive Func with the given code</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Func<T, TResult> Y<T, TResult>(Func<Func<T, TResult>, Func<T, TResult>> f)
{
Func<T, TResult> g = null;
g = f(a => g(a));
return g;
}
public IEnumerable<Item> GetDescendants(IEnumerable<Item> items, int key)
{
var lookup = items.ToLookup(i => i.Id_Parent);
Stack<Item> st = new Stack<Item>(lookup[key]);
while (st.Count > 0)
{
var item = st.Pop();
yield return item;
foreach (var i in lookup[item.Id])
{
st.Push(i);
}
}
}
public class Item
{
public int Id;
public int Price;
public int Id_Parent;
}
protected Item[] getItems(int count, bool wide)
{
Item[] Items = new Item[count];
for (int i = 0; i < count; ++i)
{
Item ix = new Item()
{
Id = i,
Id_Parent = wide ? i / 2 : i - 1,
Price = i % 17
};
Items[i] = ix;
}
return Items;
}
public void test()
{
Item[] items = null;
int CalculatePrice(int id)
{
int price = items.Where(item => item.Id_Parent == id).Sum(child => CalculatePrice(child.Id));
return price + items.First(item => item.Id == id).Price;
}
var functions = new List<(Func<Item[], int, int>, string)>() {
((it, key) => ItemDescendents(it, key).Sum(i => i.Price), "ItemDescendents"),
((it, key) => ItemDescendentsFlat(it, key).Sum(i => i.Price), "ItemDescendentsFlat"),
((it, key) => ItemDescendantsFlat2(it, key).Sum(i => i.Price), "ItemDescendentsFlat2"),
((it, key) => ItemDescendantsFlat3(it, key).Sum(i => i.Price), "ItemDescendentsFlat3"),
((it, key) => ItemDescendantsFlat4(it, key).Sum(i => i.Price), "ItemDescendentsFlat4"),
((it, key) => CalculatePrice(key), "CalculatePrice"),
((it, key) => Y<int, int>(x => y =>
{
int price = it.Where(item => item.Id_Parent == y).Sum(child => x(child.Id));
return price + it.First(item => item.Id == y).Price;
})(key), "Y"),
((it, key) => GetSum(it, key), "GetSum"),
((it, key) => GetDescendants(it, key).Sum(i => i.Price), "GetDescendants" )
};
System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
var testSetup = new[]
{
new { Count = 10, Wide = true, Key=3}, //warmup
new { Count = 100000, Wide = true, Key=3},
new { Count = 3000, Wide = false, Key=3}
};
List<int> sums = new List<int>();
foreach (var setup in testSetup)
{
items = getItems(setup.Count, setup.Wide);
Console.WriteLine("---------- " + (setup.Wide ? "Wide" : "Narrow")
+ " " + setup.Count + " " + setup.Key + " ----------");
foreach (var func in functions)
{
st.Restart();
sums.Add(func.Item1(items, setup.Key));
st.Stop();
Console.WriteLine(func.Item2 + ": " + st.ElapsedMilliseconds + "ms");
}
Console.WriteLine();
Console.WriteLine("checks: " + string.Join(", ", sums));
sums.Clear();
}
Console.WriteLine("---------- END ----------");
}
}
static void Main(string[] args)
{
Test t = new Test();
t.test();
}
}
}
For future readers who may experience a StackOverflowException, the alternative I use is in the following example: (dotnetfiddle example)
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var items = new List<Item>
{
new Item{ Id = 1, Name = "Machine" },
new Item{ Id = 3, Id_Parent = 1, Name = "Machine1"},
new Item{ Id = 5, Id_Parent = 3, Name = "Machine1-A", Number = 2, Price = 10 },
new Item{ Id = 9, Id_Parent = 3, Name = "Machine1-B", Number = 4, Price = 11 },
new Item{ Id = 100, Name = "Item" } ,
new Item{ Id = 112, Id_Parent = 100, Name = "Item1", Number = 5, Price = 55 }
};
foreach(var item in items)
{
Console.WriteLine("{0} {1} $" + GetSum(items, item.Id).ToString(), item.Name, item.Id);
}
}
public static int GetSum(IEnumerable<Item> items, int id)
{
// add all matching items
var itemsToSum = items.Where(i => i.Id == id).ToList();
var oldCount = 0;
var currentCount = itemsToSum.Count();
// it nothing was added we skip the while
while (currentCount != oldCount)
{
oldCount = currentCount;
// find all matching items except the ones already in the list
var matchedItems = items
.Join(itemsToSum, item => item.Id_Parent, sum => sum.Id, (item, sum) => item)
.Except(itemsToSum)
.ToList();
itemsToSum.AddRange(matchedItems);
currentCount = itemsToSum.Count;
}
return itemsToSum.Sum(i => i.Price);
}
public class Item
{
public int Id { get; set; }
public int Id_Parent { get; set; }
public int Number { get; set; }
public int Price { get; set; }
public string Name { get; set; }
}
}
Result:
Machine 1 $21
Machine1 3 $21
Machine1-A 5 $10
Machine1-B 9 $11
Item 100 $55
Item1 112 $55
Basically we create a list with the initial items matching the id passed. If the id doesn't match we have no items and we skip the while loop. If we do have items, then we join to find all items that have a parent id of the items we currently have. From that list we then exclude the ones already in the list. Then append what we've found. Eventually there are no more items in the list that have matching parent id's.

Tarjan cycle detection help C#

Here is a working C# implementation of tarjan's cycle detection.
The algorithm is found here:
http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
public class TarjanCycleDetect
{
private static List<List<Vertex>> StronglyConnectedComponents;
private static Stack<Vertex> S;
private static int index;
private static DepGraph dg;
public static List<List<Vertex>> DetectCycle(DepGraph g)
{
StronglyConnectedComponents = new List<List<Vertex>>();
index = 0;
S = new Stack<Vertex>();
dg = g;
foreach (Vertex v in g.vertices)
{
if (v.index < 0)
{
strongconnect(v);
}
}
return StronglyConnectedComponents;
}
private static void strongconnect(Vertex v)
{
v.index = index;
v.lowlink = index;
index++;
S.Push(v);
foreach (Vertex w in v.dependencies)
{
if (w.index < 0)
{
strongconnect(w);
v.lowlink = Math.Min(v.lowlink, w.lowlink);
}
else if (S.Contains(w))
{
v.lowlink = Math.Min(v.lowlink, w.index);
}
}
if (v.lowlink == v.index)
{
List<Vertex> scc = new List<Vertex>();
Vertex w;
do
{
w = S.Pop();
scc.Add(w);
} while (v != w);
StronglyConnectedComponents.Add(scc);
}
}
Note a DepGraph is just a list of Vertex. and Vertex has a list of other Vertex which represent the edges. Also index and lowlink are initialized to -1
EDIT: This is working...I just misinterpreted the results.
The above is actually correct, I did not understand what a strongly connected component was. I was expecting the function to return an empty List of strongly connected components, yet it was returning a list of single nodes.
I believe the above is working. Feel free to use if you need it!
As of 2008 quickgraph has supported this algorithm. See the StronglyConnectedComponentsAlgorithm class for the implementation, or AlgorithmExtensions.StronglyConnectedComponents method for a usage shortcut.
Example:
// Initialize result dictionary
IDictionary<string, int> comps = new Dictionary<string, int>();
// Run the algorithm
graph.StronglyConnectedComponents(out comps);
// Group and filter the dictionary
var cycles = comps
.GroupBy(x => x.Value, x => x.Key)
.Where(x => x.Count() > 1)
.Select(x => x.ToList())
Example presented above in question isn't functional should anyone want to quickly play with it. Also note that it is stack based, which will detonate your stack if you give anything but the most trivial of graphs. Here is a working example with a unit test that models the graph presented on the Tarjan wikipedia page:
public class Vertex
{
public int Id { get;set; }
public int Index { get; set; }
public int Lowlink { get; set; }
public HashSet<Vertex> Dependencies { get; set; }
public Vertex()
{
Id = -1;
Index = -1;
Lowlink = -1;
Dependencies = new HashSet<Vertex>();
}
public override string ToString()
{
return string.Format("Vertex Id {0}", Id);
}
public override int GetHashCode()
{
return Id;
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
Vertex other = obj as Vertex;
if (other == null)
return false;
return Id == other.Id;
}
}
public class TarjanCycleDetectStack
{
protected List<List<Vertex>> _StronglyConnectedComponents;
protected Stack<Vertex> _Stack;
protected int _Index;
public List<List<Vertex>> DetectCycle(List<Vertex> graph_nodes)
{
_StronglyConnectedComponents = new List<List<Vertex>>();
_Index = 0;
_Stack = new Stack<Vertex>();
foreach (Vertex v in graph_nodes)
{
if (v.Index < 0)
{
StronglyConnect(v);
}
}
return _StronglyConnectedComponents;
}
private void StronglyConnect(Vertex v)
{
v.Index = _Index;
v.Lowlink = _Index;
_Index++;
_Stack.Push(v);
foreach (Vertex w in v.Dependencies)
{
if (w.Index < 0)
{
StronglyConnect(w);
v.Lowlink = Math.Min(v.Lowlink, w.Lowlink);
}
else if (_Stack.Contains(w))
{
v.Lowlink = Math.Min(v.Lowlink, w.Index);
}
}
if (v.Lowlink == v.Index)
{
List<Vertex> cycle = new List<Vertex>();
Vertex w;
do
{
w = _Stack.Pop();
cycle.Add(w);
} while (v != w);
_StronglyConnectedComponents.Add(cycle);
}
}
}
[TestMethod()]
public void TarjanStackTest()
{
// tests simple model presented on https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
var graph_nodes = new List<Vertex>();
var v1 = new Vertex() { Id = 1 };
var v2 = new Vertex() { Id = 2 };
var v3 = new Vertex() { Id = 3 };
var v4 = new Vertex() { Id = 4 };
var v5 = new Vertex() { Id = 5 };
var v6 = new Vertex() { Id = 6 };
var v7 = new Vertex() { Id = 7 };
var v8 = new Vertex() { Id = 8 };
v1.Dependencies.Add(v2);
v2.Dependencies.Add(v3);
v3.Dependencies.Add(v1);
v4.Dependencies.Add(v3);
v4.Dependencies.Add(v5);
v5.Dependencies.Add(v4);
v5.Dependencies.Add(v6);
v6.Dependencies.Add(v3);
v6.Dependencies.Add(v7);
v7.Dependencies.Add(v6);
v8.Dependencies.Add(v7);
v8.Dependencies.Add(v5);
v8.Dependencies.Add(v8);
graph_nodes.Add(v1);
graph_nodes.Add(v2);
graph_nodes.Add(v3);
graph_nodes.Add(v4);
graph_nodes.Add(v5);
graph_nodes.Add(v6);
graph_nodes.Add(v7);
graph_nodes.Add(v8);
var tcd = new TarjanCycleDetectStack();
var cycle_list = tcd.DetectCycle(graph_nodes);
Assert.IsTrue(cycle_list.Count == 4);
}
I added a Id property to the Vertex object so it is simple to see what is being done, it isn't strictly needed. I also cleaned up some of the code a little, author was using naming from page pseudo-code, which is good for comparison, but it wasn't very informative.

Categories

Resources