How can I use Lambda syntax to remove duplicates? [duplicate] - c#

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

Combine two list elements into single element

I have a list with two elements
element 1:
no:1,
vendor: a,
Description: Nice,
price :10
element 2:
no:1
vendor:a,
Description: Nice,
price:20
i have lot more fields in list elements so i cannot use new to sum the price
if everything is same except price i need to combine two elements into a single element by summing price.
o/p element 1:
no:1,
vendor:a,
Description:Nice,
price:30
Tried below one but not sure how to sum the price and return the entire fields with out using new
list.GroupBy(y => new { y.Description,y.vendor, y.no})
.Select(x => x.ToList().OrderBy(t => t.Price)).FirstOrDefault()
If you prefer LINQ query expressions:
var groupedElements = from element in elements
group element by new
{
element.no,
element.Description,
element.vendor
}
into grouped
select new {grouped, TotalPrice = grouped.Sum(x => x.price)};
The total price is calculated with the final .Sum method call on the grouped elements.
Try following :
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
List<Element> totals = elements.GroupBy(x => x.no).Select(x => new Element()
{
no = x.Key,
vendor = x.FirstOrDefault().vendor,
Description = x.FirstOrDefault().Description,
price = x.Sum(y => y.price)
}).ToList();
}
}
public class Element
{
public int no { get;set; }
public string vendor { get;set; }
public string Description { get;set; }
public decimal price { get;set; }
}
Try following using Clone
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
var groups = elements.GroupBy(x => x.no).ToList();
List<Element> totals = new List<Element>();
foreach (var group in groups)
{
Element newElement = (Element)group.FirstOrDefault().Clone();
newElement.price = group.Sum(x => x.price);
totals.Add(newElement);
}
}
}
public class Element : ICloneable
{
public int no { get;set; }
public string vendor { get;set; }
public string Description { get;set; }
public decimal price { get;set; }
public object Clone()
{
return this;
}
}
}
Willy-nilly you have to create Key which has 3 properties;
If you don't like the current solution with anonymous class
list
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
...
You can do it in different way, e.g. with a help of unnamed tuple:
list
.GroupBy(y => Tuple.Create(
y.Description,
y.vendor,
y.no)
)
...
Or named tuple (see https://learn.microsoft.com/en-us/dotnet/csharp/tuples for details):
list
.GroupBy(y => (
Description : y.Description,
vendor : y.vendor,
no : y.no)
)
...
Or even tailored class. What's matter the most, however, is that you can't just get First item from the group
but should create a new instance. Another issue is premature materialization: .ToList() when you then get rid of this new born list and keep on querying with .OrderBy(...)
var result = result
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
.Select(group => MyObject() { //TODO: put the right syntax here
Description = group.Key.Description,
vendor = group.Key.vendor,
no = group.Key.no,
price = group.Sum(item => item.price) // you want to sum prices, right?
});
You need to create a custom IEqualityComparer, which when passed into the GroupBy clause, will group the items according to your needs.
Asuming the following sample class:
public class Element
{
public int no { get; set; }
public string vendor { get; set; }
public string Description { get; set; }
public decimal price { get; set; }
}
You can implement the following IEqualityComparer which using Reflection will compare every Propertypresent in the Element class except the ones defined in the Linq Where clause, in this case "price". Bear in mind further customizations could be required.
public class ElementComparer : IEqualityComparer<Element>
{
public bool Equals(Element a, Element b) => typeof(Element).GetProperties()
.Where(p => p.Name != "price")
.All(p => p.GetValue(a).Equals(p.GetValue(b)));
public int GetHashCode(Element obj) => obj.no.GetHashCode();
}
Then simply group them this way
list.GroupBy(x => x, new ElementComparer()).Select(g =>
{
// Here you need to either clone the first element of the group like
// #jdweng did, or create a new instance of Element like I'm doing below
Element element = new Element();
foreach (var prop in element.GetType().GetProperties())
{
if (prop.Name == "price")
{
prop.SetValue(element, g.Sum(y => y.price));
}
else
{
prop.SetValue(element, prop.GetValue(g.First()));
}
}
return element;
});
I think what you're trying to do is write dynamic code that groups by all properties except for the property you want to sum. This solution should work, though I loath to use reflection. A more performant method would be to use expression trees to generate an aggregation delegate that you reuse, but that is very involved. This should do the trick:
Edit: There's another answer that also seems to work. Mine assumes you will want to do this with any collection regardless of type. Doesn't require ICloneable or a type-specific IEqualityComparer<T>, though, as a slight trade-off, the other one will likely perform better in very large datasets.
static T[] GetGroupSums<T>(IEnumerable<T> collection, string sumPropertyName) where T : new()
{
//get the PropertyInfo you want to sum
//var sumProp = (PropertyInfo)((MemberExpression)((UnaryExpression)memberExpression.Body).Operand).Member;
var sumProp = typeof(T).GetProperty(sumPropertyName);
//get all PropertyInfos that are not the property to sum
var groupProps = typeof(T).GetProperties().Where(x => x != sumProp).ToArray();
//group them by a hash of non-summed properties (I got this hash method off StackExchange many years back)
var groups = collection
.GroupBy(x => GetHash(groupProps.Select(pi => pi.GetValue(x)).ToArray()))
.Select(items =>
{
var item = new T();
var firstItem = items.First();
//clone the first item
foreach (var gp in groupProps)
{
gp.SetValue(item, gp.GetValue(firstItem));
}
//Get a decimal sum and then convert back to the sum property type
var sum = items.Sum(_item => (decimal)Convert.ChangeType(sumProp.GetValue(_item), typeof(decimal)));
sumProp.SetValue(item, Convert.ChangeType(sum, sumProp.PropertyType));
//If it will always be int, just do this
//var sum = items.Sum(_item => (int)sumProp.GetValue(_item));
//sumProp.SetValue(item, sum);
return item;
});
return groups.ToArray();
}
//I got this hash method off StackExchange many years back
public static int GetHash(params object[] args)
{
unchecked
{
int hash = 17;
foreach (object arg in args)
{
hash = hash * 23 + arg.GetHashCode();
}
return hash;
}
}
Use it like this:
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 2, vendor = "a", Description = "Nice", price = 15},
new Element() { no = 2, vendor = "b", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
var groups = GetGroupSums(elements, nameof(Element.price));

How to remove value that does not exist in another list?

Suppose I have two list master and update, now the master list contains all records available in a table, and the update list contains only some record of master that need to be updated.
I want exclude from the master list all the record that's not contained from the update list, so I tried:
master.RemoveAll(c => update.Any(x => x.Id != c.Id));
this will return 0.
The record are:
list_name | id
master 1
master 2
master 3
master 4
master 5
update 3
update 4
update 5
at the end the master list should contains only the records: 3, 4, 5.
What I did wrong?
First, let's fix your code - you remove when any item in the update matches master, so != should be ==:
master.RemoveAll(c => !update.Any(x => x.Id == c.Id));
That's all you need for a list of, say, 1000 items or so. If the list is, say, 10,000 items, this could become slow due to O(n2) nature of the above algorithm. You can put IDs of update into a HashSet, and use Contains for a potential speed-up:
var updateIds = new HashSet<int>(update.Select(u => u.Id));
master.RemoveAll(m => !updateId.Contains(m.Id));
You're looking for:
master.RemoveAll(c => !update.Any(x => x.Id == c.Id));
What you are looking for is the method Intersect (with a twist). You need an equality comparer.
Intersect: Produces the set intersection of two sequences by using the default equality comparer to compare values.
First solution: Override Equals
Try it Online!
public class Item
{
public int Id {get;set;}
public override bool Equals(object obj)
{
var item = obj as Item;
return item == null ? false : this.Id.Equals(item.Id);
}
public override int GetHashCode() => Id.GetHashCode();
}
public static void Main()
{
var master = (new []{ 1, 2, 3, 4, 5}).Select(x => new Item {Id = x});
var update = (new []{ 1, 3, 5}).Select(x => new Item {Id = x});
// yes all you need is here
master = master.Intersect(update);
foreach (var item in master)
Console.WriteLine(item.Id);
}
output
1
3
5
Second solution: Create a custom comparer
Try it Online!
public class Item
{
public int Id { get; set; }
}
public static void Main()
{
// example
var master = (new []{ 1, 2, 3, 4, 5}).Select(x => new Item {Id = x});
var update = (new []{ 1, 3, 5}).Select(x => new Item {Id = x});
// everything happens here.
var master = master.Intersect(update, new KeyEqualityComparer<Item>(s => s.Id));
foreach (var item in master)
Console.WriteLine(item.Id);
}
// Interset doest not know how to compare by property. This will help it.
public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, object> keyExtractor;
public KeyEqualityComparer(Func<T, object> keyExtractor) => keyExtractor = keyExtractor;
public bool Equals(T x, T y) => keyExtractor(x).Equals(this.keyExtractor(y));
public int GetHashCode(T obj) => keyExtractor(obj).GetHashCode();
}
output
1
3
5
You can use Join to simulate an intersect:
Try it Online!
public class Item
{
public int Id { get; set; }
}
public static void Main()
{
var master = (new []{ 1, 2, 3, 4, 5}).Select(x => new Item {Id = x});
var update = (new []{ 1, 3, 5}).Select(x => new Item {Id = x});
// we need to get all update's ids.
master = master.Join(update.Select(x => x.Id), o => o.Id, id => id, (o, id) => o);
foreach (var item in master)
{
Console.WriteLine(item.Id);
}
}
output
1
3
5
Try This
var listMaster = new List<Master>();
var listUpdate = new List<Update>();
listMaster.Add(new Master { ID = 1, Name = "Jai" });
listMaster.Add(new Master { ID = 2, Name = "Ram" });
listMaster.Add(new Master { ID = 3, Name = "Amit" });
listMaster.Add(new Master { ID = 4, Name = "Mohan" });
listMaster.Add(new Master { ID = 5, Name = "JAg" });
listUpdate.Add(new Update { ID = 1, Name = "JaiU" });
listUpdate.Add(new Update { ID = 2, Name = "RamU" });
listUpdate.Add(new Update { ID = 3, Name = "ShyamU" });
listMaster.RemoveAll(c => !listUpdate.Any(x => x.ID == c.ID));

