C# LINQ: Get items with max price - c#

I have a list of my objects:
class MyObj
{
public String Title { get; set; }
public Decimal Price { get; set; }
public String OtherData { get; set; }
}
var list = new List<MyObj> {
new MyObj { Title = "AAA", Price = 20, OtherData = "Z1" },
new MyObj { Title = "BBB", Price = 20, OtherData = "Z2" },
new MyObj { Title = "AAA", Price = 30, OtherData = "Z5" },
new MyObj { Title = "BBB", Price = 10, OtherData = "Z10" },
new MyObj { Title = "CCC", Price = 99, OtherData = "ZZ" }
};
What is the best way to get list with unique Title and MAX(Price).
Resulting list needs to be:
var ret = new List<MyObj> {
new MyObj { Title = "BBB", Price = 20, OtherData = "Z2" },
new MyObj { Title = "AAA", Price = 30, OtherData = "Z5" },
new MyObj { Title = "CCC", Price = 99, OtherData = "ZZ" }
};

Well, you could do:
var query = list.GroupBy(x => x.Title)
.Select(group =>
{
decimal maxPrice = group.Max(x => x.Price);
return group.Where(x => x.Price == maxPrice)
.First();
};
If you need LINQ to SQL (where you can't use statement lambdas) you could use:
var query = list.GroupBy(x => x.Title)
.Select(group => group.Where(x => x.Price == group.Max(y => y.Price))
.First());
Note that in LINQ to Objects that would be less efficient as in each iteration of Where, it would recompute the maximum price.
Adjust the .First() part if you want to be able return more than one item with a given name if they both have the same price.
Within LINQ to Objects you could also use MoreLINQ's MaxBy method:
var query = list.GroupBy(x => x.Title)
.Select(group => group.MaxBy(x => x.Price));

var ret = list.GroupBy(x => x.Title)
.Select(g => g.Aggregate((a, x) => (x.Price > a.Price) ? x : a));
(And if you need the results to be a List<T> rather than an IEnumerable<T> sequence then just tag a ToList call onto the end.)

var ret = list.OrderByDescending(x => x.Price).GroupBy(x => x.Title).Select(#group => #group.ElementAt(0)).ToList();
this should do it.

Would like to mention that
var query = list.GroupBy(x => x.Title)
.Select(group => group.Where(x => x.Price == group.Max(y => y.Price))
.First());
Should be
var query = list.GroupBy(x => x.Title)
.First(group => group.Where(x => x.Price == group.Max(y => y.Price)));
I like the Richard solution to greatest-n-per-group problem.
var query = list
.OrderByDescending(o => o.Price) //set ordering
.GroupBy(o => o.Title) //set group by
.Select(o => o.First()); //take the max element
However it needs to be slightly modified
var query = list
.OrderByDescending(o => o.Price) //set ordering
.GroupBy(o => o.Title) //set group by
.Select(o => o.Where(k => k.Price == o.First().Price)) //take max elements

Related

How can i use distinct with count in Linq?

The extention method below does not have Distinct and Count
public static IEnumerable<Something> ToFilterModel(this IEnumerable<Product> products)
{
var v = products
.SelectMany(x => x.ProductVariants)
.GroupBy(x => x.OptionId)
.Select(x => new
{
Id = x.Key.ToString(),
Items = x.Select(x => new Item { Id = x.ValueId, Text = x.Value.OptionValue })
});
return v;
}
Given the input below it should return 2 Items rows and not 3, since i am interested for ValueIds
and also Count by ValueIds
how should i modify it?
More spesifically it should return items with rows 1 and 2 and also
Count equal to 1 for the first row and Count equal to 2 for the second row.
You could group by ValueId the grouped options, like :
Items = x
.GroupBy(y => y.ValueId)
.Select(z => new Item { Id = z.Key, Text = z.First().Value.OptionValue, Count = z.Count() })
The result will be :
{
"Id":1,
"Items":[
{
"Id":1,
"Text":"text1",
"Count":1
},
{
"Id":2,
"Text":"text2",
"Count":2
}
]
}
NOTE : the Text is the count of grouped value ids.
The whole code :
var v = products
.SelectMany(x => x.ProductVariants)
.GroupBy(x => x.OptionId)
.Select(x => new
{
Id = x.Key.ToString(),
Items = x
.GroupBy(y => y.ValueId)
.Select(z => new Item { Id = z.Key, Text = z.First().Value.OptionValue, Count = z.Count() })
});

Summing up double lists in a Linq GroupBy

I need to group a large number of records which were recorded every minute into daily and bind them to a chart. These records have two fields the datetime value and the double list. I've tried something like this:
var result = Alldatas
.AsEnumerable()
.GroupBy(r => r.TimeStamp.Day)
.Select(x => new {
Day = x.Key,
Value = x.Sum(r => r.Value.Sum())
})
.OrderBy(x => x.Day)
.ToList();
The problem is that the list items in the double list is being summed up each other into a single double value. The correct result is that double lists should be adding up each other into a single double list for each day. Is there a way to achieve this? Thanks.
If I understood you correctly, here is the code:
var result = Alldatas
.AsEnumerable()
.GroupBy(r => r.TimeStamp.Day)
.Select(x => new {
Day = x.Key,
// Using Aggregate method
Value = x
.Select(y => y.Value)
.Aggregate(new List<double>(), (acc, list) =>
{
for (int i = 0; i < list.Count; ++i)
{
if (acc.Count == i) acc.Add(0);
acc[i] += list[i];
}
return acc;
}),
// Pure LINQ, using GroupBy
Value2 = x
// Create tuple (index, value) for each double
.SelectMany(y => y.Value.Select((z, i) => Tuple.Create(i, z)))
// Group by index
.GroupBy(y => y.Item1)
// Sum values within groups
.Select(y => y.Select(z => z.Item2).Sum())
// Make list
.ToList()
})
.OrderBy(x => x.Day)
.ToList();
For input:
var Alldatas = new []
{
new { TimeStamp = DateTime.Now, Value = new List<double> { 1, 2, 3 } },
new { TimeStamp = DateTime.Now, Value = new List<double> { 1, 2, 3 } },
new { TimeStamp = DateTime.Now, Value = new List<double> { 1, 2, 3 } }
};
This will produce following result:
new[] {
new { Day = 20, Value = new[] {3,6,9}, Value2 = new[] {3,6,9} }
}

Evenly distribute males and females in list with LINQ

I have Class1 like:
{
string Name,
string Sex
}
And I have a List<Class1> with 100 items where 50 are Males and 50 are Females, how do I get 10 groups of 5Males and 5Females each with LINQ?
I already manage to get the list grouped in 10 groups but not distributed evenly by sex.
var foo = My100List.Select((person, index) => new {person, index})
.GroupBy(x => x.index%10)
.Select(i => new Group
{
Name= "Group" + i.Key,
Persons= i.Select(y => y.person).ToList()
});
The code above don't distribute by sex.
Try this (untested):
int groupSize = 5;
var foo = My100List.GroupBy(x => x.Sex)
.SelectMany(g => g.Select((x, i) => new { Person = x, Group = i / groupSize}))
.GroupBy(x => x.Group)
.Select(g => new Group
{
Name = "Group" + g.Key,
Persons = g.Select(x => x.Person).ToList()
});
EDIT
Tested and confirmed. The above code works.
Add .OrderBy for sex before the .Select
Tested and working:
var foo = My100List.OrderBy(p => p.Sex).Select((person, index) => new {person, index})
.GroupBy(x => x.index%10)
.Select(i => new Group
{
Name= "Group" + i.Key,
Persons= i.Select(y => y.person).ToList()
});

Linq group by with parent object

How do I group so that I don't loose the parent identifier.
I have the following
var grouped = mymodel.GroupBy(l => new { l.AddressId })
.Select(g => new
{
AddressId = g.Key.AddressId,
Quotes = g.SelectMany(x => x.Quotes).ToList(),
}).ToList();
this returns
{ AddressId1, [Quote1, Quote2, Quote3...]}
{ AddressId2, [Quote12, Quote5, Quote8...]}
Now I would like to group these by Quote.Code and Quote.Currency, So that Each address has 1 Object-Quote (that is if all 4 quotes belonging to the address have the same Code and Currency). I would like the sum of Currency in that object.
This works, but I can't get how to add Address to this result:
var test = grouped.SelectMany(y => y.Quotes).GroupBy(x => new { x.Code, x.Currency }).Select(g => new
{
test = g.Key.ToString()
});}
this gives compile error, whenever i try to add AddressId to result:
var test1 = grouped.SelectMany(y => y.Quotes, (parent, child) => new { parent.AddressId, child }).GroupBy(x => new { x.Provider, x.Code, x.Currency, x.OriginalCurrency }).Select(g => new
{
test = g.Key.ToString(),
Sum = g.Sum(x => x.Price)
});
compiler error as well:
var test1 = grouped.Select(x => new { x.AddressId, x.Quotes.GroupBy(y => new { y.Provider, y.Code, y.Currency, y.OriginalCurrency }).Select(g => new
{
addr = x.AddressId,
test = g.Key.ToString(),
Sum = g.Sum(q => q.Price)
};
I would do that this way:
var grouped = mymodel.GroupBy(l => new { l.AddressId })
.Select(g => new
{
AddressId = g.Key.AddressId,
QuotesByCode = g.SelectMany(x => x.Quotes)
.GroupBy(x=>x.Code)
.Select(grp=>new
{
Code = grp.Key.Code,
SumOfCurrency=grp.Sum(z=>z.Currency)
}).ToList(),
}).ToList();

Getting duplicate data based on dynamic key

I have a list of Person objects:
List<PersonData> AllPersons
From this list I want all those person objects that are duplicated based on a certain property.
Example, this code give all the duplicates based on the Id
var duplicateKeys = AllPersons.GroupBy(p => p.Id).Select(g => new { g.Key, Count = g.Count() }).Where(x => x.Count > 1).ToList().Select(d => d.Key);
duplicates = AllPersons.Where(p => duplicateKeys.Contains(p.Id)).ToList();
Can the part p.Id be dynamic?
Meaning if the user specifies the unique column in a config file and it's read like so:
string uniqueColumn = "FirstName";
How can the query be composed to add that functionality?
Regards.
You can use Reflection to achieve that:
List<PersonData> AllPersons = new List<PersonData>()
{
new PersonData { Id = 1, FirstName = "Tom" },
new PersonData { Id = 2, FirstName = "Jon" },
new PersonData { Id = 3, FirstName = "Tom" }
};
string uniqueColumn = "FirstName";
var prop = typeof(PersonData).GetProperty(uniqueColumn);
var duplicateKeys = AllPersons.GroupBy(p => prop.GetValue(p, null))
.Select(g => new { g.Key, Count = g.Count() })
.Where(x => x.Count > 1)
.Select(d => d.Key)
.ToList();
var duplicates = AllPersons.Where(p => duplicateKeys.Contains(prop.GetValue(p, null))).ToList();
duplicates have 2 elements with FirstName == "Tom" after query execution.
You might want to look into Dynamic LINQ or PredicateBuilder.

Categories