What does a GroupBy KeySelector function look like? - c#

I would like to break the KeySelector function of GroupBy into its own method. The KeySelector code isn't right. One major problem is Widget.foo isn't matching on "Green" but I think it should be.
Widget.cs
public class Widget
{
public string foo { get; set; }
public double bar { get; set; }
public bool fee { get; set; }
}
Program.cs
static void Main(string[] args)
{
var widgets = new List<Widget>()
{
new Widget() { foo = "red" , bar = 1.0, fee = true },
new Widget() { foo = "green", bar = 2.0, fee = true },
new Widget() { foo = "green", bar = 2.0, fee = false },
new Widget() { foo = "green", bar = 3.0, fee = false },
new Widget() { foo = "blue" , bar = 4.0, fee = true }
};
var gb = widgets.GroupBy(
w => GenerateGroupByKey(),
w => w,
(prop, groupedWidgets) => new
{
GroupedWidgets = groupedWidgets
}
).ToList();
}
KeySelector
static Func<Widget, object> GenerateGroupByKey()
{
Func<Widget, object> s = delegate(Widget widget)
{
return new { widget.foo };
};
return s;
}

You must pass delegate to parameters, so just call the function and return the delegate.
var gb = widgets.GroupBy(
GenerateGroupByKey(),
w => w,
(prop, groupedWidgets) => new
{
GroupedWidgets = groupedWidgets
}
).ToList();

Group by you using have parameters like below
Func<TSource, TKey> keySelector,
Func<TSource, TElement> elementSelector,
Func<TKey, IEnumerable<TElement>, TResult> resultSelector
when you are writing w => GenerateGroupByKey()
you are creating a new FUNC with input parameter as GenerateGroupByKey()
But GenerateGroupByKey() itself is a FUNC
static Func<Widget, object> GenerateGroupByKey()
{
Func<Widget, object> s = delegate(Widget widget)
{
return new { widget.foo };
};
return s;
}
And you are not invoking that FUNC
Solution:
Do not create another FUNC. Pass GenerateGroupByKey() directly :)
static void Main(string[] args)
{
var widgets = new List<Widget>()
{
new Widget() { foo = "red" , bar = 1.0, fee = true },
new Widget() { foo = "green", bar = 2.0, fee = true },
new Widget() { foo = "green", bar = 2.0, fee = false },
new Widget() { foo = "green", bar = 3.0, fee = false },
new Widget() { foo = "blue" , bar = 4.0, fee = true }
};
var gb = widgets.GroupBy(
GenerateGroupByKey(), // Your FUNC method
w => w,
(prop, groupedWidgets) => new
{
GroupedWidgets = groupedWidgets
}
).ToList();
}

Your grouping does not work because inside GenerateGroupByKey() method you create new object.
Objects are equals if they have equal memory references. You can review more details about Equals here.
For what you create new object like new { widget.foo }?
You can just return string instead of object.
Try to use delegate like it:
static Func<Widget, object> GenerateGroupByKey()
{
Func<Widget, object> s = delegate(Widget widget)
{
return widget.foo;
};
return s;
}

Related

Sort items by field name

I have many fields which I would like to distinct generically.
Right now, I have to write this for each field:
if (cFieldName == "cEVEN_DAL")
{
eventLOGS_DistinctField = eventLogs.DistinctBy(x => x.cEVEN_DAL);
}
What I would like to do is something like:
eventLOGS_DistinctField = eventLogs.DistinctBy(myFieldName);
Is it possible to do this?
You can generate an Expression x => x.cEVEN_DAL with Linq API:
// because you said in comments that all fields are string I'm using Func<T, string> here
public static Expression<Func<T, string>> Create<T>(string fieldName)
{
var parameter = Expression.Parameter(typeof(T), "p");
var property = Expression.PropertyOrField(parameter, fieldName);
return Expression.Lambda<Func<T, string>>(property, parameter);
}
If you are usinq MoreLinq you need to compile this expression:
var lambda = Create< TypeOfEventLogItem >("cEVEN_DAL");
var func = lambda.Compile();
var result = eventLogs.DistinctBy(func);
This way:
class Program
{
static void Main(string[] args)
{
List<Test> tests = new List<Test>() //Example objects
{
new Test
{
A = 1,
B = 2,
C = 3,
},
new Test
{
A = 2,
B = 2,
C = 3,
},
new Test
{
A = 3,
B = 2,
C = 3,
},
new Test
{
A = 1,
B = 1,
C = 3,
},
new Test
{
A = 1,
B = 2,
C = 3,
},
new Test
{
A = 1,
B = 3,
C = 3,
},
new Test
{
A = 1,
B = 2,
C = 1,
},
new Test
{
A = 1,
B = 2,
C = 2,
},
new Test
{
A = 1,
B = 2,
C = 3,
}
};
List<Test> results = DistinctBy(tests, "A"); //Use of DistinctBy
}
private static List<T> DistinctBy<T>(List<T> objs, string propertyName)
{
Type type = typeof(T);
PropertyInfo property = type.GetProperty(propertyName);
return objs.GroupBy(x => property.GetValue(x)).Select(x => x.First()).ToList();
}
}
public class Test
{
public int A { get; set; }
public int B { get; set; }
public int C { get; set; }
}

