I want to distinctly merge two or more collections on an id and create a collection of the other property for each id.
I have an object foo
public class foo
{
public int id { get; set; }
public string color { get; set; }
}
Which I have multiple List<foo> which have common id's but with different colors
//list 1
id = 1, color = "red"
id = 2, color = "blue"
id = 3, color = "green"
id = 1, color = "blue"
id = 2, color = "orange"
//list 2
id = 1, color = "black"
id = 2, color = "amber"
id = 3, color = "red"
id = 4, color = "red"
id = 2, color = "silver"
I want to use linq to project a new collection that will be distinct on the id but create and list of the color property.
id = 1, color = { "red", "blue", "black" }
id = 2, color = { "blue", "orange", "amber", "silver" }
id = 3, color = { "green", "red" }
id = 4, color = { "red" }
Q: How can this be written in linq
var colours = list.GroupBy(x => x.id)
.Select(x => new {id = x.Key, color = x.Select(y => y.color).ToList()})
.ToList();
That will give you a list called colours in the structure you want.
If you want the colour field to be a comma separated string then change it to:
var colours = list.GroupBy(x => x.id)
.Select(x => new {id = x.Key, color = x.Select(y => y.color).ToArray().Join(",")})
.ToList();
I think you want a Dictionary<int, List<string>> instead:
Dictionary<int, List<string>> idColors = foos
.GroupBy(f => f.id)
.ToDictionary(g => g.Key, g => g.Select(f => f.color).ToList());
Now you can access every id's color(s) in this way:
List<string> colors = idColors[1];
Console.WriteLine(string.Join(",", colors));
var result = List.GroupBy(g => new { g.id, g.color })
.Select(g => g.First())
.ToList();
you can use below mentioned code
var mergedList = list1.Concat(list2)
.GroupBy(person => person.id)
.Select(group => group.Aggregate(
(merged, next) => merged.Merge(next)))
.ToList();
I think this is what you are looking for:
list1.Concat(list2)
.GroupBy(x => x.id)
.Select(x => new foo
{
id = x.Key,
color = string.Join(",", x.Select(g => g.color))
}).ToList();
Try this:
var combinedList = list1;
combinedList.AddRange(list2);
var uniqueIds = combinedList.Select(p => p.id).Distinct()
var result = new Dictionary<int, List<string>>();
uniqueIds.ForEach(i => result.Add(i, new List<string>());
combinedList.ForEach(i => result[i.id].Add(i.color);
I haven't tested this, but I think it should be okay...
Might be a better way of doing it, but this should get you working for the moment!
Related
I use EF Core and I want to select only the IDs I need, as I would do it, I use an In SQL expression. How can I get the result in the same order as the Ids in the array? And fill OrderNum value in result Dto?
public IEnumerable<ResultDto> Foo()
{
int[] validIds = { 100, 2, 3, 4, 5, 6, 8, 13, 14, 16, 22 };
// Without the required sorting
var query = dc.LeaveRequests.Where(x => validIds.Contains(x.Id));
...
}
class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
class ResultDto
{
public int Id { get; set; }
public string Name { get; set; }
public int OrderNum { get; set; }
}
I would create an index lookup dictionary with the ID as the key and the index as the value. You can then order the result by looking up the index in the dictionary in O(1) time. (using .IndexOf on the array would be an O(n) operation)
int[] validIds = { 100, 2, 3, 4, 5, 6, 8, 13, 14, 16, 22 };
var result = dc.LeaveRequests.Where(x => validIds.Contains(x.Id)).AsEnumerable();
var indexLookup = validIds.Select((v,i) => (v,i)).ToDictionary(x => x.v, x => x.i);
var sortedResult = result.OrderBy(x => indexLookup[x.Id]);
Perhaps an even more simple solution would be to join the validIds with the result of the query. The order from the first collection is preserved and the join will use a HashSet internally for the lookup. It would also perform better since ordering the result using OrderBy can be avoided.
int[] validIds = { 100, 2, 3, 4, 5, 6, 8, 13, 14, 16, 22 };
var result = dc.LeaveRequests.Where(x => validIds.Contains(x.Id)).AsEnumerable();
var sortedResult = validIds.Join(result, x => x, x => x.Id, (x, y) => y);
Assuming that the valid ids may be provided in another order, you could order by the index position of the id in the validIds (using a list instead of an array) and map the index position of the result to the OrderNum property:
var query = dc.LeaveRequests
.Where(x => validIds.Contains(x.Id))
.OrderBy(x => validIds.IndexOf(x.Id))
.Select((x, i) => new ResultDto
{
Id = x.Id,
Name = x.Name,
OrderNum = i
});
Try OrderBy if you don't have more requirements.
var query = dc.LeaveRequests
.Where(x => validIds.Contains(x.Id))
.OrderBy(x => validIds.IndexOf(x.Id))
.Select(x => new OrderNum {
Id = x.Id,
Name = x.Name,
OrderNum = //fill OrderNum here,
})
.AsEnumerable();
var results = dc.LeaveRequests
.Where(x => validIds.Contains(x.Id))
.Select(x => new ResultDto
{
Id = x.Id,
Name = x.FechaCreacion,
})
.AsEnumerable()
.OrderBy(x => validIds.IndexOf(x.Id))
.ToList();
for (int i = 0; i < results.Count; i++)
{
results[i].OrderNum = i;
}
Suppose I have the items
{ // item 0
Color = yellow,
Priority = high,
},
{ // item 1
Color = yellow,
Priority = medium,
},
{ // item 2
Color = green,
Priority = high,
},
I want to be able to progressively build a GroupBy query as using var grouped = items.GroupBy(item => item.Color), what in this case I would have 2 groups and then group again using by the key item.Priority to have 3 groups.
Unfortunately using var regrouped = grouped.GroupBy(item => item.Priority) is not a valid solution.
The final result must be equivalent as doing
items.GroupBy(item => new {item.Color, item.Priority});
Since you want to regroup on a subkey as a new object, you can use ToLookup to create the IGroupings you need, and SelectMany to flatten them into the new IEnumerable:
var regrouped = grouped.SelectMany(cg => cg.GroupBy(i => i.Priority)
.ToLookup(pg => new { Color = cg.Key, Priority = pg.Key },
pg => pg.Select(i => i)
)
);
If I understood you correctly, you want to group each group of Color into Priority. If that's the case, this should work:
var items = new List<Item>
{
new Item { Color = "yellow", Priority = "high" },
new Item { Color = "red", Priority = "high" },
new Item { Color = "yellow", Priority = "medium" },
new Item { Color = "red", Priority = "medium" },
new Item { Color = "green", Priority = "high" },
new Item { Color = "green", Priority = "high" },
new Item { Color = "red", Priority = "high" },
};
var groupped = items.GroupBy(x => x.Color);
var regroupped = groupped.Select(x => new
{
Color = x.Key,
Priorities = x.GroupBy(y => y.Priority)
});
Notice that this returns the same result set than your query
var regroupped = items.GroupBy(item => new {item.Color, item.Priority});
I have a list of key/value pairs in the following form:
[{John:6},{Alex:100},{Peter:4},{Peter,John:5},{Alex,Kati:1}]
I wonder if there is a simple linq expression I can use to translate the list into
[{John:11},{Alex:101},{Peter:9},{Kati:1}]
ie split string by comma and adjust counts.
the list above is coming from following LINQ
var list = people.Where(a => !string.IsNullOrWhiteSpace(a.Name))
.GroupBy(a => a.Name.Trim()).Select(a => new User { Name = a.Key, Items= a.Count() });
With
var list = new[]
{
new KeyValuePair<string, int>("John", 6),
new KeyValuePair<string, int>("Alex", 100),
new KeyValuePair<string, int>("Peter", 4),
new KeyValuePair<string, int>("Peter,John", 5),
new KeyValuePair<string, int>("Alex,Kati", 1)
};
this grouping
var modifiedList = list.SelectMany(p => p.Key.Split(',').Select(n => new {Name = n, Number = p.Value}))
.GroupBy(p => p.Name).Select(g => new KeyValuePair<string, int>(g.Key, g.Sum(r => r.Number)));
gives you the output
{[John, 11]}
{[Alex, 101]}
{[Peter, 9]}
{[Kati, 1]}
Try this:
var list = new List<KeyValuePair<string, int>>
{
new KeyValuePair<string,int>("John", 6),
new KeyValuePair<string,int>("Alex", 100),
new KeyValuePair<string,int>("Peter", 4),
new KeyValuePair<string,int>("Peter,John", 5),
new KeyValuePair<string,int>("Alex,Kati", 1)
};
var result = list.SelectMany(x => x.Key.Split(','),
(x, y) => new KeyValuePair<string, int>(y, x.Value))
.GroupBy(x => x.Key)
.ToDictionary(key => key.Key, value => value.Sum(x => x.Value));
Dictionary<string, int> result = keyVals
.SelectMany(kv => kv.Key.Split(',').Select(name => new{ name, kv.Value }))
.GroupBy(x => x.name)
.ToDictionary(xg => xg.Key, xg => xg.Sum(x => x.Value));
Result:
{[John, 11]}
{[Alex, 101]}
{[Peter, 9]}
{[Kati, 1]}
Demo
So you have classes like this
public class Person
{
public string Name { get; set; }
}
public class User
{
public string Name { get; set; }
public int Items { get; set; }
}
If I understand correctly you want to count how many times each name occurs
var people = new[]
{
new Person { Name = "John" },
new Person { Name = "John,Alex" },
new Person { Name = "Alex" },
new Person { Name ="Peter,John" }
};
var list = people.SelectMany(p => p.Name.Split(','))
.GroupBy(n => n)
.Select(g => new User { Name = g.Key, Items = g.Count() });
I have a List like the following:
var products = new List<Product>
{
new Product { Id = 1, Category = "Electronics", Value = 15.0 },
new Product { Id = 2, Category = "Groceries", Value = 40.0 },
new Product { Id = 3, Category = "Garden", Value = 210.3 },
new Product { Id = 4, Category = "Pets", Value = 2.1 },
new Product { Id = 5, Category = "Electronics", Value = 19.95 },
new Product { Id = 6, Category = "Pets", Value = 5.50 },
new Product { Id = 7, Category = "Electronics", Value = 250.0 },
};
I want to group by category and get the sum of 'Values' belonging to that category..
Example: Electronics: 284.95
While I can do this in some other way, I want to learn usage of Look-Up.
Is it possible to get these 2 values (category and Value) in a Look-Up? If yes, How can I do that?
When you retrieve by key from a Lookup, it behaves just like a grouping, so you can do things like this:
var productLookup = products.ToLookup(p => p.Category);
var electronicsTotalValue = productLookup["Electronics"].Sum(p => p.Value);
var petsTotalValue = productLookup["Pets"].Sum(p => p.Value);
//etc
var totalValue = products.Sum(p => p.Value);
// I wouldn't use the Lookup here, the line above makes more sense and would execute faster
var alsoTotalValue = productLookup.Sum(grp => grp.Sum(p => p.Value));
You probably want to use ToDictionary() instead of ToLookup
var dict = products
.GroupBy(p => p.Category)
.ToDictionary(grp => grp.Key, grp => grp.Sum(p => p.Value));
foreach(var item in dict)
{
Console.WriteLine("{0} = {1}", item.Key, item.Value);
}
You don't need a Lookup. You can do this with just a query:
var results =
from p in products
group p by p.Category into g
select new
{
Category = g.Key,
TotalValue = g.Sum(x => x.Value)
};
var rez = products.ToLookup(k => k.Category, v => v.Value).Select(k=>new KeyValuePair<string, double>(k.Key, k.Sum()));
List<int> a = 11,2,3,11,3,22,9,2
//output
11
This may not be the most efficient way, but it will get the job done.
public static int MostFrequent(IEnumerable<int> enumerable)
{
var query = from it in enumerable
group it by it into g
select new {Key = g.Key, Count = g.Count()} ;
return query.OrderByDescending(x => x.Count).First().Key;
}
And the fun single line version ...
public static int MostFrequent(IEnumerable<int> enumerable)
{
return (from it in enumerable
group it by it into g
select new {Key = g.Key, Count = g.Count()}).OrderByDescending(x => x.Count).First().Key;
}
a.GroupBy(item => item).
Select(group => new { Key = group.Key, Count = group.Count() }).
OrderByDescending(pair => pair.Count).
First().
Key;
Another example :
IEnumerable<int> numbers = new[] { 11, 2, 3, 11, 3, 22, 9, 2 };
int most = numbers
.Select(x => new { Number = x, Count = numbers.Count(y => y == x) })
.OrderByDescending(z => z.Count)
.First().Number;