I have a class Items with properties (Id, Name, Code, Price).
The List of Items is populated with duplicated items.
For ex.:
1 Item1 IT00001 $100
2 Item2 IT00002 $200
3 Item3 IT00003 $150
1 Item1 IT00001 $100
3 Item3 IT00003 $150
How to remove the duplicates in the list using linq?
var distinctItems = items.GroupBy(x => x.Id).Select(y => y.First());
var distinctItems = items.Distinct();
To match on only some of the properties, create a custom equality comparer, e.g.:
class DistinctItemComparer : IEqualityComparer<Item> {
public bool Equals(Item x, Item y) {
return x.Id == y.Id &&
x.Name == y.Name &&
x.Code == y.Code &&
x.Price == y.Price;
}
public int GetHashCode(Item obj) {
return obj.Id.GetHashCode() ^
obj.Name.GetHashCode() ^
obj.Code.GetHashCode() ^
obj.Price.GetHashCode();
}
}
Then use it like this:
var distinctItems = items.Distinct(new DistinctItemComparer());
If there is something that is throwing off your Distinct query, you might want to look at MoreLinq and use the DistinctBy operator and select distinct objects by id.
var distinct = items.DistinctBy( i => i.Id );
This is how I was able to group by with Linq. Hope it helps.
var query = collection.GroupBy(x => x.title).Select(y => y.FirstOrDefault());
An universal extension method:
public static class EnumerableExtensions
{
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> keySelector)
{
return enumerable.GroupBy(keySelector).Select(grp => grp.First());
}
}
Example of usage:
var lstDst = lst.DistinctBy(item => item.Key);
You have three option here for removing duplicate item in your List:
Use a a custom equality comparer and then use Distinct(new DistinctItemComparer()) as #Christian Hayter mentioned.
Use GroupBy, but please note in GroupBy you should Group by all of the columns because if you just group by Id it doesn't remove duplicate items always. For example consider the following example:
List<Item> a = new List<Item>
{
new Item {Id = 1, Name = "Item1", Code = "IT00001", Price = 100},
new Item {Id = 2, Name = "Item2", Code = "IT00002", Price = 200},
new Item {Id = 3, Name = "Item3", Code = "IT00003", Price = 150},
new Item {Id = 1, Name = "Item1", Code = "IT00001", Price = 100},
new Item {Id = 3, Name = "Item3", Code = "IT00003", Price = 150},
new Item {Id = 3, Name = "Item3", Code = "IT00004", Price = 250}
};
var distinctItems = a.GroupBy(x => x.Id).Select(y => y.First());
The result for this grouping will be:
{Id = 1, Name = "Item1", Code = "IT00001", Price = 100}
{Id = 2, Name = "Item2", Code = "IT00002", Price = 200}
{Id = 3, Name = "Item3", Code = "IT00003", Price = 150}
Which is incorrect because it considers {Id = 3, Name = "Item3", Code = "IT00004", Price = 250} as duplicate. So the correct query would be:
var distinctItems = a.GroupBy(c => new { c.Id , c.Name , c.Code , c.Price})
.Select(c => c.First()).ToList();
3.Override Equal and GetHashCode in item class:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Code { get; set; }
public int Price { get; set; }
public override bool Equals(object obj)
{
if (!(obj is Item))
return false;
Item p = (Item)obj;
return (p.Id == Id && p.Name == Name && p.Code == Code && p.Price == Price);
}
public override int GetHashCode()
{
return String.Format("{0}|{1}|{2}|{3}", Id, Name, Code, Price).GetHashCode();
}
}
Then you can use it like this:
var distinctItems = a.Distinct();
Use Distinct() but keep in mind that it uses the default equality comparer to compare values, so if you want anything beyond that you need to implement your own comparer.
Please see http://msdn.microsoft.com/en-us/library/bb348436.aspx for an example.
Try this extension method out. Hopefully this could help.
public static class DistinctHelper
{
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
var identifiedKeys = new HashSet<TKey>();
return source.Where(element => identifiedKeys.Add(keySelector(element)));
}
}
Usage:
var outputList = sourceList.DistinctBy(x => x.TargetProperty);
List<Employee> employees = new List<Employee>()
{
new Employee{Id =1,Name="AAAAA"}
, new Employee{Id =2,Name="BBBBB"}
, new Employee{Id =3,Name="AAAAA"}
, new Employee{Id =4,Name="CCCCC"}
, new Employee{Id =5,Name="AAAAA"}
};
List<Employee> duplicateEmployees = employees.Except(employees.GroupBy(i => i.Name)
.Select(ss => ss.FirstOrDefault()))
.ToList();
Another workaround, not beautiful buy workable.
I have an XML file with an element called "MEMDES" with two attribute as "GRADE" and "SPD" to record the RAM module information.
There are lot of dupelicate items in SPD.
So here is the code I use to remove the dupelicated items:
IEnumerable<XElement> MList =
from RAMList in PREF.Descendants("MEMDES")
where (string)RAMList.Attribute("GRADE") == "DDR4"
select RAMList;
List<string> sellist = new List<string>();
foreach (var MEMList in MList)
{
sellist.Add((string)MEMList.Attribute("SPD").Value);
}
foreach (string slist in sellist.Distinct())
{
comboBox1.Items.Add(slist);
}
When you don't want to write IEqualityComparer you can try something like following.
class Program
{
private static void Main(string[] args)
{
var items = new List<Item>();
items.Add(new Item {Id = 1, Name = "Item1"});
items.Add(new Item {Id = 2, Name = "Item2"});
items.Add(new Item {Id = 3, Name = "Item3"});
//Duplicate item
items.Add(new Item {Id = 4, Name = "Item4"});
//Duplicate item
items.Add(new Item {Id = 2, Name = "Item2"});
items.Add(new Item {Id = 3, Name = "Item3"});
var res = items.Select(i => new {i.Id, i.Name})
.Distinct().Select(x => new Item {Id = x.Id, Name = x.Name}).ToList();
// now res contains distinct records
}
}
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
Related
I need to group items in a list and then pass each group to a function for further elaboration.
This is my code:
var list = new List<MyObj>(); // list is created ad populated elsewhere in code...
var query = list.AsEnumerable();
query = query.Where(x => x.MyProp == true).Select(x => x); // query definition is way more complicated
var grp = query.GroupBy(x => new { x.Name, x.Surname }).ToList();
Here grp is of type List<IGrouping<'a, MyObj>>.
I can easily iterate through my items with:
foreach (var g in grp)
{
foreach (var o in g)
{
// here "o" is of type MyObj
}
}
but I don't know how to create a function that receives a group and iterates through its items:
foreach (var g in grp)
{
DoSomethingWithGroup(g);
}
This is because I have an anonymous type (Key) in grp definition.
I tried to replace the Key anonymous type with a custom type:
private class GrpKey
{
public string Name { get; set; }
public string Surname { get; set; }
}
/* ... */
var grp = query.GroupBy(x => new GrpKey { Name = x.Name, Surname = x.Surname }).ToList();
This way grp is of type List<IGrouping<MyGrpKey, MyObj>> instead of List<IGrouping<'a, MyObj>>.
I could then create a function:
private void DoSomethingWithGroup(IGrouping<MyGrpKey, MyObj>) { /* ... */ }
Unfortunately, this way grouping doesn't work anymore: grp now contains as many groups as items in the source list, each one with a single item.
Instead of specifying your method like you did:
private void DoSomethingWithGroup(IGrouping<MyGrpKey, MyObj>)
separate the key and the elements into their on parameters
private void DoSomethingWithGroup<T>(T groupKey, IEnumerable<MyObj> entities)
With this change you can do the following:
//Populate list with some dummy data
var list = new List<MyObj>()
{
new MyObj { Id = 1, MyProp = true, Name = "A", Surname = "B"},
new MyObj { Id = 2, MyProp = false, Name = "A", Surname = "B"},
new MyObj { Id = 3, MyProp = true, Name = "B", Surname = "B"},
new MyObj { Id = 4, MyProp = true, Name = "B", Surname = "B"},
new MyObj { Id = 5, MyProp = true, Name = "C", Surname = "B"},
};
//Perform the grouping
var groups = list
.Where(x => x.MyProp)
.GroupBy(x => new { x.Name, x.Surname })
.ToList();
//Perform some arbitrary action on the group basis
foreach (var group in groups)
{
DoSomethingWithGroup(group.Key, group);
}
If the implementation of the DoSomethignWithGroup looks like this:
void DoSomethingWithGroup<T>(T groupKey, IEnumerable<MyObj> entities)
{
Console.WriteLine(groupKey);
foreach (var entity in entities)
{
Console.WriteLine($"- {entity.Id}");
}
}
then the output will be this:
{ Name = A, Surname = B }
- 1
{ Name = B, Surname = B }
- 3
- 4
{ Name = C, Surname = B }
- 5
DotnetFiddle link
If the key is not needed in your function, you can omit that in the method. A IGrouping<TKey, TElement> is an IEnumerable<TElement> so you could just define it as:
private void DoSomethingWithGroup(IEnumerable<MyObj> items)
{
///...
}
I have 2 lists with objects. I want to compare them and return all the NEW objects in new list
I try below code, but I don't get answered
var inInsyt = (from prd in db.COM_CUSTOMER_PRODUCT
join inv in db.INS_INVENTORY on prd.COM_CUSTOMER_PRODUCT_ID
equals inv.COM_PRODUCT_ID
where prd.COM_CUSTOMER_ID == 5252
select new ProductInventoryInfo
{
sku = prd.PRODUCT_CODE,
quantity = inv.INV_AVAILABLE
}).ToList();
var inEComSite = (from qlInv in db.INS_OPENCART_QOOLMART_INVENTORY
where qlInv.ID>0
select new ProductInventoryInfo
{
sku = qlInv.SKU,
quantity = qlInv.QUANTITY
}).ToList();
---------1st method----------------------------------------------------------------------------
var firstNotSecond = inInsyt.Except(inEComSite).ToList();
var secondNotFirst = inEComSite.Except(inInsyt).ToList();
--------------------2nd method-----------------------------------------------------------
List<ProductInventoryInfo> objectList3 = inEComSite.Where(o => inInsyt.Contains(o)).ToList();
List<ProductInventoryInfo> objectList4 = inInsyt.Where(o => !inEComSite.Contains(o)).ToList();
You should implement an IEqualityComparer for your ProductInventoryInfo class.
Take a look here
If you have two lists of strings or numbers, you can use the following methods to compare
Main Method
var list1 = new List<string>
{
"Product 1",
"Product 2",
"Product 3",
"Product 4"
};
var list2 = new List<string>
{
"Product 2",
};
var list3 = list1.Where(i => list2.All(x => x != i)).ToList();
var list4 = list1.Except(list2).ToList();
If two lists are objecs you can use the following methods to compare
class ProductInventoryInfo
public class ProductInventoryInfo
{
public string ProductName { get; set; }
public override bool Equals(object obj)
{
if (!(obj is ProductInventoryInfo))
{
return false;
}
var other = (ProductInventoryInfo)obj;
return this.ProductName == other.ProductName;
}
protected bool Equals(ProductInventoryInfo other)
{
return ProductName == other.ProductName;
}
public override int GetHashCode()
{
return (ProductName != null ? ProductName.GetHashCode() : 0);
}
}
Extensions Method to compare objects
public static class ExtensionsMethod
{
public static bool ObjectsAreEqual(this IEnumerable<ProductInventoryInfo> items, ProductInventoryInfo obj2)
{
return items.Any(productInventoryInfo => ObjectsAreEqual<ProductInventoryInfo>(productInventoryInfo, obj2));
}
private static bool ObjectsAreEqual<T>(T obj1, T obj2)
{
return JsonConvert.SerializeObject(obj1) == JsonConvert.SerializeObject(obj2);//convert object to json by Newtonsoft.Json and compare that
}
}
Main Method
var list1 = new List<ProductInventoryInfo>
{
new ProductInventoryInfo{ ProductName="Product 1"},
new ProductInventoryInfo{ ProductName="Product 2"},
new ProductInventoryInfo{ ProductName="Product 3"},
new ProductInventoryInfo{ ProductName="Product 4"},
};
var list2 = new List<ProductInventoryInfo>
{
new ProductInventoryInfo{ ProductName="Product 2"},
};
var list3 = list1.Where(x => !list2.ObjectsAreEqual(x)).ToList(); //use Extensions Method
//use override Equals
var list4 = new List<ProductInventoryInfo>();
list1.ForEach(x =>
{
list2.ForEach(y =>
{
if (!x.Equals(y))
{
list4.Add(x);
}
});
});
See the below snippet:
List<int> firstList = new List<int>() { 1,2,2,3,3 };
List<int> secondList = new List<int>() { 1 };
List<int> newList = new List<int>();
var firstNotSecond = firstList.Except(secondList).ToList();
var secondNotFirst = secondList.Except(firstList).ToList();
newList.AddRange(firstNotSecond);
newList.AddRange(secondNotFirst);
output newList: {2,3}
This works perfectly if you add objects to lists from same type
var differences = list2.Where(l2 =>
!list1.Any(l1 => l1.sku == l2.sku && l1.quantity == l2.quantity ));
Or(If you prefer)
var differences = list2.Where(l2 =>
!list1.Any(l1 => l1.sku == l2.sku || l1.quantity == l2.quantity ));
I am wondering if/how I can do the following thing using LINQ: I have a list of objects with some properties and another list of different distinct values corresponding to a certain property.
Example:
A = [{id=1, property=1}, {id=2, property=1}, {id=3, property=2}]
B = [1, 2]
Is there a way of achieving the following thing (obtaining the counts list) using only LINQ?
var counts = new List<int>();
foreach (var b in B)
{
counts.Add(A.Where(a => a.property == b).Count();
}
Sample code:
public class MyObject
{
public MyObject(int id, int prop)
{
Id = id;
Property = prop;
}
public int Id { get; set; }
public int Property { get; set; }
public void test()
{
var A = new List<MyObject>
{
new MyObject(1, 1), new MyObject(2, 1), new MyObject(3, 2)
};
var B = new List<int>{1, 2};
// LINQ magic
// 2 objects with property 1
// 1 object with property 2
}
}
Yes, use select operators to only select the specific properties you want to compare, and then use intersect and count to get the count. Example:
var listOfObjects = new List<PocoClass>()
{
new PocoClass(){Id=1,Property=3},
new PocoClass(){Id=2,Property=2}
};
var intArray = new int[] { 1, 2, 3 };
var count = listOfObjects.Select(o => o.Property).Intersect(intArray).Count();
Sure, you can just loop through the values and, for each one, get the count of items that have Property == value.
In the sample below, I'm selecting an anonymous type that contains the Value and the Count of each item that has Property == value:
public class Data
{
public int Id { get; set; }
public int Property { get; set; }
}
public class Program
{
private static void Main()
{
var allData = new List<Data>
{
new Data {Id = 1, Property = 1},
new Data {Id = 2, Property = 1},
new Data {Id = 3, Property = 2},
};
var values = new[] {1, 2};
var results = values.Select(value =>
new {Value = value, Count = allData.Count(item => item.Property == value)});
foreach (var result in results)
{
Console.WriteLine($"{result.Count} objects with Property {result.Value}");
}
}
}
Output
You can use Count method with a predicate:
var A = new[] {new {id = 1, property = 1}, new {id = 2, property = 1}, new {id = 3, property = 2}};
var B = new[] {1, 2};
var count = B.Count(b => A.Any(a => a.property == b));
Code above will check every member in B and if at least one member in A have a property with that value it will be counted
convert B to List and run ForEach on it
B.OfType<int>().ToList().ForEach(m=>{
counts.Add(A.Where(a => a.property == m).Count();
})
This is the gist of what you need. I'm typing this without a c# compiler, so hopefully this doesn't have errors.
var results =
from a in A
join b in B on a.property equals b
group a by a.property into g
select new { Property = g.Key, Count = g.Count() }
Here is my model for Product
public class Product
{
public string Name{ get; set; }
public int ProductNumber{ get; set; }
public List<Product> ProductList { get; set; }
}
//// below is the structure of the list
IList<Product> rootList = new List<Product>
{
new Product
{
ProductNumber = 1, Name = "A",
ProductList = new List<Product> { new Product { ProductNumber = 2, Name = "A1",
ProductList = new List<Product> { new Product { ProductNumber = 3, Name = "A2", ProductList = new List<Product>()} }}
}
},
new Product
{
ProductNumber = 4, Name = "B",
ProductList = new List<Product> { new Product { ProductNumber = 5, Name = "B1",
ProductList = new List<Product> { new Product { ProductNumber = 6, Name = "B2", ProductList = new List<Product>()} }}
}
},
new Product
{
ProductNumber = 7, Name = "C",
ProductList = new List<Product> { new Product { ProductNumber = 8, Name = "C1",
ProductList = new List<Product> { new Product { ProductNumber = 9, Name = "C2", ProductList = new List<Product>()} }}
}
}
};
I need to filter the above list which contain the ProductNumber less than 5, ie. the output is expected to be a list of Product which has product numbers less than 5.
is there any extensions available? Please help.
Here my expected result
Product
{
ProductNumber : 1,
Name : "A",
ProductList : { {
ProductNumber : 2,
Name : "A1",
ProductList :{ {
ProductNumber = 3,
Name : "A2",
ProductList : null} }}
}
},
Product
{
ProductNumber : 4,
Name : "B"
ProductList : null
}
It's fairly easy to knock together a "flatten-this-tree" LINQ-like function
public static IEnumerable<T> Flatten<T>(
this IEnumerable<T> source,
Func<T, IEnumerable<T>> childSelector)
{
HashSet<T> added = new HashSet<T>();
Queue<T> queue = new Queue<T>();
foreach(T t in source)
if (added.Add(t))
queue.Enqueue(t);
while (queue.Count > 0)
{
T current = queue.Dequeue();
yield return current;
if (current != null)
{
IEnumerable<T> children = childSelector(current);
if (children != null)
foreach(T t in childSelector(current))
if (added.Add(t))
queue.Enqueue(t);
}
}
}
which you can then use in regular LINQ e.g.
var lessThanFive = rootList
.Flatten(p => p.ProductList)
.Where(p => p.ProductNumber < 5)
.ToList();
EDIT: From your edit I can see that this isn't quite what you wanted. (You don't want a list of products, you want a tree of products...) I'm going to leave this here as I quite like it as a solution to what I thought your problem was, but I'll have a think about your new problem too...
EDIT: If you don't mind modifying your original objects, you can use this as follows:
rootList = rootList.Where(p => p.ProductNumber < 5).ToList();
foreach (var pr in rootList.Flatten(p => p.ProductList))
pr.ProductList = pr.ProductList.Where(p => p.ProductNumber < 5).ToList();
You need something like this:
public static class EnumerableExtensions
{
public static IEnumerable<TR> Recur<T, TR>(
this IEnumerable<T> source,
Func<T, bool> filter,
Func<T, IEnumerable<T>> recursor,
Func<T, IEnumerable<T>, TR> resultor)
{
foreach(var t in source)
if (filter(t))
yield return resultor(t, recursor(t));
}
}
which you'll call like this:
var q = rootList.Recur(
p => p.ProductNumber < 5,
p => p.ProductList,
(p, cs) => new
{
p.ProductNumber,
p.Name,
ProductList = cs
});
Assuming
public class MyClass
{
public int ID {get; set; }
public string Name {get; set; }
}
and
List<MyClass> classList = //populate with MyClass instances of various IDs
I can do
List<MyClass> result = classList.FindAll(class => class.ID == 123);
and that will give me a list of just classes with ID = 123. Works great, looks elegant.
Now, if I had
List<List<MyClass>> listOfClassLists = //populate with Lists of MyClass instances
How do I get a filtered list where the lists themselves are filtered. I tried
List<List<MyClass>> result = listOfClassLists.FindAll
(list => list.FindAll(class => class.ID == 123).Count > 0);
it looks elegant, but doesn't work. It only includes Lists of classes where at least one class has an ID of 123, but it includes ALL MyClass instances in that list, not just the ones that match.
I ended up having to do
List<List<MyClass>> result = Results(listOfClassLists, 123);
private List<List<MyClass>> Results(List<List<MyClass>> myListOfLists, int id)
{
List<List<MyClass>> results = new List<List<MyClass>>();
foreach (List<MyClass> myClassList in myListOfLists)
{
List<MyClass> subList = myClassList.FindAll(myClass => myClass.ID == id);
if (subList.Count > 0)
results.Add(subList);
}
return results;
}
which gets the job done, but isn't that elegant. Just looking for better ways to do a FindAll on a List of Lists.
Ken
listOfClasses.SelectMany(x=>x).FindAll( /* yadda */)
Sorry about that, FindAll is a method of List<T>.
This
var result = from x in listOfClasses from y in x where SomeCondition(y) select y;
or
var result = listOfClasses.SelectMany(x=>x).Where(x=>SomeCondition(x));
To keep a list of lists, you could do something like this example:
MyClass a = new MyClass() { ID = 123, Name = "Apple" };
MyClass b = new MyClass() { ID = 456, Name = "Banana" };
MyClass c = new MyClass() { ID = 789, Name = "Cherry" };
MyClass d = new MyClass() { ID = 123, Name = "Alpha" };
MyClass e = new MyClass() { ID = 456, Name = "Bravo" };
List<List<MyClass>> lists = new List<List<MyClass>>()
{
new List<MyClass>() { a, b, c },
new List<MyClass>() { d, e },
new List<MyClass>() { b, c, e}
};
var query = lists
.Select(list => list.Where(item => item.ID == 123).ToList())
.Where(list => list.Count > 0).ToList();
query would be List<List<MyClass>> holding lists of MyClass objects that passed the test. At first glance, it looks out of order with the Where extension coming after the Select, but the transformation of the inner lists needs to occur first, and that's what's happening in the Select extension. Then it is filtered by the Where.
I would probably go with this
List<List<string>> stuff = new List<List<string>>();
List<List<string>> results = new List<List<string>>();
stuff.ForEach(list=> {var result = list.FindAll(i => i == "fun").ToList();
if (result.Count > 0) results.Add(result);
});
List<string> flatResult = new List<string>();
stuff.ForEach(List => flatResult.AddRange(List.FindAll(i => i == "fun")));
That way you can go with a jagged array or flatten it out.. But the Linq way works well too :-).
While producing a flat List<MyClass> will answer your need most of the time, the exact answer to your question is:
var result = (from list in ListOfClassLists
let listWithTheId=
(
(from myClass in list
where myClass.ID == id
select myClass)
.ToList()
)
where listWithTheId.Count > 0
select listWithTheId
).ToList();
This code snippet was taken from my Proof of Concept:
using System.Collections.Generic;
using System.Linq;
namespace ListOfListSelectionSpike
{
public class ListSpikeClass
{
public List<List<MyClass>> ListOfClassLists { get; set; }
private List<MyClass> list1, list2, list3;
public ListSpikeClass()
{
var myClassWithId123 = new MyClass("123");
var myClassWithIs345 = new MyClass("456");
list1 = new List<MyClass> { myClassWithId123, myClassWithIs345 };
list2 = new List<MyClass> { myClassWithId123, myClassWithIs345, myClassWithId123 };
list3 = new List<MyClass> { myClassWithIs345, myClassWithIs345 };
ListOfClassLists = new List<List<MyClass>> { list1, list2, list3 };
}
public List<List<MyClass>> GetListOfListsById(string id)
{
var result = (from list in ListOfClassLists
let listWithTheId =
((from myClass in list
where myClass.ID == id
select myClass)
.ToList())
where listWithTheId.Count > 0
select listWithTheId)
.ToList();
return result;
}
}
public class MyClass
{
public MyClass(string id)
{
ID = id;
Name = "My ID=" + id;
}
public string ID { get; set; }
public string Name { get; set; }
}
}