How can I do this with a LINQ query?

What I'm trying to do is take an IEnumerable of an object that has 2 fields and find how many of one of the fields is associated with only 1 of the first.
In other words, the setup is like
using System;
using System.Linq;
using System.Collections.Generic;
public class Baz
{
public string Foo { get; set; }
public int Bar { get; set; }
}
public class Program
{
public void Main()
{
List<Baz> bazzers = new List<Baz>() {
new Baz { Foo = "A", Bar = 1 },
new Baz { Foo = "A", Bar = 3 },
new Baz { Foo = "B", Bar = 1 },
new Baz { Foo = "B", Bar = 1 },
new Baz { Foo = "C", Bar = 2 },
new Baz { Foo = "C", Bar = 2 },
new Baz { Foo = "D", Bar = 1 },
new Baz { Foo = "D", Bar = 1 }
};
// WANTED: An IEnumerable<Baz> that is like
// { Bar = 1, LoyalFoos = 2 }, (because both "B" and "D" are paired only with 1)
// { Bar = 2, LoyalFoos = 1 }, (because "C" is paired only with 2)
// { Bar = 3, LoyalFoos = 0 } (because there is no Foo that is paired only with the Bar 3)
}
}
Is there a nice way to do this with LINQ?
I'm not exactly sure what you want for output. Maybe something like this if you're looking for the count of loyal foos for each bar:
var result = bazzers
.Select(bazzer => bazzer.Bar)
.Distinct()
.Select(bar => new
{
Bar = bar,
LoyalFoos = bazzers
.GroupBy(bazzer => bazzer.Foo)
.Count(group => group.All(bazzer => bazzer.Bar == bar))
});
Or if you want a grouping of each loyal foo given bar:
var result = bazzers
.Select(bazzer => bazzer.Bar)
.Distinct()
.Select(bar => new
{
Bar = bar,
LoyalFoos = bazzers
.GroupBy(bazzer => bazzer.Foo)
.Where(group => group.All(bazzer => bazzer.Bar == bar))
.SelectMany(group => group)
});

Linq - group by using the elements inside an array property

