Transform a collection - c#

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]

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

OrderBy child property linq to sql

Could someone help me with ordering nested collection in linq to sql. See my example below:
public class A
{
public int Id { get; set; }
}
public class B
{
public virtual ICollection<A> ListA { get; set; }
}
Call for data from db:
_unitOfWork.DbContext.B
.OrderByDescending(ev => ev.ListA.OrderBy(a => a.Id))
.ToList();
I case when ListA not empty all works fine. But if List A is empty i get an exception says that at least one object needs to implement IComparable. Is there a way to overcome that problem ?
You cannot order by null. null does not implement IComparable obviously.
Add a where clause before the orderby to check if listA is not null.
_unitOfWork.DbContext.B
.Where(ev => ev.ListA != null)
.OrderByDescending(ev => ev.ListA.OrderBy(a => a.Id))
.ToList();
If the aim is to have the B with Something in ListA, then Bs with empty collection, then Bs with Null reference.
With a ListA that is also ordered, you can check for null and empty list, then order based on that.
var Bs = new[] {
new B{ListA = new []{ new A { Id = 3 },new A { Id = 1 },new A { Id = 2 }} }
,new B{ListA = new List<A>{} }
,new B{ListA = null }
};
var result = Bs.Select(b =>
{
int i = 0;
if (b.ListA == null)
{
i = 2;
}
else if (!b.ListA.Any())
{
i = 1;
}
else {
b.ListA = b.ListA.OrderBy(a => a.Id).ToList();
}
return new { oIndex = i, value = b };
})
.OrderByDescending(x => x.oIndex)
.Select(g => g.value);
You stated : "when ListA not empty all works fine"; I'm not sure to understand how. As the following code will throw without any null or empty collection.
var Bis = new[] {
new B{ListA = new []{ new A { Id = 3 },new A { Id = 1 },new A { Id = 2 }} }
,new B{ListA = new []{ new A { Id = 3 },new A { Id = 1 }} }
,new B{ListA = new []{ new A { Id = 999 }} }
,new B{ListA = new []{ new A { Id = 3 },new A { Id = 1 },new A { Id = 2 },new A { Id = 4 }} }
};
var result = Bis.OrderByDescending(ev => ev.ListA.OrderBy(a => a.Id)).ToList();
Il you are comparing list you need to compare them on something. Is {1, 2, 3} bigger than {1,1,1,1}? How is {1,2,3} compare to {999, 999}? {2} and {1,1}? Because it has more element? Bigger value? What if one has bigger value but the others sum is twice bigger? For 2 list of 0s.. you base you comparaison on Sum and Value?

Get distinct List<T> by properties of U of the list T.X?

