LINQ inner WHERE - inner collection contains string - c#

Suppose I have the following collection:
ONE
- Banana
- Mango
TWO
- Apple
- Mango
THREE
- Orange
- Pear
I want to get only the collection which has Mango in it, such as:
ONE
- Banana
- Mango
TWO
- Apple
- Mango
The following example still returns a collection with 3 items:
List<Order> list = new List<Order> {
new Order { Id = 1, Name = "ONE", Items = new List<Items> { new Items { Id = 1, Nama = "Banana" }, new Items { Id = 2, Nama = "Mango" } }},
new Order { Id = 1, Name = "TWO", Items = new List<Items> { new Items { Id = 1, Nama = "Orange" }, new Items { Id = 2, Nama = "Mango" } }},
new Order { Id = 1, Name = "THREE", Items = new List<Items> { new Items { Id = 1, Nama = "Pear" }, new Items { Id = 2, Nama = "Chery" } }},
};
var result = list.Where(x => x.Items.Any(y => !y.Nama.Equals("Mango"))).ToList();

You are getting all collections where there is at least one item that is not Mango.
Try removing the "!".
var result = list.Where(x => x.Items.Any(y => y.Nama.Equals("Mango"))).ToList();

You're almost there! Try reading out the logic of your code to make sense of what it's is doing.
Your inner Where clause logic is saying 'Check the List and if Any of the entries does not Equal "Mango", then we keep that list'. With this logic, every entry in your list has a List with an entry that does not equal "Mango".
Reverse your logic so it says 'Check the List and if Any of the entries Equals "Mango", then we keep that list'.
var result = list.Where(x => x.Items.Any(y => y.Nama.Equals("Mango"))).ToList();

Related

Get items from List A where Id is common in both List A and List B and counter of that Id is more than 1 in List A or List B

