If I have a list of objects and I don't want to allow duplicates of a certain attribute of the objects. My understanding is that I can use DistinctBy() to remove one of the objects. My question is, how do I choose which of the objects with the same value of an attribute value do I keep?
Example:
How would I go about removing any objects with a duplicate value of "year" in the list tm and keep the object with the highest value of someValue?
class TestModel{
public int year{ get; set; }
public int someValue { get; set; }
}
List<TestModel> tm = new List<TestModel>();
//populate list
//I was thinking something like this
tm.DistinctBy(x => x.year).Select(x => max(X=>someValue))
You can use GroupBy and Aggregate (there is no MaxBy built-in method in LINQ):
tm
.GroupBy(tm => tm.year)
.Select(g => g.Aggregate((acc, next) => acc.someValue > next.someValue ? acc : next))
User the GroupBy followed by the SelectMany/Take(1) pattern with an OrderBy:
IEnumerable<TestModel> result =
tm
.GroupBy(x => x.year)
.SelectMany(xs =>
xs
.OrderByDescending(x => x.someValue)
.Take(1));
Here's an example:
List<TestModel> tm = new List<TestModel>()
{
new TestModel() { year = 2020, someValue = 5 },
new TestModel() { year = 2020, someValue = 15 },
new TestModel() { year = 2019, someValue = 6 },
};
That gives me:
This question already has answers here:
How to make a linq Sum return null if the summed values are all null
(5 answers)
Keep null when adding Nullable int?
(1 answer)
Closed 2 years ago.
I am creating a simple test method for sum of list to render null when the list is not rendering any value
[Fact]
public void GetSum()
{
List<TestClass> list = new List<TestClass>();
list.Add(new TestClass
{
Amount = null,
Id = 1
});
list.Add(new TestClass
{
Amount = null,
Id = 0
});
IQueryable<TestClass> classes = list.AsQueryable();
var sum = classes.AsEnumerable().Where(i => i.Id > 1).Select(i => i.Amount).Sum();
var sum1 = classes.AsEnumerable().Sum(i => i.Amount);
Assert.NotNull(sum);
}
#endregion Impact Ratio
}
public class TestClass
{
public int Id { get; set; }
public double? Amount { get; set; }
}
sum and sum1 both render 0, I want them up to be null in case the list is not having any appropriate value. Am I missing anything out in this ?
the Sum() function works as it does so you will have to use another function to achieve what you want. One possibility is the Aggregate() function which allows you to define how you want the summation to work.
For example this seems to achieve what you want:
[Fact]
public void GetSum()
{
var classes = new[]
{
new TestClass {Amount = null, Id = 1},
new TestClass {Amount = 1, Id = 0}
};
var sum1 = classes.Where(i => i.Id > 1)
.Select(i => i.Amount)
.Aggregate((double?)null, (a, b) => a.HasValue && b.HasValue ? a + b : a ?? b);
var sum2 = classes.Select(i => i.Amount)
.Aggregate((double?)null, (a, b) => a.HasValue && b.HasValue ? a + b : a ?? b);
Assert.Null(sum1);
Assert.NotNull(sum2);
}
Note that this method only works when you have an IEnumerable - it does not work with IQueryable, but you convert your IQueryable to IEnumerable in your own example so I guess that is ok...
I have a List<Model> modelList where Model contains a property GuidProperty of type `GUID?. All elements within this list are guaranteed not to have a null value as the list ist filtered using extension
modelList.Where(x=>x.GuidProperty.HasValue)
Moreover each GUID is unique.
Now by trying to iterate through this list modelList by
foreach (var model in modelList.GroupBy(x => x.GuidProperty.Value)){
doSomething();
}
I recognized that there are not as many groups as modelList.Count which does not makes sense to me. In my case one group was "missing".
Is this caused by the way I created this list modelList?:
//all guid are unique in the example
List<Model> modelList = new List<Model>();
using (var entities = _modelContext.EntitiesNoChanges)
{
modelList.AddRange(
entities.OrderProcessingItems.Where(x => x.GuidProperty.HasValue).ToList());
}
//manually filled list of unique guids contained in modelList
List<Guid> guidList = new List<Guid>();
foreach (var model in modelList)
{
// ReSharper disable once PossibleInvalidOperationException
var guid = model.GuidProperty.Value;
if (guidList.Contains(guid))
{
continue;
}
guidList.Add(guid);
}
// grouped list that should have n groups | n=modelList.Count
foreach (var model in modelList.GroupBy(x => x.GuidProperty.Value)){
doSomething();
}
UPDATE
Here is the actual problem:
- guidList Count = 4 System.Collections.Generic.List<System.Guid>
+ [0] {46952518-1529-4b0f-8123-6bad36057e84} System.Guid
+ [1] {80395ccb-2307-4bea-b772-69b36ea8d663} System.Guid
+ [2] {e1161daf-8c7b-4b31-a987-694e4563e0e6} System.Guid
+ [3] {2c44861e-073b-42b2-8fbf-6ff0e1cc0b7b} System.Guid
+ Raw View
//this is the grouping for modelList - ignore namings...
- offeringRateGroup {System.Linq.Lookup<System.Guid, slModel.Db.OrderProcessingItem>.Grouping} System.Linq.IGrouping<System.Guid, slModel.Db.OrderProcessingItem> {System.Linq.Lookup<System.Guid, slModel.Db.OrderProcessingItem>.Grouping}
+ Key {46952518-1529-4b0f-8123-6bad36057e84} System.Guid
System.Collections.Generic.ICollection<slModel.Db.OrderProcessingItem>.Count 3 int
System.Collections.Generic.ICollection<slModel.Db.OrderProcessingItem>.IsReadOnly true bool
count 3 int
+ elements {slModel.Db.OrderProcessingItem[4]} slModel.Db.OrderProcessingItem[]
guidList is manually created and covers all models properies within the list.
Update
Order of code
I have an IEnumerable collection of UnitGroup: IEnumerable<UnitGroup>,
class UnitGroup
{
string key { get; set; }
List<UnitType> NameList { get; set; }
}
class UnitType
{
String UnitName{ get; set; }
Description { get; set; }
}
Now I want to filterIEnumerable<UnitGroup> based on UnitType's UnitName.
For example I want to get only the records of UnitName that contains a string and remove remaining.
something like this:
IEnumerable<UnitGroup> Groups;
IEnumerable<UnitGroup> filteredResult = Groups.NameList(o => o.UnitName.contains("test"));
And get IEnumerable<UnitGroup> with only filtered UnitNames under UnitType under UnitGroup.
What is the best way of acheiving this?
I'm not 100% sure what you're trying to achieve. Could you provide some sample data, to make it more clear?
Although, I think it may fit into your goal:
IEnumerable<UnitGroup> Groups;
var filteredResult = Groups.Select(g => new UnitGroup {
key = g.key,
NameList = g.NameList
.Where(n => n.UnitName == "test")
.ToList()
})
.Where(g => g.NameList.Count > 0);
Here is another way that should do what #MarcinJuraszek answers does. (I am guessing the intent of the question as well.)
IEnumerable<UnitGroup> Groups;
var filteredResult = Groups.Where (g => g.NameList.Count() > g.NameList.RemoveAll(nl => nl.UnitName != "Name1"));
If the number of removed items was less than the original count, then we have items that are of interest, so select the parent.
Note: This will modify the original collection, so if you need to filter it more than once then this is not the answer you are looking for.
Try this:
var filteredList = from g in Groups
where g.NameList.Exists(i=>i.UnitName=="test")
select g;
I know I have asked this question in a different manner earlier today but I have refined my needs a little better.
Given the following csv file where the first column is the title and there could be any number of columns;
year,2008,2009,2010,2011,2012,2013,2014,2015,2016,2017
income,1000,1500,2000,2100,2100,2100,2100,2100,2100,2100
dividends,100,200,300,300,300,300,300,300,300,300
net profit,1100,1700,2300,2400,2400,2400,2400,2400,2400,2400
expenses,500,600,500,400,400,400,400,400,400,400
profit,600,1100,1800,2000,2000,2000,2000,2000,2000,2000
How do I select the profit value for a given year? So I may provide a year of say 2011 and expect to get the profit value of 2000 back.
At the moment I have this which shows the profit value for each year but ideally I'd like to specify the year and get the profit value;
var data = File.ReadAllLines(fileName)
.Select(
l => {
var split = l.Split(",".ToCharArray());
return split;
}
);
var profit = (from p in data where p[0] == profitFieldName select p).SingleOrDefault();
var years = (from p in data where p[0] == yearFieldName select p).FirstOrDefault();
int columnCount = years.Count() ;
for (int t = 1; t < columnCount; t++)
Console.WriteLine("{0} : ${1}", years[t], profit[t]);
I've already answered this once today, but this answer is a little more fleshed out and hopefully clearer.
string rowName = "profit";
string year = "2011";
var yearRow = data.First();
var yearIndex = Array.IndexOf(yearRow, year);
// get your 'profits' row, or whatever row you want
var row = data.Single(d => d[0] == rowName);
// return the appropriate index for that row.
return row[yearIndex];
This works for me.
You have an unfortunate data format, but I think the best thing to do is just to define a class, create a list, and then use your inputs to create objects to add to the list. Then you can do whatever querying you need to get your desired results.
class MyData
{
public string Year { get; set; }
public decimal Income { get; set; }
public decimal Dividends { get; set; }
public decimal NetProfit { get; set; }
public decimal Expenses { get; set; }
public decimal Profit { get; set; }
}
// ...
string dataFile = #"C:\Temp\data.txt";
List<MyData> list = new List<MyData>();
using (StreamReader reader = new StreamReader(dataFile))
{
string[] years = reader.ReadLine().Split(',');
string[] incomes = reader.ReadLine().Split(',');
string[] dividends = reader.ReadLine().Split(',');
string[] netProfits = reader.ReadLine().Split(',');
string[] expenses = reader.ReadLine().Split(',');
string[] profits = reader.ReadLine().Split(',');
for (int i = 1; i < years.Length; i++) // index 0 is a title
{
MyData myData = new MyData();
myData.Year = years[i];
myData.Income = decimal.Parse(incomes[i]);
myData.Dividends = decimal.Parse(dividends[i]);
myData.NetProfit = decimal.Parse(netProfits[i]);
myData.Expenses = decimal.Parse(expenses[i]);
myData.Profit = decimal.Parse(profits[i]);
list.Add(myData);
}
}
// query for whatever data you need
decimal maxProfit = list.Max(data => data.Profit);