Given the following the following code.
class T {
public List<U> X { get; set; } /*.....*/
}
class U {
public int A { get; set; }
public int B { get; set; }
// other properties omit for easier testing
}
var l = new List<T> {
new T { X = new List<U> { new U { A = 0, B = 9 }, new U { A = 1, B = 8 } } },
new T { X = new List<U> { new U { A = 0, B = 9 }, new U { A = 1, B = 8 } } },
new T { X = new List<U> { new U { A = 2, B = 4 }, new U { A = 3, B = 5 } } },
new T { X = new List<U> { new U { A = 2, B = 4 }, new U { A = 3, B = 5 } } },
// ......
};
What's the most concise way to get the distinct List<T> of the l? The return is expected to have two sub-lists which X has [{0,9}, {1,8}] and [{2,4}, {3,5}].
Updated code based on comments to Enigmativity's answer:
interface IBase<I> { I A { get; set; } I B { get; set; } }
class T<I> {
public List<U<I>> X { get; set; } /*.....*/
}
class U<I> : IBase<I> {
public I A { get; set; }
public I B { get; set; }
// other properties omit for easier testing
}
var l = new List<T<int>> {
new T<int> { X = new List<U<int>> { new U<int> { A=0, B=9 }, new U<int> { A=1, B=8 } } },
new T<int> { X = new List<U<int>> { new U<int> { A=0, B=9 }, new U<int> { A=1, B=8 } } },
new T<int> { X = new List<U<int>> { new U<int> { A=2, B=4 }, new U<int> { A=3, B=5 } } },
new T<int> { X = new List<U<int>> { new U<int> { A=2, B=4 }, new U<int> { A=3, B=5 } } },
// ......
};
Updated sample data as per comments:
var l = new List<T> {
new T { X = new List<U> { new U { A = 0, B = 9 }, new U { A = 1, B = 8 } } },
new T { X = new List<U> { new U { A = 0, B = 9 }, new U { A = 1, B = 8 } } },
new T { X = new List<U> { new U { A = 2, B = 4 }, new U { A = 3, B = 5 } } },
new T { X = new List<U> { new U { A = 2, B = 4 }, new U { A = 3, B = 5 } } },
new T { X = new List<U> {} }
// ......
};
For your given code the quickest way is to implement an IEqualityComparer<T> and use that in the standard LINQ .Distinct operator.
public class TEqualityComparer : IEqualityComparer<T>
{
public bool Equals(T t1, T t2)
{
if (t2 == null && t1 == null)
return true;
else if (t1 == null || t2 == null)
return false;
else
{
return
t1.X.Select(x => x.A).SequenceEqual(t2.X.Select(x => x.A))
&& t1.X.Select(x => x.B).SequenceEqual(t2.X.Select(x => x.B));
}
}
public int GetHashCode(T t)
{
return t.X.Select(x => x.A.GetHashCode())
.Concat(t.X.Select(x => x.B.GetHashCode()))
.Aggregate((x1, x2) => (x1 * 17 + 13) ^ x2);
}
}
Then you can do this:
IEnumerable<T> result = l.Distinct(new TEqualityComparer());
Which gives you:
But you want the result as a List<List<U>> so then you'd do this:
List<List<U>> result =
l.Distinct(new TEqualityComparer())
.Select(t => t.X.ToList())
.ToList();
Based on your updated code, this is what you need:
public class TEqualityComparer<V> : IEqualityComparer<T<V>>
{
public bool Equals(T<V> t1, T<V> t2)
{
if (t2 == null && t1 == null)
return true;
else if (t1 == null || t2 == null)
return false;
else
{
return
t1.X.Select(x => x.A).SequenceEqual(t2.X.Select(x => x.A))
&& t1.X.Select(x => x.B).SequenceEqual(t2.X.Select(x => x.B));
}
}
public int GetHashCode(T<V> t)
{
return t.X.Select(x => x.A.GetHashCode())
.Concat(t.X.Select(x => x.B.GetHashCode()))
.Aggregate((x1, x2) => (x1 * 17 + 13) ^ x2);
}
}
You'd call it like:
IEnumerable<T<int>> result = l.Distinct(new TEqualityComparer<int>());
...or:
List<List<U<int>>> result =
l.Distinct(new TEqualityComparer<int>())
.Select(t => t.X.ToList())
.ToList();
With the updated data all you need to do to make this work now is to change GetHashCode to this:
public int GetHashCode(T<V> t)
{
return t.X.Select(x => x.A.GetHashCode())
.Concat(t.X.Select(x => x.B.GetHashCode()))
.DefaultIfEmpty(0)
.Aggregate((x1, x2) => (x1 * 17 + 13) ^ x2);
}
The data you added was for the old classes. I updated it to this:
var l = new List<T<int>> {
new T<int> { X = new List<U<int>> { new U<int> { A=0, B=9 }, new U<int> { A=1, B=8 } } },
new T<int> { X = new List<U<int>> { new U<int> { A=0, B=9 }, new U<int> { A=1, B=8 } } },
new T<int> { X = new List<U<int>> { new U<int> { A=2, B=4 }, new U<int> { A=3, B=5 } } },
new T<int> { X = new List<U<int>> { new U<int> { A=2, B=4 }, new U<int> { A=3, B=5 } } },
new T<int> { X = new List<U<int>> { } },
// ......
};

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

C# linq totalization

