Linq group of chains - c#

This is my collection
var list1 = new List<string> { "aa", "bb", "cc", "dd", "ee" }
.Select(x => new Pair { PropName = x, TypeName = x + "Prop1" })
.ToList();
var list2 = new List<string> { "aa", "bb2", "cc", "dd", "ee" }
.Select(x => new Pair { PropName = x, TypeName = x + "Prop2" })
.ToList();
var list3 = new List<string> { "aa", "bb", "cc", "dd" }
.Select(x => new Pair { PropName = x, TypeName = x + "Prop3" })
.ToList();
var list4 = new List<string> { "aa" }
.Select(x => new Pair { PropName = x, TypeName = x + "Prop4" })
.ToList();
var list5 = new List<string> { "aa", "bb", "bb5", "cc5" }
.Select(x => new Pair { PropName = x, TypeName = x + "Prop5" })
.ToList();
var collection = new List<List<Pair>> { list1, list2, list3, list4, list5 };
It represents some hierarcys (i.e. "aa" is father for "bb")
I want to make something like tree with "Pair" objects. It should have structure like on picture. But with Pair objects. And I do not have any Idea how to do that.

Related

How to remove duplicates in List of Class with Linq

I have a list of class :
class GroupAssets
{
public string Name { get; set; }
public List<string> Assets { get; set; }
}
List<GroupAssets> GroupList2 = new List<GroupAssets>{
new GroupAssets { Name="Group1", Assets = new List<string>{ "A","B","C","D" }},
new GroupAssets { Name="Group1", Assets = new List<string>{ "A","B","E","F" }},
new GroupAssets { Name="Group3", Assets = new List<string>{ "A","B","H","G" }},
new GroupAssets { Name="Group4", Assets = new List<string>{ "A","I","C","J" }}
};
I would like to remove the duplicates and have this result :
Group1 => D
Group2 => E,F
Group3 => H,G
Group4 => I,J
Duplicate => A,B,C
Thank you for your help
List<GroupAssets> GroupList = new List<GroupAssets>{
new GroupAssets { Name="Group1", Assets = new List<string>{ "A","B","C","D" }},
new GroupAssets { Name="Group1", Assets = new List<string>{ "A","B","E","F" }},
new GroupAssets { Name="Group3", Assets = new List<string>{ "A","B","H","G" }},
new GroupAssets { Name="Group4", Assets = new List<string>{ "A","I","C","J" }}
};
var assetList = new Dictionary<string,int>();
foreach (var g in GroupList.Select(x=> x.Assets)) {
g.ForEach(x=> {
if (!assetList.ContainsKey(x)) assetList.Add(x,1);
else assetList[x]++;
});
}
var nonUnique = assetList.Where(x=> x.Value > 1).Select(x=> x.Key).ToList();
nonUnique.ForEach(x=> { Console.WriteLine(x); });
alternative solution in case you want to know the total amount of duplicates
I assume you made a mistake and property GroupAssets.Assets contains the list of assets (new List<string>() {"A", "B"}) and not the list of comma separated strings with only one string in the list (new List<string>() {"A,B"}).
First you have to figure out what are the duplicates. You could group items by one of strings "A" to "J" and the value int is the number of occurrences of that key in all lists. We take code from another Stack Overflow question, enhanced with one SelectMany because we want to flatten many lists into one.
var assetCount = GroupList
.SelectMany(x => x.Assets)
.GroupBy(x => x)
.Select(s => new { Asset = s.Key, Count = s.Count() });
Then we make list of duplicates, and a list of groups with unique assets:
var duplicates = assetCount.Where(x => x.Count > 1).Select(x => x.Asset).ToList();
var uniqueAssetsGroupList = GroupList
.Select(x => new GroupAssets() { Name = x.Name, Assets = x.Assets.Except(duplicates).ToList() });
foreach (var group in uniqueAssetsGroupList)
Console.WriteLine(string.Format("{0} => {1}", group.Name, string.Join(",", group.Assets)));
Console.WriteLine("Duplicate => {0}", string.Join(",", duplicates));
Assuming that you have
class GroupAssets
{
public String Name { get; set; }
public IList Assets { get; set; }
}
List<GroupAssets> GroupList = new List<GroupAssets>{
new GroupAssets { Name="Group1", Assets = new List<string>{ "A" ,"B", "C", "D" }},
new GroupAssets { Name="Group2", Assets = new List<string>{ "A" ,"B", "E", "F" }},
new GroupAssets { Name="Group3", Assets = new List<string>{ "A" ,"B", "H", "G" }},
new GroupAssets { Name="Group4", Assets = new List<string>{ "A" ,"I", "C", "J" }},
};
note, that each Asset has 4 items (not 1) you can put
Code:
HashSet<string> duplicates = new HashSet<string>();
HashSet<string> all = new HashSet<string>();
foreach (var item in GroupList)
foreach (var asset in item.Assets)
if (!all.Add(asset)) // duplicate if all contains the asset
duplicates.Add(asset);
// removing duplicates from each Asset
foreach (var item in GroupList)
item.Assets.RemoveAll(item => duplicates.Contains(item));
Let's have a look:
string report = string.Join(Environment.NewLine, GroupList
.Select(item => $"{item.Name} => {string.Join(", ", item.Assets)}"));
Console.WriteLine(report);
Console.WriteLine("Duplicate => {string.Join(", ", duplicates)}");
Outcome:
Group1 => D
Group2 => E, F
Group3 => H, G
Group4 => I, J
Duplicate => A, B, C
If, however, each of Assets contains 1 comma separated item, you should add Split and Join:
HashSet<string> duplicates = new HashSet<string>();
HashSet<string> all = new HashSet<string>();
foreach (var item in GroupList)
foreach (var asset in item.Assets.SelectMany(list => list.Split(',')))
if (!all.Add(asset))
duplicates.Add(asset);
foreach (var item in GroupList) {
item.Assets = item
.Assets
.Select(asset => asset.Split(',').Where(c => !duplicates.Contains(c)))
.Where(asset => asset.Any())
.Select(asset => string.Join(",", asset))
.ToList();
}
find the unique duplicates and then use except to remove the duplicates from the asset list
[Fact]
public void TestRemoveDuplicate()
{
List<GroupAssets> GroupList = new List<GroupAssets>{
new GroupAssets { Name="Group1", Assets = new List<string>{ "A" ,"B", "C", "D" }},
new GroupAssets { Name="Group2", Assets = new List<string>{ "A" ,"B", "E", "F" }},
new GroupAssets { Name="Group3", Assets = new List<string>{ "A" ,"B", "H", "G" }},
new GroupAssets { Name="Group4", Assets = new List<string>{ "A" ,"I", "C", "J" }},
};
IList<String> duplicates = new List<String>();
foreach (var item in GroupList)
{
foreach (var element in item.Assets)
{
if (GroupList.Where(e =>e.Name!=item.Name && e.Assets.Contains(element)).Any())
{
if (duplicates.Contains(element) == false) { duplicates.Add(element); }
}
}
}
foreach (var item in GroupList)
{
item.Assets = item.Assets.Except(duplicates).ToList();
string result = "";
foreach (var element in item.Assets)
{
result += element + " ";
}
_output.WriteLine($"Name: {item.Name} Asset: {result}");
}
Assert.True(duplicates.Count() > 0);
}
output:
Name: Group1 Asset: D
Name: Group2 Asset: E F
Name: Group3 Asset: H G
Name: Group4 Asset: I J
List<string> tempList = new List<string>();
Dictionary<string, int> keyValuePairs = new Disctionary<string, int>();
GroupList.ForEach(x => {
tempList.AddRange(x.Assets);
});
tempList.ForEach(X => {
if(!keyValuePairs.Keys.Contains(x))
{
keyValuePairs.Add(x,1);
}
else
{
keyValuePairs[x]++;
}
});
tempList.Clear();
tempList.AddRange(keyValuePairs.Where(x => x.Value > 1).Select(x => x.Key));
GroupList.ForEach(x => {
var temp = x.Assets;
x.Assets = temp.Except(tempList).ToList();
});

