I run into this issue again and again: how can I group a list of objects by a containing list of other objects?
I have a list of objects of type A and each of these objects has an property (lets call it ListProp) which is a list also. ListProp has elements of the type B. There are multiple elements of type A with identically B-objects in ListProp, but the ListProp property reference differs from element to element. How can I group these A-objects the fastest way, where the B-objects in ListProp are identically?
Sample code:
class Program
{
static void Main(string[] args)
{
var exampleList = new List<A>
{
// Should be in first group
new A { ListProp = new List<B>
{
new B { Prop = new C { Number = 0 }},
new B { Prop = new C { Number = 1 }}
}},
// Should be in first group
new A { ListProp = new List<B>
{
new B { Prop = new C { Number = 0 }},
new B { Prop = new C { Number = 1 }}
}},
// Should be in second group
new A { ListProp = new List<B>
{
new B { Prop = new C { Number = 0 }},
new B { Prop = new C { Number = 1 }},
new B { Prop = new C { Number = 1 }}
}},
// Should be in third group
new A { ListProp = new List<B>
{
new B { Prop = new C { Number = 0 }},
new B { Prop = new C { Number = 0 }}
}}
};
// Doesn't work because the reference of ListProp is always different
var groupedExampleList = exampleList.GroupBy(x => x.ListProp);
}
}
class C
{
public int Number { get; set; }
public override bool Equals(object o)
{
if (o is C)
return Number.Equals(((C)o).Number);
else
return false;
}
}
class B
{
public C Prop { get; set; }
}
class A
{
public IList<B> ListProp { get; set; }
}
You can implement IEqualityComparer<List<B>> and use it in the other GroupBy overload.
public class ListOfBEqualityComparer : IEqualityComparer<List<B>>
{
public bool Equals(List<B> x, List<B> y)
{
// you can also implement IEqualityComparer<B> and use the overload
return x.SequenceEqual(y);
}
public int GetHashCode(List<B> obj)
{
//implementation of List<T> may not work for your situation
return obj.GetHashCode();
}
}
Then you can use the overload
var groupedExampleList = exampleList.GroupBy(x => x.ListProp,
new ListOfBEqualityComparer());
Try this:
GroupBy(x => String.Join(",", x.ListProp));
It will group by 0,1; 0,1; 0,1; 0,1,1; 0,1 accordingly.
I would approach this the following way:
Associate each child element (in ListProp property) with its parent
Group the parents by children
Project the results
var data = exampleList.SelectMany(a=>a.ListProp.Select(x=>new{Key = x.Prop.Number, Value = a}))
.GroupBy(x=>x.Key)
.Select(g=>new {Number = g.Key, Items = g.ToList()});
Related
Let's say I have a list of objects which inherits from the same base class. Is it then possible to get the value that is only specified in one of the subclasses through LINQ? In my example, i want to find the instance which has a specific object which has a specific property?
I have made this example in Linqpad:
void Main()
{
var list = new List<A>
{
new B
{
MyProp = new D{ OtherProp = 1}
},
new C(),
new B
{
MyProp = new D{ OtherProp = 30}
},
};
list.Where(x => ....) // how to find the instance where OtherProp == 30 ?
}
public class A
{
public int JustAprop { get; set; }
}
public class B : A
{
public D MyProp { get; set; }
}
public class C : A
{
}
public class D
{
public int OtherProp { get; set; }
}
You can use Where method and try to cast every item to B class, then OtherProp value inside MyProp
var result = list.Where(l => (l as B)?.MyProp?.OtherProp == 30);
This can be rewritten a little bit using pattern matching with is operator
var result = list.Where(l => l is B b && b.MyProp.OtherProp == 30);
Another way is to use OfType<T> method to get a list of B instances only, then check OtherProp inside MyProp
var result = list.OfType<B>().Where(b => b.MyProp.OtherProp == 30);
I have 2 lists of a specific type, in this case it is List. In the class DataDictionary there is a property called TableName. I have 2 lists with the same type I am trying to compare. I have other properties aswell which I need to hold association with that specific TableName so I can't just compare them separately.
I need to find a way to compare the TableName in 2 different lists of DataDictionary and then find which ones they don't have in common. From there I then need to compare all the other properties against the 2 items in each list with the same TableName.
I have tried to use the Except IEnumerate solution which works if you just compare the strings directly but I don't know how to keep the association with the object.
List<DataDictionary> ColumnsDataDict = daDD.getTablesandColumnsDataDictionary();
List<DataDictionary> ColumnsWizard = daWiz.getColumnsWizard();
var newlist = ColumnsWizard.Except(ColumnsDataDict);
foreach(DataDictionary item in newlist)
{
Console.WriteLine(item.TableName);
}
Here is the DataDictionary class:
public string TableName { get; set; }
public string Description { get; set; }
public string TableID { get; set; }
public string ColumnDesc { get; set; }
public string ColumnName { get; set; }
This directly compares the objects, but I just want to compare the TableName property in my DataDictionary class. I want this to then get a list of objects that doesn't have the same table name in each list. Any help is appreciated, thanks!
I don't believe your problem can be solved with LINQ alone and there for I would recommend using a good old loop.
If you want to compare two lists, you will have to use two loops nested in one another.
Like this:
public class Program
{
public static void Main()
{
var listA = new List<Foo>() {
new Foo() { TableName = "Table A", Value = "Foo 1" },
new Foo() { TableName = "Table B", Value = "Foo 1" },
};
var listB = new List<Foo>() {
new Foo() { TableName = "Table A", Value = "Foo 10" },
new Foo() { TableName = "Table C", Value = "Foo 12" },
};
foreach (var itemA in listA)
{
foreach (var itemB in listB)
{
if (itemA.TableName == itemB.TableName)
{
Console.WriteLine($"ItemA's Value: {itemA.Value}");
Console.WriteLine($"ItemB's Value: {itemB.Value}");
}
}
}
}
}
public class Foo
{
public string TableName { get; set; }
public string Value { get; set; }
}
At the position where I am printing the values of itemA and itemB you can compare your objects and find the difference between them.
So instead of:
Console.WriteLine($"ItemA's Value: {itemA.Value}");
Console.WriteLine($"ItemB's Value: {itemB.Value}");
Maybe something like this:
if (itemA.Value != itemB.Value) //extend the `if` with all the properties you want to compare
{
Console.WriteLine($"ItemA's Value isn't equal to ItemB's Value");
}
If you need to check if listA doesn't have an entry in listB then you can extend the inner loop like this:
foreach (var itemA in listA)
{
var found = false;
foreach (var itemB in listB)
{
if (itemA.TableName == itemB.TableName)
{
found = true;
}
}
if (found == false)
{
Console.WriteLine("ItemA's TableName wasn't found in listB");
}
}
SOLUTION:
// Merges both objects
List<DataDictionary> duplicatesRemovedLists = ColumnsDataDict.Concat (ColumnsWizard).ToList ();
// Removes common objects based on its property value (eg: TableName)
foreach (var cddProp in ColumnsDataDict) {
foreach (var cwProp in ColumnsWizard) {
if ((cddProp.TableName == cwProp.TableName)) {
duplicatesRemovedLists.Remove (cddProp);
duplicatesRemovedLists.Remove (cwProp);
}
}
}
// Prints expected output
foreach (DataDictionary item in duplicatesRemovedLists) Console.WriteLine (item.TableName);
You can use several LINQ methods implementing a customer IEqualityComparer.
For instance having a comparer for the property TableName:
public class TableNameEqualityComparer : IEqualityComparer<DataDictionary>
{
public bool Equals(DataDictionary x, DataDictionary y)
{
if (x == null && y == null)
{
return true;
}
return x != null && y != null && x.TableName == y.TableName;
}
public int GetHashCode(DataDictionary obj)
{
return obj?.TableName?.GetHashCode() ?? 0;
}
}
Two instances returning true for the Equals method must return
the same value for GetHashCode.
Just make an instance of your custom comparer.
var listA = new List<DataDictionary>()
{
new DataDictionary() {TableName = "Table A"},
new DataDictionary() {TableName = "Table B"},
};
var listB = new List<DataDictionary>()
{
new DataDictionary() {TableName = "Table A"},
new DataDictionary() {TableName = "Table C"},
};
var tableNameComparer = new TableNameEqualityComparer();
And then use it with different LINQ methods:
// A - B
var listAExceptB = listA.Except(listB, tableNameComparer);
Assert.Collection(listAExceptB,
x => Assert.Equal("Table B", x.TableName));
// B - A
var listBExceptA = listB.Except(listA, tableNameComparer);
Assert.Collection(listBExceptA,
x => Assert.Equal("Table C", x.TableName));
// A ∩ B
var listIntersect = listA.Intersect(listB, tableNameComparer);
Assert.Collection(listIntersect,
x => Assert.Equal("Table A", x.TableName));
// A ∪ B
var listUnion = listA.Union(listB, tableNameComparer);
Assert.Collection(listUnion,
x => Assert.Equal("Table A", x.TableName),
x => Assert.Equal("Table B", x.TableName),
x => Assert.Equal("Table C", x.TableName));
I have the below two classes:
public class FirstInner
{
public int Id { get; set; }
public string Type { get; set; }
public string RoleId { get; set; }
}
public class SecondInner
{
public int Id { get; set; }
public string Type { get; set; }
}
Again, there are lists of those types inside the below two classes:
public class FirstOuter
{
public int Id { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public List<FirstInner> Inners { get; set; }
}
public class SecondOuter
{
public int Id { get; set; }
public string Name { get; set; }
public List<SecondInner> Inners { get; set; }
}
Now, I have list of FirstOuter and SecondOuter. I need to check if FirstOuter list is a subset of SecondOuter list.
Please note:
The names of the classes cannot be changed as they are from different systems.
Some additional properties are present in FirstOuter but not in SecondOuter. When comparing subset, we can ignore their presence in SecondOuter.
No.2 is true for FirstInner and SecondInner as well.
List items can be in any order---FirstOuterList[1] could be found in SecondOuterList[3], based on Id, but inside that again need to compare that FirstOuterList[1].FirstInner[3], could be found in SecondOuterList[3].SecondInner[2], based on Id.
I tried Intersect, but that is failing as the property names are mismatching. Another solution I have is doing the crude for each iteration, which I want to avoid.
Should I convert the SecondOuter list to FirstOuter list, ignoring the additional properties?
Basically, here is a test data:
var firstInnerList = new List<FirstInner>();
firstInnerList.Add(new FirstInner
{
Id = 1,
Type = "xx",
RoleId = "5"
});
var secondInnerList = new List<SecondInner>();
secondInner.Add(new SecondInner
{
Id = 1,
Type = "xx"
});
var firstOuter = new FirstOuter
{
Id = 1,
Name = "John",
Title = "Cena",
Inners = firstInnerList
}
var secondOuter = new SecondOuter
{
Id = 1,
Name = "John",
Inners = secondInnerList,
}
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
Need to check if firstOuterList is part of secondOuterList (ignoring the additional properties).
So the foreach way that I have is:
foreach (var item in firstOuterList)
{
var secondItem = secondOuterList.Find(so => so.Id == item.Id);
//if secondItem is null->throw exception
if (item.Name == secondItem.Name)
{
foreach (var firstInnerItem in item.Inners)
{
var secondInnerItem = secondItem.Inners.Find(sI => sI.Id == firstInnerItem.Id);
//if secondInnerItem is null,throw exception
if (firstInnerItem.Type != secondInnerItem.Type)
{
//throw exception
}
}
}
else
{
//throw exception
}
}
//move with normal flow
Please let me know if there is any better approach.
First, do the join of firstOuterList and secondOuterList
bool isSubset = false;
var firstOuterList = new List<FirstOuter> { firstOuter };
var secondOuterList = new List<SecondOuter> { secondOuter };
var jointOuterList = firstOuterList.Join(
secondOuterList,
p => new { p.Id, p.Name },
m => new { m.Id, m.Name },
(p, m) => new { FOuterList = p, SOuterList = m }
);
if(jointOuterList.Count != firstOuterList.Count)
{
isSubset = false;
return;
}
foreach(var item in jointOuterList)
{
var jointInnerList = item.firstInnerList.Join(
item.firstInnerList,
p => new { p.Id, p.Type },
m => new { m.Id, m.type },
(p, m) => p.Id
);
if(jointInnerList.Count != item.firstInnerList.Count)
{
isSubset = false;
return;
}
}
Note: I am assuming Id is unique in its outer lists. It means there will not be multiple entries with same id in a list. If no, then we need to use group by in above query
I think to break the question down..
We have two sets of Ids, the Inners and the Outers.
We have two instances of those sets, the Firsts and the Seconds.
We want Second's inner Ids to be a subset of First's inner Ids.
We want Second's outer Ids to be a subset of First's outer Ids.
If that's the case, these are a couple of working test cases:
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsTrue(isInnerSubset);
Assert.IsTrue(isOuterSubset);
}
[TestMethod]
public void ICanSeeWhenInnerAndOuterCollectionsAreNotSubsets()
{
HashSet<int> firstInnerIds = new HashSet<int>(GetFirstOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> firstOuterIds = new HashSet<int>(GetFirstOuterList().Select(outer => outer.Id).Distinct());
HashSet<int> secondInnerIds = new HashSet<int>(GetSecondOuterList().SelectMany(outer => outer.Inners.Select(inner => inner.Id)).Distinct());
HashSet<int> secondOuterIds = new HashSet<int>(GetSecondOuterList().Select(outer => outer.Id).Distinct());
firstInnerIds.Clear();
firstInnerIds.Add(5);
firstOuterIds.Clear();
firstOuterIds.Add(5);
bool isInnerSubset = secondInnerIds.IsSubsetOf(firstInnerIds);
bool isOuterSubset = secondOuterIds.IsSubsetOf(firstOuterIds);
Assert.IsFalse(isInnerSubset);
Assert.IsFalse(isOuterSubset);
}
private List<FirstOuter> GetFirstOuterList() { ... }
private List<SecondOuter> GetSecondOuterList() { ... }
I was trying to follow and then extend an old example, Linq query list contains a list, but it didn't work for me.
class Part
{
public int id { get; set; }
public string name { get; set; }
}
class Program
{
static void Main(string[] args)
{
{
List<int> L1 = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> L2 = new List<int> { 4, 3 };
bool t = L2.Where(p => L2.All(q => L1.Contains(q))).Any();
}
{
List<Part> L1 = new List<Part> { new Part { id = 1 }, new Part { id = 2 }, new Part { id = 3 }, new Part { id = 4 } };
List<Part> L2 = new List<Part> { new Part { id = 3 }, new Part { id = 4 } };
bool u = L2.Where(p => L2.All(q => L1.Contains(q.id))).Any();
}
}
}
The first test works for me but doesn't exactly match the earlier code I found. My second test has a syntax error at "L1.Contains(q.id)". I'm stumped.
L1 is a List<Part>, q is a Part, q.id is an int.
L1 cannot Contain an item of type int
To check if L1 contains an item with that ID, use Any
L2.All(q => L1.Any(e => e.id == q.id))
When you use the equality operator you are checking to see if the lists contain references to the same objects. If you want to match then you have to do something like you did by choosing which values to compare on. Your syntax error is coming from the fact that you are comparing ids to Parts. If you project L1 to the ids (L1.Select(p => p.Id)) you should be good.
For "IsContained" question:
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
class Part
{
public int id { get; set; }
public string name { get; set; }
}
public static void Main()
{
{
var L1 = new List<int> { 1, 2, 3, 4, 5, 6 };
var L2 = new List<int> { 4, 3 };
bool t = L2.All(l2 => L1.Contains(l2));
Console.WriteLine("L1 contains L2: {0}", t);
}
{
var L1 = new List<Part> { new Part { id = 1 }, new Part { id = 2 }, new Part { id = 3 }, new Part { id = 4 } };
var L2 = new List<Part> { new Part { id = 3 }, new Part { id = 4 } };
bool u = L2.All(l2 => L1.Any(l1 => l1.id == l2.id));
Console.WriteLine("L1 contains L2: {0}", u);
}
}
}
You can't use Contains there because the type is a List<Part>, at least not without some kind of projection or a different argument. If you're going to commonly be intersecting lists with these types I recommend implementing IEquatable<T> for part like so;
class Part : IEquatable<Part>
{
public int id { get; set; }
public string name { get; set; }
public bool Equals(Part other)
{
//Check whether the compared object is null.
if (Object.ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (Object.ReferenceEquals(this, other)) return true;
//Check whether the products' properties are equal.
return id.Equals(other.id) && name.Equals(other.name);
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public override int GetHashCode()
{
//Get hash code for the Name field if it is not null.
int nameHash = name == null ? 0 : name.GetHashCode();
int idHash = id.GetHashCode();
//Calculate the hash code for the part.
return nameHash ^ idHash;
}
}
In doing so you can simply do L1.Intersect(L2) and get the expected results. If you're just going to do this once probably simpler to write something like; L2.All(q => L1.Any(e => e.id == q.id)) though I wouldn't recommend repeated use of that in place of Intersect.
I have the following classes ( simplified ):
public class SeguradoraCalculo
{
public int CodigoSeguradora { get; set; }
public List<Franquia> Franquias{get;set;}
}
public class Franquia {
public int CodFranquia{get;set;}
}
And i have the following data:
var allData = new List<SeguradoraCalculo>(){new SeguradoraCalculo(){
CodigoSeguradora = 11,
Franquias = new List<Franquia>()
{
new Franquia()
{
CodigoFranquia = 1
},
new Franquia()
{
CodigoFranquia = 2
}
}
}
};
var except = new List<SeguradoraCalculo>()
{
new SeguradoraCalculo()
{
CodigoSeguradora = 11,
Franquias = new List<Franquia>()
{
new Franquia()
{
CodigoFranquia = 1
}
}
}
};
How can i have a result that, removing items that match CodigoSeguradora and CodigoFranquia, existing Franquias are removed and i get SeguradoraCalculo only if i have at least one Franquia in my Franquias List?
In this example my final list would be:
CodigoSeguradora = 11,
Franquias = new List<Franquia>()
{
new Franquia()
{
CodigoFranquia = 2
}
}
Thanks
First join the two collections, based on the key, then go through each joined pair and remove all items from the first that are contained in the second:
var query = allData.Join(except,
item => item.CodigoSeguradora,
item => item.CodigoSeguradora,
(a, b) => new { a, b });
foreach (var pair in query)
pair.a.Franquias.RemoveAll(f =>
pair.b.Franquias.Select(x => x.CodFranquia).Contains(f.CodFranquia));