I need to get a totalization by enum values. See this example:
In this source:
namespace ConsoleApplication
{
enum fruits { Orange, Grape, Papaya }
class item
{
public fruits fruit;
public string foo;
}
class Program
{
static void Main(string[] args)
{
item[] list = new item[]
{
new item() { fruit = fruits.Orange, foo = "afc" },
new item() { fruit = fruits.Orange, foo = "dsf" },
new item() { fruit = fruits.Orange, foo = "gsi" },
new item() { fruit = fruits.Orange, foo = "jskl" },
new item() { fruit = fruits.Grape, foo = "mno" },
new item() { fruit = fruits.Grape, foo = "pqu" },
new item() { fruit = fruits.Grape, foo = "tvs" },
};
var vTotals = from... //Here
}
}
}
I would like to vTotals be
of type
Dictionary<fruits, int>
with the values
{
{ fruits.Orange, 4 }
{ fruits.Grape, 3 }
{ fruits.Papaya, 0 }
}
How I can do it with Linq?
Thanks in advance
What you want to do here is logically a group join. You want to join this table with a sequence representing each of the fruits, and then count the size of those groups.
var totals = Enum.GetValues(typeof(fruits)).OfType<fruits>()
.GroupJoin(list,
fruit => fruit,
item => item.fruit,
(fruit, group) => new { Key = fruit, Value = group.Count() })
.ToDictionary(pair => pair.Key, pair => pair.Value);
You can use group join of all fruit types with items:
var vTotals = from fruits f in Enum.GetValues(typeof(fruits))
join i in list on f equals i.fruit into g
select new {
Fruit = f,
Count = g.Count()
};
Result:
[
{ Fruit: "Orange", Count: 4 },
{ Fruit: "Grape", Count: 3 },
{ Fruit: "Papaya", Count: 0 }
]
Here's one way to do it. It may not be considered as pretty as doing it all in one query, but it is (IMO) clear: (first part of the code thanks to D Stanley, who since deleted his answer)
var vTotals = list.GroupBy(i => i.fruit)
.ToDictionary(g => g.Key, g => g.Count());
foreach (var fruit in Enum.GetValues(typeof(fruits)).Cast<fruits>()
.Where(x => !vTotals.ContainsKey(x)))
{
vTotals.Add(fruit, 0);
}
var total = from e in Enum.GetValues(typeof(fruits)).OfType<fruits>()
select new
{
Fruit = e,
Count = list.Where(f => f.fruit == e).Count()
};
From #Servy's answer, if you want to unit test it, using MSTest.
[TestClass]
public class DummyTests {
[TestMethod]
public void GroupCountByFruitType() {
// arrange
var expected = new Dictionary<Fruits, int>() {
{ Fruits.Grape, 3 }
, { Fruits.Orange, 4 }
, { Fruits.Papaya, 0 }
};
Item[] list = new Item[] {
new Item() { Fruit = Fruits.Orange, Foo = "afc" },
new Item() { Fruit = Fruits.Orange, Foo = "dsf" },
new Item() { Fruit = Fruits.Orange, Foo = "gsi" },
new Item() { Fruit = Fruits.Orange, Foo = "jskl" },
new Item() { Fruit = Fruits.Grape, Foo = "mno" },
new Item() { Fruit = Fruits.Grape, Foo = "pqu" },
new Item() { Fruit = Fruits.Grape, Foo = "tvs" }
};
// act
var actual = Enum.GetValues(typeof(Fruits)).OfType<Fruits>()
.GroupJoin(list
, fruit => fruit
, item => item.Fruit
, (fruit, group) => new { Key = fruit, Value = group.Count() })
.ToDictionary(pair => pair.Key, pair => pair.Value);
// assert
actual.ToList()
.ForEach(item => Assert.AreEqual(expected[item.Key], item.Value));
}
private class Item {
public Fruits Fruit { get; set; }
public string Foo { get; set; }
}
private enum Fruits {
Grape,
Orange,
Papaya
}
}
This should do it
var vTotals = list.GroupBy(item => item.fruit)
.Select(item => Tuple.Create(item.Key, item.Count()))
.ToDictionary(key => key.Item1, value => value.Item2);
Here we simply group on the fruit name with their count and later turn it into a dictionary

Categories