how to sum values of duplicate element in array

I have two arrays:
string[] fruit = { "apple", "banana", "lemon", "apple", "lemon" };
int[] quantity = { 2, 4, 1, 2, 2 };
The second one has the same length of the first one, and the integer numbers are the quantity of each fruit.
I want to create these two arrays:
totalefruit = { "apple", "banana", "lemon" };
totalquantity = {4, 4, 3}
Try this:
string[] fruit = { "apple", "banana", "lemon", "apple", "lemon" };
int[] quantity = { 2, 4, 1, 2, 2 };
var result =
fruit
.Zip(quantity, (f, q) => new { f, q })
.GroupBy(x => x.f, x => x.q)
.Select(x => new { Fruit = x.Key, Quantity = x.Sum() })
.ToArray();
var totalefruit = result.Select(x => x.Fruit).ToArray();
var totalquantity = result.Select(x => x.Quantity).ToArray();
result looks like this:
You could use Zip and a lookup:
var fruitQuantityLookup = fruit
.Zip(quantity, (f, q) => new { Fruit = f, Quantity = q })
.ToLookup(x => x.Fruit, x => x.Quantity);
string[] totalefruit = fruitQuantityLookup.Select(fq => fq.Key).ToArray();
int[] totalquantity = fruitQuantityLookup.Select(fq => fq.Sum()).ToArray();

Two arrays into one Dictionary