Hierarchical data sorting C#

I have a small hierarchy. Example:
entity:
public class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
public int ParentID { get; set; }
}
My hierarchy data look like:
Id = 1 Name = Item1 ParentId = NULL
Id = 2 Name = Item2 ParentId = 1
Id = 3 Name = Item3 ParentId = 2
Id = 4 Name = Item4 ParentId = 2
Id = 5 Name = Item5 ParentId = 3
The problem is I need to sort it that child nodes must be after its immediate parent. The example bellow must look like
Id = 1 Name = Item1 ParentId = NULL
Id = 2 Name = Item2 ParentId = 1
Id = 3 Name = Item3 ParentId = 2
// the elements with parentID = 3
Id = 5 Name = Item5 ParentId = 3
//continue
Id = 4 Name = Item4 ParentId = 2
Any adwices?
Assuming you have a _list of MyClass objects, then sort it first on Name field, then on ParentId field, like shown below using LINQ:
_list.OrderBy(L=>L.Name).ThenBy(L=>L.ParentId);
Hope this may help.
Try this
I assume that 1st you want to order by parentid and in each parent you want to sort by id.
myClassList.OrderBy(parent=>parent.ParentId).ThenBy(parent=>parent.Id);
Try this recursive code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MyClass.data = new List<MyClass>() {
new MyClass() { ID = 1, Name = "Item1", ParentID = null},
new MyClass() { ID = 2, Name = "Item2", ParentID = 1 },
new MyClass() { ID = 3, Name = "Item3", ParentID = 2 },
new MyClass() { ID = 4, Name = "Item4", ParentID = 2 },
new MyClass() { ID = 5, Name = "Item5", ParentID = 3 }
};
MyClass myClass = new MyClass();
myClass.GetData(null, 0);
Console.ReadLine();
}
}
public class MyClass
{
public static List<MyClass> data = null;
public int ID { get; set; }
public string Name { get; set; }
public int? ParentID { get; set; }
public void GetData(int? id, int level)
{
List<MyClass> children = data.Where(x => x.ParentID == id).ToList();
foreach (MyClass child in children)
{
Console.WriteLine(" {0} ID : {1}, Name : {2}, Parent ID : {3}", new string(' ',4 * level),child.ID, child.Name, child.ParentID);
GetData(child.ID, level + 1);
}
}
}
}
Here you have a way to do it. As you can see, I overrode the ToString method and added a few more cases.
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public override string ToString()
{
return string.Format("{0}: {1} - {2}", Id, Name, ParentId);
}
}
class Program
{
static void Main(string[] args)
{
List<MyClass> list = new List<MyClass>();
list.Add(new MyClass { Id = 1, Name = "Item1", ParentId = null });
list.Add(new MyClass { Id = 2, Name = "Item2", ParentId = 1 });
list.Add(new MyClass { Id = 3, Name = "Item3", ParentId = 2 });
list.Add(new MyClass { Id = 4, Name = "Item4", ParentId = 2 });
list.Add(new MyClass { Id = 5, Name = "Item5", ParentId = 3 });
list.Add(new MyClass { Id = 6, Name = "Item6", ParentId = 1 });
list.Add(new MyClass { Id = 7, Name = "Item7", ParentId = null });
list.Add(new MyClass { Id = 8, Name = "Item8", ParentId = 2 });
list.Add(new MyClass { Id = 9, Name = "Item9", ParentId = 6 });
list.Add(new MyClass { Id = 10, Name = "Item10", ParentId = 7 });
foreach(var item in list.Where(x => !x.ParentId.HasValue).OrderBy(x => x.Id))
ProcessItem(item, list, 0);
Console.ReadKey();
}
private static void ProcessItem(MyClass item, List<MyClass> list, int level)
{
Console.WriteLine("{0}{1}", new string(' ', level * 2), item.ToString());
foreach (var subitem in list.Where(x => x.ParentId == item.Id).OrderBy(x => x.Id))
ProcessItem(subitem, list, level + 1);
}
}
Would something like this work for you?
If you need an actual ordered list, try this:
foreach (var item in OrderList(list))
Console.WriteLine(item.ToString());
(...)
private static List<MyClass> OrderList(List<MyClass> list)
{
List<MyClass> orderedList = new List<MyClass>(list.Count());
foreach (var item in list.Where(x => !x.ParentId.HasValue).OrderBy(x => x.Id))
AddItem(item, list, orderedList);
return orderedList;
}
private static void AddItem(MyClass item, List<MyClass> list, List<MyClass> orderedList)
{
orderedList.Add(item);
foreach (var subitem in list.Where(x => x.ParentId == item.Id).OrderBy(x => x.Id))
AddItem(subitem, list, orderedList);
}
The following should do the trick (and show some better performance because we save the hierarchy in a lookup, instead of searching the IEnumerable on the fly):
public List<MyClass> SortHierarchically(IEnumerable<MyClass> myClasses)
{
if(myClasses == null)
return new List<MyClass>();
var myClassesByParentId = myClasses.ToLookup(mc => mc.ParentId);
var result = new List<MyClass>(myClasses.Count());
int? currentParentId = null;
MyClass currentItem = myClassesByParentId[currentParentId].Single();
result.Add(currentItem);
currentParentId = currentItem.Id;
if(myClassesByParentId.Contains(currentParentId))
result.AddRange(myClassesByParentId[currentParentId].SelectMany(mc => GetAllSortedChildren(mc, myClassesByParentId)));
return result;
}
public List<MyClass> GetAllSortedChildren(MyClass parent, ILookup<int?, MyClass> myClassesByParentId)
{
var result = new List<MyClass>() { parent };
if(myClassesByParentId.Contains(parent.Id))
retsult.AddRange(myClassesByParentId[parent.Id].SelectMany(mc => GetAllSortedChildren(mc, myClassesByParentId)));
return result;
}
It would be interesting to find a method of sorting this by standard LINQ, with some clever comparer or such.
One of the answers above works well. This is a generic version.
public static class SortingMethods
{
public static IList<T> OrderByHierarchical<T>(
this IEnumerable<T> items,
Func<T, string> getId,
Func<T, string> getParentId)
{
if (items == null)
return new List<T>();
var itemsByParentId = items.ToLookup(item => getParentId(item));
var result = new List<T>(items.Count());
var currentParentId = "";
var currentItem = itemsByParentId[currentParentId].Single();
result.Add(currentItem);
currentParentId = getId(currentItem);
if (itemsByParentId.Contains(currentParentId))
result.AddRange(itemsByParentId[currentParentId].SelectMany(item => GetAllSortedChildren(item, itemsByParentId, getId)));
return result;
}
private static IList<T> GetAllSortedChildren<T>(T parent, ILookup<string, T> itemsByParentId, Func<T, string> getId)
{
var result = new List<T>() { parent };
if (itemsByParentId.Contains(getId(parent)))
{
result.AddRange(itemsByParentId[getId(parent)].SelectMany(item => GetAllSortedChildren(item, itemsByParentId, getId)));
}
return result;
}
}