I was looking to get items from ListA, where the value of Id is same in both of the lists, and the count of Id must be more than 1 in list A or list B
var items = itemsA.Where(x => itemsB.Select(y => y.Id == x.Id).Count() > 1);
This gives me the result where same Ids in itemsB is more then 1, I want to use a or condition to check for the same counter in itemsA
Eg 1:
ListA=[{"id"=1,"name="abc"},{"id=1, "name"="def"}]
ListB=[{"id=2","name="xyz"}, {"id=1, "name"="mno"}]
Should return [{"id"=1,"name="abc"},{"id=1, "name"="def"}] because id =1 exists in listB and the count of id with value 1 in listA is more then 1.
Eg 2:
ListA=[{"id"=2,"name="abc"},{"id=1, "name"="def"}]
ListB=[{"id=1","name="xyz"}, {"id=1, "name"="mno"}]
should return {"id=1, "name"="def"} because common id in both list is 1 and the count of id with value 1 in ListB is more then 1.
I am not certain this is the best solution, but as far as I've understood the question, it should be a solution.
Assuming you have an Item class as follows:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
and define itemsA and itemsB as List<Item>s, you can first find all Ids that are present in both lists, then select the applicable items from itemsA based on occurrence of each Id in either list:
IEnumerable<int> idsInBothItemLists = itemsA
.Select(a => a.Id)
.Intersect(itemsB.Select(b => b.Id))
.Distinct();
List<Item> items = itemsA
.Where(a => idsInBothItemLists.Contains(a.Id))
.GroupBy(a => a.Id)
.Where(gr =>
gr.Skip(1).Any() ||
itemsB.Where(b => b.Id == gr.Key).Skip(1).Any())
.SelectMany(gr => gr.Select(item => item))
.ToList();
(.Skip(1).Any() serves the same purpose as .Count() > 1 in your original code; it simply checks whether there are any items left after skipping the first item.)
Printing the output from the suggested population of itemsA and itemsB
foreach (var entry in items)
{
Console.WriteLine(entry.Id + " " + entry.Name);
}
e.g. for input
var itemsA = new List<Item>
{
new Item { Id = 1, Name = "abc" },
new Item { Id = 3, Name = "def" },
new Item { Id = 1, Name = "ghi" },
new Item { Id = 2, Name = "jkl" }
};
var itemsB = new List<Item>
{
new Item { Id = 2, Name = "xyz" },
new Item { Id = 2, Name = "jkl" },
new Item { Id = 1, Name = "mno" },
new Item { Id = 3, Name = "pqr" }
};
gives
1 abc
1 ghi
2 jkl

add all values to a list of list from a list

I have a list of type class abc
public class abc
{
public int count;
public string country;
}
The list can have values like
Count: 1 - country: US Count: 2 - country: US Count: 3 -
country: IND Count: 4 - country: UK Count: 5 - country: UK
Now I want to put this list into a list of lists where it should be segregated based on countries.
My new list of lists should look like this
Count: 1 - country: US Count: 2 - country: US
Count: 3 - country: IND
Count: 4 - country: UK Count: 5 - country: UK
The count can have any integer and the country can have any string.
Is there any easy way to do this?
You can use GroupBy and select afterwards each group into a separate list:
List<abc> mylist = new List<abc>()
{
new abc{count = 1, country = "US"},
new abc{count = 2, country = "US"},
new abc{count = 3, country = "IND"},
new abc{count = 4, country = "UK"},
new abc{count = 5, country = "UK"},
};
List<List<abc>> result = mylist.GroupBy(x => x.country).Select(y => y.ToList()).ToList();
this way you get a list containing 3 other lists
Implement it like this:
List<abc> list = new List<abc>()
{
new abc() {country = "US", count = 1},
new abc() {country = "US", count = 2},
new abc() {country = "IND", count = 3},
new abc() {country = "UK", count = 4},
new abc() {country = "UK", count = 5}
};
Dictionary<string,List<abc>> dictionary = new Dictionary<string, List<abc>>();
foreach (var item in list)
{
if(!dictionary.TryGetValue(item.country,out var l))
{
l = new List<abc>();
dictionary.Add(item.country,l);
}
l.Add(item);
}
List<List<abc>> result = dictionary.Values.ToList();
you can do like this.
List<abc> ls = new List<abc>();
ls.Add(new abc() { count = 1, country = "US" });
ls.Add(new abc() { count = 2, country = "US" });
ls.Add(new abc() { count = 3, country = "IND" });
ls.Add(new abc() { count = 4, country = "UK" });
ls.Add(new abc() { count = 5, country = "UK" });
List<List<abc>> listOfList = new List<List<abc>>();
foreach (var group in ls.GroupBy(x => x.country))
{
List<abc> list = new List<abc>();
foreach (var item in group)
{
list.Add(new abc() { count = item.count, country = item.country });
}
listOfList.Add(list);
}
LINQ
List<List<abc>> listOfList = new List<List<abc>>();
foreach (var (group, list) in from item in ls.GroupBy(x => x.country)
let temp = new List<abc>()
select (item, temp))
{
foreach (var item2 in group)
{
list.Add(new abc() { count = item2.count, country = item2.country });
}
listOfList.Add(list);
}
Like many already answered, you could group your list by the countryString. However I personally would prefer to add it into a dictionary, so the access would be much easier to understand.
List<abc> myList = new List<abc>()
{
new abc{count = 1, country = "US"},
new abc{count = 2, country = "US"},
new abc{count = 3, country = "IND"},
new abc{count = 4, country = "UK"},
new abc{count = 5, country = "UK"},
};
You could as mentioned just group them:
var groupedLists = myList.GroupBy(x => x.country).Select(y => y.ToList()).ToList();
or you can make a dictionary out of it:
var myDictionary = myList.Select(item => item.country).Distinct().ToDictionary(country => country, country => myList.Where(item => item.country == country).ToList());
Now having the dictionary, you could access the specific list by the key (country). For example:
myDictionary["US"]; //would give you all items with the country "US"
It is up to you to chose whatever you would like to use. Just be aware that if you use the dictionary, you need to handle the possible keyNotFoundException
The easiest way to do this is with Linq.
You can use .GroupBy() to create groupings based on a properties value - in this case the Country.
In this example the .Select() statement uses a named tuple to make the data a bit more readable.
using System.Collections.Generic;
using System.Linq;
var data = new List<Abc>()
{
new()
{
Count = 1,
Country = "UK"
},
new()
{
Count = 2,
Country = "UK"
},
new()
{
Count = 3,
Country = "US"
}
};
var groupedData = data
.GroupBy(x => x.Country)
.Select(x => (Country: x.Key, Data: x.ToList()))
.ToList();
A way to consume and use this list of lists would be like so:
foreach (var (country, groupData) in groupedData)
{
var groupDataString = string.Join(" ", groupData.Select(x => x.Count));
Console.WriteLine($"{country}: {groupDataString}");
}
Example output looks like:
UK: 1 2
US: 3

Add duplicates together in List

First question :)
I have a List<Materiau> (where Materiau implements IComparable<Materiau>), and I would like to remove all duplicates and add them together
(if two Materiau is the same (using the comparator), merge it to the first and remove the second from the list)
A Materiau contains an ID and a quantity, when I merge two Materiau using += or +, it keeps the same ID, and the quantity is added
I cannot control the input of the list.
I would like something like this:
List<Materiau> materiaux = getList().mergeDuplicates();
Thank you for your time :)
Check out Linq! Specifically the GroupBy method.
I don't know how familiar you are with sql, but Linq lets you query collections similarly to how sql works.
It's a bit in depth to explain of you are totally unfamiliar, but Code Project has a wonderful example
To sum it up:
Imagine we have this
List<Product> prodList = new List<Product>
{
new Product
{
ID = 1,
Quantity = 1
},
new Product
{
ID = 2,
Quantity = 2
},
new Product
{
ID = 3,
Quantity = 7
},
new Product
{
ID = 4,
Quantity = 3
}
};
and we wanted to group all the duplicate products, and sum their quantities.
We can do this:
var groupedProducts = prodList.GroupBy(item => item.ID)
and then select the values out of the grouping, with the aggregates as needed
var results = groupedProducts.Select( i => new Product
{
ID = i.Key, // this is what we Grouped By above
Quantity = i.Sum(prod => prod.Quantity) // we want to sum up all the quantities in this grouping
});
and boom! we have a list of aggregated products
Lets say you have a class
class Foo
{
public int Id { get; set; }
public int Value { get; set; }
}
and a bunch of them inside a list
var foocollection = new List<Foo> {
new Foo { Id = 1, Value = 1, },
new Foo { Id = 2, Value = 1, },
new Foo { Id = 2, Value = 1, },
};
then you can group them and build the aggregate on each group
var foogrouped = foocollection
.GroupBy( f => f.Id )
.Select( g => new Foo { Id = g.Key, Value = g.Aggregate( 0, ( a, f ) => a + f.Value ) } )
.ToList();
List<Materiau> distinctList = getList().Distinct(EqualityComparer<Materiau>.Default).ToList();

