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; }
}
Related
the expected result is new list with last occurrence order.
Is it possible using the group by. Please do helpful
var list = new List<string> {"name1", "name2", "name1", "name3", "name4", "name2", "name3"};
var result = list
.Select((value, index) => (value, index))
.GroupBy(x => x.value)
.Select(g => g.Last());
Obj obj0 = new() { Id = 0, Name = "name1" };
Obj obj1 = new() { Id = 1, Name = "name2" };
Obj obj2 = new() { Id = 2, Name = "name1" };
Obj obj3 = new() { Id = 3, Name = "name3" };
Obj obj4 = new() { Id = 4, Name = "name4" };
Obj obj5 = new() { Id = 5, Name = "name2" };
Obj obj6 = new() { Id = 6, Name = "name3" };
List<Obj> list = new() { obj0,obj1,obj2, obj3 , obj4 , obj5 , obj6};
var result = list.OrderByDescending(o => o.Id).DistinctBy(p => p.Name).OrderBy(o => o.Id);
foreach (Obj obj in result)
{
Console.WriteLine(obj);
}
record Obj
{
public int Id { get; set; }
public string Name { get; set; }
}
In this contrived example, which closely resembles my real-world problem, I have a data set coming from an external source. Each record from the external source takes the following form:
[Classification] NVARCHAR(32),
[Rank] INT,
[Data] NVARCHAR(1024)
I am looking to build an object where the Rank and Data are patched into a single instance of a response object that contains list properties for the three hard-coded Classification values, ordered by Rank.
I have something that works, but I can't help but think that it could be done better. This is what I have:
public static void Main()
{
IEnumerable<GroupingTestRecord> records = new List<GroupingTestRecord>
{
new GroupingTestRecord { Classification = "A", Rank = 1, Data = "A1" },
new GroupingTestRecord { Classification = "A", Rank = 2, Data = "A2" },
new GroupingTestRecord { Classification = "A", Rank = 3, Data = "A3" },
new GroupingTestRecord { Classification = "B", Rank = 1, Data = "B1" },
new GroupingTestRecord { Classification = "B", Rank = 2, Data = "B2" },
new GroupingTestRecord { Classification = "B", Rank = 3, Data = "B3" },
new GroupingTestRecord { Classification = "C", Rank = 1, Data = "C1" },
new GroupingTestRecord { Classification = "C", Rank = 2, Data = "C2" },
new GroupingTestRecord { Classification = "C", Rank = 3, Data = "C3" },
};
GroupTestResult r = new GroupTestResult
{
A = records.Where(i => i.Classification == "A").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
B = records.Where(i => i.Classification == "B").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
C = records.Where(i => i.Classification == "C").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
};
The source record DTO:
public class GroupingTestRecord
{
public string Classification { get; set; }
public int? Rank { get; set; }
public string Data { get; set; }
}
The destination single class:
public class GroupTestResult
{
public IEnumerable<GroupTestResultItem> A { get; set; }
public IEnumerable<GroupTestResultItem> B { get; set; }
public IEnumerable<GroupTestResultItem> C { get; set; }
}
The distination child class:
public class GroupTestResultItem
{
public int? Rank { get; set; }
public string Data { get; set; }
}
Ouput
{
"A":[
{
"Rank":1,
"Data":"A1"
},
{
"Rank":2,
"Data":"A2"
},
{
"Rank":3,
"Data":"A3"
}
],
"B":[
{
"Rank":1,
"Data":"B1"
},
{
"Rank":2,
"Data":"B2"
},
{
"Rank":3,
"Data":"B3"
}
],
"C":[
{
"Rank":1,
"Data":"C1"
},
{
"Rank":2,
"Data":"C2"
},
{
"Rank":3,
"Data":"C3"
}
]
}
Fiddle
Is there a better way to achieve my goal here?
The same JSON output was achieved using GroupBy first on the Classification and applying ToDictionary on the resulting IGrouping<string, GroupingTestRecord>.Key
var r = records
.GroupBy(_ => _.Classification)
.ToDictionary(
k => k.Key,
v => v.Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank).ToArray()
);
var json = JsonConvert.SerializeObject(r);
Console.WriteLine(json);
which should easily deserialize to the destination single class (for example on a client)
var result = JsonConvert.DeserializeObject<GroupTestResult>(json);
is it possible to get the top level result into a GroupTestResult object?
Build the result from the dictionary
var result = new GroupTestResult {
A = r.ContainsKey("A") ? r["A"] : Enumerable.Empty<GroupTestResultItem>();,
B = r.ContainsKey("B") ? r["B"] : Enumerable.Empty<GroupTestResultItem>();,
C = r.ContainsKey("C") ? r["C"] : Enumerable.Empty<GroupTestResultItem>();,
};
Or this
var result = records.GroupBy(x => x.Classification)
.ToDictionary(x => x.Key, x => x.Select(y => new {y.Rank, y.Data})
.OrderBy(y => y.Rank));
Console.WriteLine(JsonConvert.SerializeObject(result));
Full Demo Here
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()
});
Is there a way to build Expression<Func<T,bool>> from Expression<Func<T>>?
For example for class
public class MyClass
{
public int Prop1{get;set;}
public int Prop2{get;set;}
public int Prop3{get;set;}
}
if Expression<Func<T>> is () => new MyClass{Prop2 = 5} then result should be x => x.Prop2 == 5
if Expression<Func<T>> is () => new MyClass{Prop1 = 1, Prop3 = 3} then result should be x => x.Prop1 == 1 && x.Prop3 == 3
In other words is it possible to create func with any number of conditions at runtime?
Like this:
static Expression<Func<T,bool>> Munge<T>(Expression<Func<T>> selector)
{
var memberInit = selector.Body as MemberInitExpression;
if (memberInit == null)
throw new InvalidOperationException("MemberInitExpression is expected");
var p = Expression.Parameter(typeof(T), "x");
Expression body = null;
foreach (MemberAssignment binding in memberInit.Bindings)
{
var comparer = Expression.Equal(
Expression.MakeMemberAccess(p, binding.Member),
binding.Expression);
body = body == null ? comparer : Expression.AndAlso(body, comparer);
}
if (body == null) body = Expression.Constant(true);
return Expression.Lambda<Func<T, bool>>(body, p);
}
Let's the code speak for himself:
class Program
{
static Expression<Func<T, bool>> Transform<T>(Expression<Func<T>> expression)
{
var initExpression = expression.Body as MemberInitExpression;
if (initExpression == null)
{
throw new ArgumentException();
}
Expression bodyExpression = Expression.Constant(true);
IEnumerable<MemberBinding> bindings = initExpression.Bindings;
ParameterExpression param = Expression.Parameter(typeof(T));
foreach (var memberBinding in bindings)
{
var memberAssigment = memberBinding as MemberAssignment;
if (memberAssigment == null)
{
throw new ArgumentException();
}
var member = memberAssigment.Member;
var value = memberAssigment.Expression;
bodyExpression = Expression.AndAlso(
bodyExpression,
Expression.Equal(
Expression.MakeMemberAccess(param, member),
value
)
);
}
return Expression.Lambda<Func<T, bool>>(bodyExpression, param);
}
static void Main(string[] args)
{
Expression<Func<MyClass>> exp = () => new MyClass { Prop1 = 1, Prop3 = 3 };
var result = Transform(exp);
var lambda = result.Compile();
var array = new[]
{
new MyClass { Prop1 = 1, Prop3 = 3 },
new MyClass { Prop1 = 1, Prop2 = 2, Prop3 = 3 },
new MyClass { Prop1 = 1, Prop3 = 1 },
new MyClass { Prop1 = 3, Prop3 = 3 },
new MyClass { Prop1 = 3, Prop3 = 1 },
new MyClass()
};
foreach (var o in array)
{
Console.WriteLine(lambda(o));
}
}
}
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]