Need help querying through 3 classes

I have 3 classes that are defined like this:
class Customer
{
public string Name;
public string City;
public Order[] Orders;
}
class Order
{
public int Quantity;
public Product Product;
}
class Product
{
public string ProdName;
public decimal Price;
}
And I want to use LINQ in C# to print out the names that bought a specific product which in this case is 'ProdName'. I can't find a solution in order to go through all these 3 classes that could give me the name based on the product name.
I have tried something like this but it seems it doesn;t work:
var query = from c in customers where c.Order[0].Product.ProdName.Contains("Milk")
select c.Name;
foreach(var item in query)
{
Console.WriteLine(item);
}
This is how I set up the values for each class:
static public List<Customer> GetCustomerList()
{
var customerList = new List<Customer>
{
new Customer {Name = "Johny", City = "London", Orders = new Order[3] },
new Customer {Name = "Morgan", City = "Copenhagen", Orders = new Order[4]},
new Customer {Name = "Rasmus", City = "Amsterdam", Orders = new Order[1] }
};
return customerList;
}
static public List<Order> GetOrderList()
{
var orderList = new List<Order>
{
new Order { Quantity = 10, Product = new Product()},
new Order { Quantity = 5, Product = new Product()},
new Order { Quantity = 2, Product = new Product()}
};
return orderList;
}
static public List<Product> GetProductList()
{
var productList = new List<Product>
{
new Product { Name = "Cookie, bread", Price = 50 },
new Product { Name = "Cookie, Bread, Milk", Price = 85},
new Product { Name = "bags", Price = 38}
};
return productList;
}
static void Main(string[] args)
{
List<Customer> customers = GetCustomerList();
List<Order> orders = GetOrderList();
List<Product> products = GetProductList();
}
How can I linq all 3 classes together in order to get right result? any hints, please?
You need to build real, related, test data. Some usable fake data setup might look like:
// Create a single instance of each Product that could be used
var egg = new Product { Name = "Eggs", Price = 2.0 };
var bread = new Product { Name = "Bread", Price = 3.0 };
var fooBars = new Product { Name = "FooBars", Price = 2.5 };
var customerList = new List<Customer>
{
new Customer { Name = "Johny", City = "London", Orders = new List<Order>
{
new Order { Quantity = 3, Product = bread },
new Order { Quantity = 1, Product = egg },
new Order { Quantity = 2, Product = fooBars }
}},
new Customer { Name = "Morgan", City = "Copenhagen", Orders = new List<Order>
{
new Order { Quantity = 30, Product = bread }
}},
new Customer { Name = "Rasmus", City = "Amsterdam", Orders = new List<Order>
{
new Order { Quantity = 12, Product = fooBars }
}}
};
Please note that I used List<Order> instead of Order[], but you could switch it back. I also opted for a Name property in Product as you showed in your example code, but which doesn't match your class definition.
Now you can query. Let's see who bought bread:
var whoBoughtBread = customerList
.Where(c => c.Orders.Any(o => o.Product == bread))
.Select(c => c.Name);
Or
var whoBoughtBread2 = customerList
.Where(c => c.Orders.Any(o => o.Product.Name == "Bread"))
.Select(c => c.Name);
With the data structure you have one query would be:
customerList.Where(c => c.Orders.Any(o => o.Product.ProdName == prodName));
but as mentioned in the comments you have potentials for both a null collection and null values within the collection. I would highly recommend making sure you have non-null collections with non-null elements to avoid having to inject null-checking into your query. Otherwise you'll have to do sometihng like:
customerList.Where(c => c.Orders != null &&
c.Orders.Any(o => o != null &&
o.Product != null &&
o.Product.ProdName == prodName));
With C# 6's null-propagation operator (?.) you can shorten it a little:
customerList.Where(c => c.Orders != null &&
c.Orders.Any(o => o?.Product != null &&
o.Product.ProdName == prodName));