merging 2 lists into 1 with logic

I have 2 lists of the same type.
List 1:
ID
Name
Value
1,"Prod1", 0
2,"Prod2", 50
3,"Prod3", 0
List 2:
ID
Name
Value
1,"Prod1", 25
2,"Prod2", 100
3,"Prod3", 75
I need to combine these 2 lists into 1, but I only want the values from list2 if the corresponding value from list1 == 0
So my new list should look like this:
1,"Prod1", 25
2,"Prod2", 50
3,"Prod3", 75
I've tried many variations of something like this:
var joined = from l1 in List1.Where(x=>x.Value == "0")
join l2 in List2 on l1.ID equals l2.ID into gj
select new { gj };
I've also tried a variation of the concat
What is the best way of doing this?
You just need to select the individual properties and conditionally select either the Value from the first or second list item.
var List1 = new[]
{
new { Name = "Prod1", Id = 1, Value = 0 },
new { Name = "Prod2", Id = 2, Value = 50 },
new { Name = "Prod3", Id = 3, Value = 0 },
new { Name = "NotInList2", Id = 4, Value = 0}
};
var List2 = new[]
{
new { Name = "Prod1", Id = 1, Value = 25 },
new { Name = "Prod2", Id = 2, Value = 100 },
new { Name = "Prod3", Id = 3, Value = 75 }
};
var results = from l1 in List1
join l2temp in List2 on l1.Id equals l2temp.Id into grpj
from l2 in grpj.DefaultIfEmpty()
select new
{
l1.Id,
l1.Name,
Value = l1.Value == 0 && l2 != null ? l2.Value : l1.Value
};
foreach(var item in results)
Console.WriteLine(item);
Will output
{ Id = 1, Name = Prod1, Value = 25 }
{ Id = 2, Name = Prod2, Value = 50 }
{ Id = 3, Name = Prod3, Value = 75 }
{ Id = 4, Name = NotInList2, Value = 0 }
NOTE: This assumes that you only want all the ids that are in List1 (not any that are only in List2) and that the ids are unique and that the Name from List1 is what you want even if it is different in List2.
clone l1 and
foreach (var item in l1Clone)
if (item.value == 0)
item.value == l2.FirstOrDefault(l2item => l2item.ID == item.ID)
Refer to the code below:
IEnumerable<item> join_lists(IEnumerable<item> list1, IEnumerable<item> list2)
{
var map = list2.ToDictionary(i => i.id);
return list1.Select(i => new item()
{
id = i.id,
name = i.name,
value = i.value == 0 ? map[i.id].value : i.value
});
}
You could use Zip:
var combined = list1
.Zip(list2, (product1, product2) => product1.Value == 0 ? product2 : product1);