I would like to create a
Dictionary<string, int[]> dict
out of two arrays:
string[] keys = { "A", "B", "A", "D" };
int[] values = { 1, 2, 5, 2 };
the result:
["A"] = {1,5}
["B"] = {2}
["D"] = {2}
Is there a way i can do this with LINQ?
I have read about Zip but I don't think I can use since I need to add values to an existing key.value array.
Use .Zip to bind the two collections together and then GroupBy to group the keys.
string[] keys = { "A", "B", "A", "D" };
int[] values = { 1, 2, 5, 2 };
var result = keys.Zip(values, (k, v) => new { k, v })
.GroupBy(item => item.k, selection => selection.v)
.ToDictionary(key => key.Key, value => value.ToArray());
Then to add these items into the dictionary that you already have:
I changed the int[] to List<int> so it is easier to handle Add/AddRange
Dictionary<string, List<int>> existingDictionary = new Dictionary<string, List<int>>();
foreach (var item in result)
{
if (existingDictionary.ContainsKey(item.Key))
existingDictionary[item.Key].AddRange(item.Value);
else
existingDictionary.Add(item.Key, item.Value.ToList());
}
Linq solution:
string[] keys = { "A", "B", "A", "D" };
int[] values = { 1, 2, 5, 2 };
Dictionary<string, int[]> dict = keys
.Zip(values, (k, v) => new {
key = k,
value = v })
.GroupBy(pair => pair.key, pair => pair.value)
.ToDictionary(chunk => chunk.Key,
chunk => chunk.ToArray());
Test:
string report = String.Join(Environment.NewLine, dict
.Select(pair => $"{pair.Key} [{string.Join(", ", pair.Value)}]"));
Console.Write(report);
Outcome:
A [1, 5]
B [2]
D [2]
Try this :
string[] keys = { "A", "B", "A", "D" };
int[] values = { 1, 2, 5, 2 };
Dictionary<string, int[]> dict = keys.Select((x, i) => new { key = x, value = values[i] }).GroupBy(x => x.key, y => y.value).ToDictionary(x => x.Key, y => y.ToArray());

Descending sorting of a list of object based on a count from a different class

I'm stuck on this problem where I need to do descending sort based on other list. l_lstNames need to update by age descending.
public class Test
{
public String Name;
public Int32 Age;
}
List<String> l_lstNames = new List<String> { "A1", "A3", "A2", "A4", "A0" };
List<Test> l_lstStudents = new List<Test>
{
new Test { Age = 33, Name = "A0" },
new Test { Age = 10, Name = "A1" },
new Test { Age = 50, Name = "A2" },
new Test { Age = 8, Name = "A3" },
new Test { Age = 25, Name = "A4" },
};
// Output
List<String> l_lstNames = new List<String> { "A2", "A0", "A4", "A1", "A3" };
Found few sames samples but not matching what I'm looking for. Thank you for help.
Create Dictionary<string, int> with Name to Age mapping and use it within order method:
var dict = students.ToDictionary(x => x.Name, x => x.Age);
var ordered = source.OrderByDescending(x => dict[x.Name]).ToList();
or you can just order students collection and then select Name only:
var ordered = students.OrderByDescending(x => x.Age)
.Select(x => x.Name)
.ToList();
If you just want the names in order descending:
var sorted = l_lstStudents // From the list of students
.OrderByDescending(l => l.Age) // with the oldest student first
.Select(s => s.Name) // give me just the names
.ToList(); // in a list!
I think this is what you are looking for
List<String> l_lstNames1 = (from student in l_lstStudents
where l_lstNames.Any(a => student.Name == a)
orderby student.Age descending
select student.Name ).ToList();
OR
List<String> l_lstNames2 = l_lstStudents.OrderByDescending(a => a.Age)
.Where(a => l_lstNames.Any(b => b == a.Name))
.Select(a => a.Name).ToList();

How can I add list parameters to a LINQ query?

I have two parameters passed to the method and I need to attach them to the final query list.
(1st parameter)
string[] Price= new string[5];
Price= new string[] { "50", "25", "35" };
(2nd parameter)
List<string> DiscountPrice= new List<string>();
DiscountPrice.Add ("10");
DiscountPrice.Add ("5");
DiscountPrice.Add ("3");
var list= (from d in context.List
where ....
select new MyNewList
{
Name = d.Name,
Country = d.Country,
**Price = ??** //how do I attach the parameters one by one? In the order they were saved?
**DiscountPrice** = ??
}).ToList<MyNewList>();
It sounds like you want to match list elements by index. You can iterate from zero to the number of list elements and access each element by its index:
var prices = new string[] { "50", "25", "35" };
var discountPrices = new List<string>() { "10", "5", "3" };
var items = (from d in context.List
where ....
select new { d.Name, d.Country }).ToList();
var list = (from index in Enumerable.Range(0, items.Count())
select new MyNewList
{
Name = items[index].Name,
Country = items[index].Country,
Price = prices[index],
DiscountPrice = discountPrices[index]
}).ToList();
Another way is to Zip everything together:
var list = items.Zip(prices, (item, price) => new { item, price })
.Zip(discountPrices, (x, discountPrice) => new { x.item, x.price, discountPrice})
.Select(x => new MyNewList
{
Name = x.item.Name,
Country = x.item.Country,
Price = x.price,
DiscountPrice = x.discountPrice
})
.ToList();

Categories