How to filter on a list of keys in a Dictionary using LINQ

I have this dictionary mappings declared as a Dictionary<string, HashSet<string>>.
I also have this method to do stuff on a hashset in the dictionary:
public void DoStuff(string key, int iClassId){
foreach (var classEntry in
from c in mappings[key]
where c.StartsWith(iClassId + "(")
select c)
{
DoStuffWithEntry(classEntry);
}
}
private void DoStuffWithEntry(string classEntry){
// Do stuff with classEntry here
}
In one case, I need to do this on a number of keys in the mappings dictionary, and I was thinking it was better to rewrite and filter on a list of keys instead of calling DoStuff for each key to optimise the execution.
Currently I do this:
DoStuff("key1", 123);
DoStuff("key2", 123);
DoStuff("key4", 123);
DoStuff("key7", 123);
DoStuff("key11", 123);
Logically something like this instead of calling DoStuff for each (FilterOnKeys is not a method - just what I want...):
foreach (var classEntry in
from c in mappings.FilterOnKeys("key1", "key2", "key4", "key7", "key11")
where c.StartsWith(iClassId + "(")
select c)
{
DoStuffWithEntry(classEntry);
}
It sounds like you want:
string[] keys = { "key1", "key2", ... }
var query = from key in keys
from c in mappings[key]
...;
foreach (var entry in query)
{
...
}
(I would personally use a separate variable for the query just for readability - I'm not too keen on the declaration bit of a foreach loop getting huge.)
I'm using LINQ as per your requirement
var temp = eid.Select(i =>
EmployeeList.ContainsKey(i)
? EmployeeList[i]
: null
).Where(i => i != null).ToList();
The Complete C# Source Code is
public class Person
{
public int EmpID { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public string Gender { get; set; }
}
void Main()
{
Dictionary<int, Person> EmployeeList = new Dictionary<int, Person>();
EmployeeList.Add(1, new Person() {EmpID = 1, Name = "Peter", Department = "Development",Gender = "Male"});
EmployeeList.Add(2, new Person() {EmpID = 2, Name = "Emma Watson", Department = "Development",Gender = "Female"});
EmployeeList.Add(3, new Person() {EmpID = 3, Name = "Raj", Department = "Development",Gender = "Male"});
EmployeeList.Add(4, new Person() {EmpID = 4, Name = "Kaliya", Department = "Development",Gender = "Male"});
EmployeeList.Add(5, new Person() {EmpID = 5, Name = "Keerthi", Department = "Development",Gender = "Female"});
List<int> eid = new List<int>() { 1,3 };
List<Person> SelectedEmployeeList = new List<Person>();
var temp = eid.Select(i =>
EmployeeList.ContainsKey(i)
? EmployeeList[i]
: null
).Where(i => i != null).ToList();
}
You can make use something like this
var ids = {1, 2, 3};
var query = from item in context.items
where ids.Contains(item.id )
select item;
in your case
string[] keys = { "key1", "key2", ... }
var query = from key in keys
where ids.Contains(keys )
select key ;
you could linq your way through mappings
EDITED i missed a nesting level, he wants to query hashsets not the whole dictionary
public void DoStuff(IEnumerable<string> key, int iClassId)
{
mappings.Where(i=>key.Contains(i.Key)).ToList().ForEach(obj=>
{
foreach (var classEntry in
from c in obj.Value
where c.StartsWith(iClassId + "(")
select c)
{
DoStuffWithEntry(classEntry);
}
}
changed key parameter and from c ... section.
you call it like this
string[] keys = new string[]{"key1", "key2", ... , "keyN"};
DoStuff(keys, 123);
this should work

Categories