Grouping in a generic list

I have a generic list:
List<Test> lstReport = new List<Test>();
lstReport.Add(new Test { ID = 1, Category = "Hot work", Approver = "Praveen" });
lstReport.Add(new Test { ID = 1, Category = "Civil work", Approver = "Praveen" });
lstReport.Add(new Test { ID = 1, Category = "Others", Approver = "Praveen" });
lstReport.Add(new Test { ID = 4, Category = "Hot work", Approver = "Praveen" });
lstReport.Add(new Test { ID = 5, Category = "Critical work", Approver = "Praveen" });
Now I want to take unique rows from the generic list by the ID value.
For eg, I need to group the rows having same ID and if the existence of ID's more than once, then its Category field has to be changed to "Grouped".
How can I write this expression?
This Works:
var QueryResult = (from x in lstReport
group x by x.ID into res
select new Test
{
ID = res.Key,
Category = res.Count() > 1 ? "Grouped" : res.First().Category,
Approver = res.First().Approver
}).ToList();
List<Test> lstReport = new List<Test>();
...
var groups = lstReport.GroupBy(i => i.ID).ToList();
foreach(var group in groups) {
var items = group.ToList();
if(items.Count > 1)
items.ForEach(i => { i.Category = "Grouped"; });
}
You can try this
var q = from r in lstReport
group r by r.ID into g
select new Test{ID=g.Key, Category = g.Count()>1? "Grouped" : g.Min(Category)};
also you can have g.Max(...) or g.Min(...)
You can use Linq to group objects based on some property:
var list = lstReport.GroupBy(t => t.ID);
This will return a new collection with 3 items: IEnumerable<IGrouping<TKey, TSource>>; which basically means a collection containing a collection with one or more items.
Then you can iterate over that new collection and check if each item contains more than one subitem; if it does, then iterate over the subitems and modify the Category value.
http://msdn.microsoft.com/en-us/library/system.linq.enumerable.groupby(v=vs.110).aspx

Categories