I have a number of objects and each object has an array, I would like to group these objects by the values inside the array, so conceptually they look as follows:
var objects = new []{
object1 = new object{
elements = []{1,2,3}
},
object2 = new object{
elements = []{1,2}
},
object3 = new object{
elements = []{1,2}
},
object4 = new object{
elements = null
}
}
after grouping:
group1: object1
group2: object2,object3
group3: object4
somethings that I have tried:
actual classes:
public class RuleCms
{
public IList<int> ParkingEntitlementTypeIds { get; set; }
}
var rules = new List<RuleCms>()
{
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1,2}
},
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1,2}
},
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1}
},
new RuleCms()
{
ParkingEntitlementTypeIds = null
}
};
var firstTry = rules.GroupBy(g => new { entitlementIds = g.ParkingEntitlementTypeIds, rules = g })
.Where(x => x.Key.entitlementIds !=null && x.Key.entitlementIds.Equals(x.Key.rules.ParkingEntitlementTypeIds));
var secondTry =
rules.GroupBy(g => new { entitlementIds = g.ParkingEntitlementTypeIds ?? new List<int>(), rules = g })
.GroupBy(x => !x.Key.entitlementIds.Except(x.Key.rules.ParkingEntitlementTypeIds ?? new List<int>()).Any());
You can use IEqualityComparer class. Here is the code:
class MyClass
{
public string Name { get; set; }
public int[] Array { get; set; }
}
class ArrayComparer : IEqualityComparer<int[]>
{
public bool Equals(int[] x, int[] y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(int[] obj)
{
return string.Join(",", obj).GetHashCode();
}
}
Then
var temp = new MyClass[]
{
new MyClass { Name = "object1", Array = new int[] { 1, 2, 3 } },
new MyClass { Name = "object2", Array = new int[] { 1, 2 } },
new MyClass { Name = "object3", Array = new int[] { 1, 2 } },
new MyClass { Name = "object4", Array =null }
};
var result = temp.GroupBy(i => i.Array, new ArrayComparer()).ToList();
//Now you have 3 groups
For simple data that really is as simple as your example you could do this:
.GroupBy(x => string.Join("|", x.IDS))
.Select(x => new
{
IDS = x.Key.Split('|').Where(s => s != string.Empty).ToArray(),
Count = x.Count()
});

Cast<> for hierarchical data structure

Here is an example
class A
{
public string Text = "";
public IEnumerable<A> Children = new List<A>();
}
class B
{
public string Text;
public IEnumerable<B> Children = new List<B>();
}
static void Main(string[] args)
{
// test
var a = new A[]
{
new A() { Text = "1", Children = new A[]
{
new A() { Text = "11"},
new A() {Text = "12"},
}},
new A() { Text = "2", Children = new A[]
{
new A() {Text = "21", Children = new A[]
{
new A() {Text = "211"},
new A() {Text = "212"},
}},
new A() {Text = "22"},
}},
};
B[] b = a ...; // convert a to b type
}
My original problem is hierarchical data A in Model, which has to be displayed in the View, thus I have to create B type in ViewModel with INotifyPropertyChanged, etc. Converting from A to B for hierarchical data seems more complicated than a simple linq Cast<>.
If somebody is going to try to code, then here is the nice flattering function, can be used like this:
foreach (var item in b.Flatten(o => o.Children).OrderBy(o => o.Text))
Console.WriteLine(item.Text);
Use a recursive function that can translate an A into a B.
B BFromA(A a) {
return new B { Text = a.Text, Children = a.Children.Select(BFromA).ToList() };
}
Usage:
B[] b = a.Select(BFromA).ToArray(); // convert a to b type

Transform a collection

Have a collection of objects. Schematically:
[
{ A = 1, B = 1 }
{ A = 1, B = 2 }
{ A = 2, B = 3 }
{ A = 2, B = 4 }
{ A = 1, B = 5 }
{ A = 3, B = 6 }
]
Need:
[
{ A = 1, Bs = [ 1, 2 ] }
{ A = 2, Bs = [ 3, 4 ] }
{ A = 1, Bs = [ 5 ] }
{ A = 3, Bs = [ 6 ] }
]
Is it possible to LINQ such?
Note: Ordering is important. So Bs = [5] can't be merged with Bs = [1, 2]
Given these simplistic classes:
class C {
public int A;
public int B;
}
class R {
public int A;
public List<int> Bs = new List<int>();
}
You can do it like this:
var cs = new C[] {
new C() { A = 1, B = 1 },
new C() { A = 1, B = 2 },
new C() { A = 2, B = 3 },
new C() { A = 2, B = 4 },
new C() { A = 1, B = 5 },
new C() { A = 3, B = 6 }
};
var rs = cs.
OrderBy(o => o.B).
ThenBy(o => o.A).
Aggregate(new List<R>(), (l, o) => {
if (l.Count > 0 && l.Last().A == o.A) {
l.Last().Bs.Add(o.B);
}
else {
l.Add(new R { A = o.A, Bs = { o.B } });
}
return l;
});
Note: In the above I assume that the Bs and then the As have to be sorted. If that's not the case, it's a simple matter of removing the sorting instructions:
var rs = cs.
Aggregate(new List<R>(), (l, o) => {
if (l.Count > 0 && l.Last().A == o.A) {
l.Last().Bs.Add(o.B);
}
else {
l.Add(new R { A = o.A, Bs = { o.B } });
}
return l;
});
So basically you want to group together what has the same A-value and is consecutive.
You need to tranform the list of objects to an anonymous type which contains the previous/next element. I've used two Selects to make it more redable. Then you need to check if the two elements are consecutive(adjacent indices).
Now you have all you need to GroupBy, the value and the bool.
Your objects:
var list = new System.Collections.Generic.List<Foo>(){
new Foo(){ A = 1, B = 1 },
new Foo(){ A = 1, B = 2 },
new Foo(){ A = 2, B = 3 },
new Foo(){ A = 2, B = 4 },
new Foo(){ A = 1, B = 5 },
new Foo(){ A = 3, B = 6 }
};
The query:
var groups = list
.Select((f, i) => new
{
Obj = f,
Next = list.ElementAtOrDefault(i + 1),
Prev = list.ElementAtOrDefault(i - 1)
})
.Select(x => new
{
A = x.Obj.A,
x.Obj,
Consecutive = (x.Next != null && x.Next.A == x.Obj.A)
|| (x.Prev != null && x.Prev.A == x.Obj.A)
})
.GroupBy(x => new { x.Consecutive, x.A });
Output the result:
foreach (var abGroup in groups)
{
int aKey = abGroup.Key.A;
var bList = string.Join(",", abGroup.Select(x => x.Obj.B));
Console.WriteLine("A = {0}, Bs = [ {1} ] ", aKey, bList);
}
Here's the working demo: http://ideone.com/fXgQ3
You can use The GroupAdjacent Extension Method .
Then , you just need
var grps = objects.GroupAdjacent(p => new { p.A });
I think it is the easiest way to implement it .
EDIT:
Here is my test code.
class Program
{
static void Main(string[] args)
{
var ia = new Dummycls[] {
new Dummycls{ A = 1, B = 1 },
new Dummycls{ A = 1, B = 2 },
new Dummycls{ A = 2, B = 3 },
new Dummycls{ A = 2, B = 4 },
new Dummycls{ A = 1, B = 5 },
new Dummycls{ A = 3, B = 6 },
};
var groups = ia.GroupAdjacent(i => i.A);
foreach (var g in groups)
{
Console.WriteLine("Group {0}", g.Key);
foreach (var i in g)
Console.WriteLine(i.ToString());
Console.WriteLine();
}
Console.ReadKey();
}
}
class Dummycls
{
public int A { get; set; }
public int B { get; set; }
public override string ToString()
{
return string.Format("A={0};B={1}" , A , B);
}
}
The result is
Group 1
A=1;B=1
A=1;B=2
Group 2
A=2;B=3
A=2;B=4
Group 1
A=1;B=5
Group 3
A=3;B=6
This is the structure of a method that does what you want:
public static IEnumerable<IGrouping<TKey, TElement>> GroupWithKeyBreaks<T, TKey, TElement>(IEnumerable<T> enumerable,
Func<T, TKey> keySelector,
Func<T, TElement> itemSelector)
{
// Error handling goes here
TKey currentKey = default(TKey);
List<TElement> elements = new List<TElement>();
foreach (T element in enumerable)
{
TKey thisKey = keySelector(element);
if (thisKey == null)
{
continue;
}
if (!thisKey.Equals(currentKey) && elements.Count > 0)
{
yield return new SimpleGrouping<TKey, TElement>(currentKey, elements);
elements = new List<TElement>();
}
elements.Add(itemSelector(element));
currentKey = thisKey;
}
// Add the "last" item
if (elements.Count > 0)
{
yield return new SimpleGrouping<TKey, TElement>(currentKey, elements);
}
}
It uses the following helper class:
private class SimpleGrouping<T, U> : IGrouping<T, U>
{
private T key;
private IEnumerable<U> grouping;
T IGrouping<T, U>.Key
{
get { return key; }
}
IEnumerator<U> IEnumerable<U>.GetEnumerator()
{
return grouping.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return grouping.GetEnumerator();
}
public SimpleGrouping(T k, IEnumerable<U> g)
{
this.key = k;
this.grouping = g;
}
}
Here's a sample usage:
foreach (var grouping in data.GroupWithKeyBreaks(x => x.A, x => x.B))
{
Console.WriteLine("Key: " + grouping.Key);
foreach (var element in grouping)
{
Console.Write(element);
}
}
var groupCounter = 0;
int? prevA = null;
collection
.Select(item => {
var groupId = item.A == prevA ? groupCounter : ++groupCounter;
prevA = item.A;
return new { groupId, item.A, item.B };
})
.GroupBy(item => item.groupId)
.Select(grp => new { A = grp.First().A, Bs = grp.Select(g => g.B) });
If your collection is in o, then:
var trans = o.Aggregate
(
new {
List = new List<Tuple<int, List<int>>>(),
LastSeed = (int?)0
},
(acc, item) =>
{
if (acc.LastSeed == null || item.A != acc.LastSeed)
acc.List.Add(Tuple.Create(item.A, new List<int>()));
acc.List[acc.List.Count - 1].Item2.Add(item.B);
return new { List = acc.List, LastSeed = (int?)item.A};
},
acc => acc.List.Select(
z=>new {A = z.Item1,
B = z.Item2 as IEnumerable<int>
})
);
This produces an IEnumerable<int, IEnumerable<int>> of the required form.
var result = list.ToKeyValuePairs(x => x.A)
.Select(x => new { A = x.Key, Bs = x.Value.Select(y => y.B) });
foreach (var item in result)
{
Console.WriteLine("A = {0} Bs=[{1}]",item.A, String.Join(",",item.Bs));
}
-
public static class MyExtensions
{
public static IEnumerable<KeyValuePair<S,IEnumerable<T>>> ToKeyValuePairs<T,S>(
this IEnumerable<T> list,
Func<T,S> keySelector)
{
List<T> retList = new List<T>();
S prev = keySelector(list.FirstOrDefault());
foreach (T item in list)
{
if (keySelector(item).Equals(prev))
retList.Add(item);
else
{
yield return new KeyValuePair<S, IEnumerable<T>>(prev, retList);
prev = keySelector(item);
retList = new List<T>();
retList.Add(item);
}
}
if(retList.Count>0)
yield return new KeyValuePair<S, IEnumerable<T>>(prev, retList);
}
}
OUTPUT:
A = 1 Bs=[1,2]
A = 2 Bs=[3,4]
A = 1 Bs=[5]
A = 3 Bs=[6]

Categories