Given a list of values with an unknown number of distinct string values, how do I get the most recent value of Each?
I am given a list of objects with the following three properties: Balance, BalanceType, and CreatedDate. Going in, I know there are a number of distinct values BalanceType can be set to, but I cannot be sure how many distinct values there are. For example, this is a valid input:
[{
"BalanceType":"Cash",
"Balance":350.03,
"CreatedDate":10-20-16
},
{
"BalanceType":"Cash",
"Balance":250.01,
"CreatedDate":10-20-15
},
{
"BalanceType":"Cash",
"Balance":450.21,
"CreatedDate":10-20-14
},
{
"BalanceType":"Securiites",
"Balance":350.03,
"CreatedDate":10-20-16
}]
As is the following:
[{
"BalanceType":"Cash",
"Balance":350.03,
"CreatedDate":10-20-16
},
{
"BalanceType":"Credit",
"Balance":250.01,
"CreatedDate":10-20-15
},
{
"BalanceType":"LoanAmount",
"Balance":450.21,
"CreatedDate":10-20-14
},
{
"BalanceType":"Securiites",
"Balance":350.03,
"CreatedDate":10-20-16
}]
I have already tried using the Max function to do this, but I discovered it only gives the maximum indicated value, not the object. What am I missing?
This Question is related, but in mysql so it isn't usable for me.
It is helpful if you post data in C# format so it can be used directly. Translating your data, here are queries for your answer:
var src1 = new[] {
new {
BalanceType = "Cash",
Balance = 350.03,
CreatedDate = new DateTime(2016, 10, 20)
},
new {
BalanceType = "Cash",
Balance = 250.01,
CreatedDate = new DateTime(2015, 10, 20)
},
new {
BalanceType = "Cash",
Balance = 450.21,
CreatedDate = new DateTime(2014, 10, 20)
},
new {
BalanceType = "Securiites",
Balance = 350.03,
CreatedDate = new DateTime(2016, 10, 20)
}
};
var src2 = new[] {
new {
BalanceType = "Cash",
Balance = 350.03,
CreatedDate = new DateTime(2016, 10, 20)
},
new {
BalanceType = "Credit",
Balance = 250.01,
CreatedDate = new DateTime(2015, 10, 20)
},
new {
BalanceType = "LoanAmount",
Balance = 450.21,
CreatedDate = new DateTime(2014, 10, 20)
},
new {
BalanceType = "Securiites",
Balance = 350.03,
CreatedDate = new DateTime(2016, 10, 20)
}
};
var ans1 = from r in src1
group r by r.BalanceType into rg
let latest = rg.Max(r => r.CreatedDate)
select new { BalanceType = rg.Key, Balance = rg.Where(r => r.CreatedDate == latest).FirstOrDefault().Balance, CreatedDate = latest };
var ans2 = from r in src2
group r by r.BalanceType into rg
let latest = rg.Max(r => r.CreatedDate)
select new { BalanceType = rg.Key, Balance = rg.Where(r => r.CreatedDate == latest).FirstOrDefault().Balance, CreatedDate = latest };
I assumed that if there were more than one latest dated BalanceType, it didn't matter which was chosen, so I used the first. If your DateTimes actually had times you could possibly replace FirstOrDefault() with Single() and crash your program if your assumption is wrong.
Related
I am trying to find a way to cut out repeated code in an application that center around LINQ select statements. Lets say we have existing table rows that need to be aggregated and grouped for different reporting requirements and all original data is just grouped by day and needs to be grouped by week / month and another property.
DataRow is an example object that needs to be grouped and converted into an object ReportTableRow (please note this is a much reduced object but the actual objects have far more properties and therefore become much more drawn out).
public class DataRow
{
public DateTime Date { get; set; }
public string AccountNumber { get; set; }
public string MachineNumber { get; set; }
public int TEST { get; set; }
}
public class ReportTableRow
{
public int WeekNumber { get; set; }
public int Month { get; set; }
public string AccountNumber{ get; set; }
public string MachineNumber { get; set; }
public int TEST { get; set; }
public string TEST_TRAFFICLIGHT { get; set; }
}
And we create a list of DataRows:
List<DataRow> reportTable = new List<DataRow>()
{
new DataRow()
{
Date = new DateTime(2021, 06, 14),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 1
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 6
},
new DataRow()
{
Date = new DateTime(2021, 06, 16),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 4
},
new DataRow()
{
Date = new DateTime(2021, 06, 17),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 18),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 7
},
new DataRow()
{
Date = new DateTime(2021, 06, 19),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 20),
AccountNumber = "11111111",
MachineNumber = "00AB2021",
TEST = 11
},
new DataRow()
{
Date = new DateTime(2021, 06, 14),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 1
},
new DataRow()
{
Date = new DateTime(2021, 06, 15),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 6
},
new DataRow()
{
Date = new DateTime(2021, 06, 16),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 4
},
new DataRow()
{
Date = new DateTime(2021, 06, 17),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 18),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 7
},
new DataRow()
{
Date = new DateTime(2021, 06, 19),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 2
},
new DataRow()
{
Date = new DateTime(2021, 06, 20),
AccountNumber = "22222222",
MachineNumber = "11BC2021",
TEST = 11
}
};
So if we either need the data grouped "BY WEEK" or "BY MONTH" then we need to actually return in the report the WeekNumber or Month number respectively and the grouping would look something like this where GetTrafficLight method returns a string value based on the value of the sum of TEST:
switch (aggregate.ToUpper())
{
case "BY WEEK":
reportTable = reportTable
.GroupBy(x => new { x.AccountNumber, WeekNumber = CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(x.Date, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday) })
.Select(x => new ReportTableRow
{
WeekNumber = x.Key.WeekNumber,
Month = x.Max(y => y.Date).Month,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST)
})
.ToList();
break;
case "BY MONTH":
reportTable = reportTable
.GroupBy(x => new { x.AccountNumber, x.Date.Month })
.Select(x => new ReportTableRow
{
WeekNumber = CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(x.Max(y => y.Date), CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday),
Month = x.Key.Month,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST)
})
.ToList();
break;
}
The question is, is there anyway to remove the "Select" code and pass it into either a method or object that would accept an anonymous grouping so that it can be reused multiple times. Changing the anonymous grouping to a compile time object that contains two properties means duplicates are then returned in the dataset, could be that finding a way to remove the duplicates in compile time grouped members might help to resolve?
Please note, the code has been created to pose this question.
This is optional, but the first thing I would do is put that "aggregate" into a boolean variable:
bool byWeek = aggregate.ToUpper() == "BY WEEK";
The next thing I would do is capture and tame that "GetWeekOfYear" logic:
int getFirstMonday (DateTime date) =>
CultureInfo.InvariantCulture.Calendar.GetWeekOfYear(
date,
CalendarWeekRule.FirstFourDayWeek,
DayOfWeek.Monday
);
Doing these things makes it more feasible to put the conditional logic inside the GroupBy and Select methods, making it so that you don't have to do the whole thing twice:
List<ReportTableRow> results =
reportTable
.GroupBy(x => new {
x.AccountNumber,
TimeBin = byWeek ? getFirstMonday(x.Date): x.Date.Month
})
.Select(x => new ReportTableRow {
WeekNumber = byWeek ? x.Key.TimeBin : getFirstMonday(x.Max(y => y.Date)),
Month = byWeek ? x.Max(y => y.Date).Month : x.Key.TimeBin,
MachineNumber = x.FirstOrDefault().MachineNumber,
TEST = x.Sum(y => y.TEST),
TEST_TRAFFICLIGHT = GetTrafficLight(x.Sum(y => y.TEST))
})
.ToList();
I should note that your sample data makes it so that the results are the same regardless of 'aggregate' setting. But if you change it a bit, such as changing the days for some of the entries, you'll get different results in the aggregation. And at least for the changes I made my code repeats the behavior of your code when toggling 'aggregate'.
need some help with my foreach to recongonize the empty record and fill the gap with "-";
let's say we have 30min, 45min, 90min, 120min and not 60min in the case:
It can count the total records of each id, let's say the maximum is 5, 30min, 45min, 60min, 90min and 120min.
if there are 3, then it could check which is missing and than can fill with "-".
Same ideia of the script.
List<Treatment> treatment = new List<Treatment>();
treatment.Add(new Treatment { id = 1, treatmentNameId = 11, duration = "30", price = 30 });
treatment.Add(new Treatment { id = 1, treatmentNameId = 11, duration = "45", price = 45 });
treatment.Add(new Treatment { id = 1, treatmentNameId = 11, duration = "60", price = 60 });
treatment.Add(new Treatment { id = 1, treatmentNameId = 2, duration = "30", price = 30 });
//treatment.Add(new Treatment { id = 1, treatmentNameId = 2, duration = "45", price = 45 });
treatment.Add(new Treatment { id = 1, treatmentNameId = 2, duration = "60", price = 60 });
var newList = (from t in treatment
select t)
.AsQueryable().ToList();
List<List> newList= new List<List>();
foreach (var item in newList)
{
if (item.duration == "30")
{
newList.Add(new List { treatmentNameId = item.treatmentNameId, thirtyMin = "30" });
}
if (item.duration == "45")
{
newList.Add(new List { treatmentNameId = item.treatmentNameId, fortyFive= "45" });
}
if (item.duration == "60")
{
newList.Add(new List { treatmentNameId = item.treatmentNameId, sixty= "60" });
}
}
The end result should like something as,
id:1 30, 45, 60, -
id:2 30, - , 60, 90
id:3 - , 45, -, 90
etc...
Many many thanks for the help.
I'm not going to provide a very detailed explanation of all this, but conceptually I'm just grouping by treatmentNameId and then doing something very similar to an outer join in SQL. If the group join comes up empty for the duration, I'm selecting '-' instead of the duration. Using anonymous types for brevity.
var durations = new[] {"15", "30", "45", "60"};
var results = treatments
.GroupBy(t => t.treatmentNameId).Select(group => new
{
treatmentNameId = group.Key,
durations = durations.GroupJoin(group, s => s, g => g.duration,
(s, grouping) => !grouping.Any() ? "-" : s)
});
foreach (var result in results)
{
Console.WriteLine(result.treatmentNameId + ": " + string.Join(", ", result.durations));
}
This results in:
11: -, 30, 45, 60
2: -, 30, -, 60
If you have problems with something more specific, I'd be happy to explain further.
Data:
I want to order data like the image above:
var expiredDate = DateTime.Now.AddDays(-6);
var query=*;
var first=query.Search(o=>o.PreorderTime<expiredDate&&(o.TotalMoney-o.MoneyPaid)>0); // this on the top
var second=query.Search(o=>o.PreorderTime>=expiredDate&&(o.TotalMoney-o.MoneyPaid)>0);
var third=query.Search(o=>(o.TotalMoney-o.MoneyPaid)<=0);
var left= query.Search(o=>!first.Contains(o)&&!second.Contains(o)&&!third.Contains(o));
var all = first.Concat(second).Concat(third).Concat(left);
var result=all.AsEnumerable().Select((item, index) => new{...,Index=index}).Take(pagesize).OrderBy(o=>o.Index).Skip(pagesize*(pageindex-1));
I test the result ,I can get first page, but after second page,no data.I don't know why.
Is there a smart way to order this data?
Something like this should work (hope you can manage the actual projection)
var query = new[]
{
new { Id = 1, PreorderTime = new DateTime(2015, 11, 11), Name = "Jim", MoneyPaid = 0, TotalMoney = 5000 },
new { Id = 2, PreorderTime = new DateTime(2015, 11, 09), Name = "Sim", MoneyPaid = 500, TotalMoney = 5000 },
new { Id = 3, PreorderTime = new DateTime(2015, 11, 10), Name = "Sim", MoneyPaid = 1100, TotalMoney = 5000 },
new { Id = 4, PreorderTime = new DateTime(2015, 11, 19), Name = "Long", MoneyPaid = 3200, TotalMoney = 5000 },
new { Id = 5, PreorderTime = new DateTime(2015, 11, 29), Name = "Long", MoneyPaid = 200, TotalMoney = 5000 },
new { Id = 6, PreorderTime = new DateTime(2015, 11, 08), Name = "Long", MoneyPaid = 5000, TotalMoney = 5000 },
new { Id = 7, PreorderTime = new DateTime(2015, 11, 28), Name = "Long", MoneyPaid = 5000, TotalMoney = 5000 },
};
var expiredDate = DateTime.Now.AddDays(-6);
int pageSize = 3;
int pageCount = (query.Length + pageSize - 1) / pageSize;
for (int pageIndex = 1; pageIndex <= pageCount; pageIndex++)
{
var page = query
.OrderBy(o => o.TotalMoney - o.MoneyPaid > 0 ? o.PreorderTime < expiredDate ? 0 : 1 : 2)
.ThenByDescending(o => o.PreorderTime)
.Skip(pageSize * (pageIndex - 1))
.Take(pageSize)
//.Select(...)
.ToList();
}
I'm new in linq and read some stuff on the web about them.
Now, below is a query works fine which is to calculate the project 12-month running balance from the current date. Is it possible to translate this to linq?
It would help me understand the linq better.
var firstDayMonth = new DateTimeOffset(new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1));
var months = Enumerable.Range(0, 12)
.Select(m => firstDayMonth.AddMonths(m));
List<SomeDate> SomeDates = new List<SomeDate>()
{
new SomeDate { Id = 7, Month = firstDayMonth.AddMonths(0), Balance = 1m },
new SomeDate { Id = 7, Month = firstDayMonth.AddMonths(0), Balance = 3m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 6m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 5m },
new SomeDate { Id = 8, Month = firstDayMonth.AddMonths(1), Balance = 3m },
new SomeDate { Id = 9, Month = firstDayMonth.AddMonths(2), Balance = 5m },
new SomeDate { Id = 10, Month = firstDayMonth.AddMonths(3), Balance = 3m },
new SomeDate { Id = 12, Month = firstDayMonth.AddMonths(5), Balance = 15m },
new SomeDate { Id = 13, Month = firstDayMonth.AddMonths(6), Balance = 16m },
new SomeDate { Id = 13, Month = firstDayMonth.AddMonths(6), Balance = 12m },
};
var projected12MonthsBalance = new List<SomeDate>();
foreach(var month in months)
{
projected12MonthsBalance.Add(new SomeDate { Month = month, Balance = SomeDates.TakeWhile(s => s.Month <= month).Sum(s => s.Balance) });
}
Console.WriteLine(projected12MonthsBalance);
public class SomeDate
{
public int Id { get; set; }
public DateTimeOffset Month { get; set; }
public decimal Balance { get; set; }
}
Try this:
var projected12MonthsBalance = months.Select(x => new SomeDate
{
Month = x,
Balance = SomeDates.TakeWhile(s => s.Month <= x).Sum(s => s.Balance)
}).ToList();
I have a calendar app where you select various combinations of products- a service goes out and gets the available dates based on the calendar date range. A date is only "Available" if ALL selected products are available on a particular date.
class SelectedProduct
{
public int ID { get; set; }
public int Qty { get; set; }
}
class AvailableInventory
{
public int ID { get; set; }
public DateTime Date { get; set; }
}
// List of selected products from user
List<SelectedProduct> SelectedProducts;
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory;
I want to be able to say get list of Available Inventory for each date that contains inventory for all ID's in selected products.
This is (non-working) pusdo code of a possible solution, I just don't know linq well enough to get it right
var results = List<AvailableInventory>();
foreach (var group in AvailableInventory.GroupBy(x => x.Date))
{
if (group.Contains(ALL ID's in SelectedProducts)
{
results.AddRange(group);
}
}
This groups inventory by date (ignoring the date portion), then selects only those groups that contain all selected product IDs, and finally selects all available inventory for the matching groups.
var results =
AvailableInventory.GroupBy(i => i.Date.Date)
.Where(g => !SelectedProducts.Select(p => p.ID)
.Except(g.Select(i => i.ID))
.Any())
.SelectMany(g => g);
The result is a collection of AvailableInventory.
You can group by the date, then filter out groups that don't have all the SelectedProducts.
// List of selected products from user
List<SelectedProduct> SelectedProducts = new List<SelectedProduct> {
new SelectedProduct { ID = 1, Qty = 1 },
new SelectedProduct { ID = 2, Qty = 2 },
};
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory = new List<AvailableInventory> {
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 11) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 11) },
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 12) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 13) },
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 14) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 14) },
};
var query = AvailableInventory.GroupBy(i => i.Date)
.Where(g => SelectedProducts.All(s => g.Any(i => i.ID == s.ID)));
foreach(var group in query)
{
Console.WriteLine("Date: {0}", group.Key);
foreach(var inventory in group)
{
Console.WriteLine(" Available: {0}", inventory.ID);
}
}
This would output:
Date: 4/11/2014 12:00:00 AM
Available: 1
Available: 2
Date: 4/14/2014 12:00:00 AM
Available: 1
Available: 2
I think this is what you are looking for. Try this
var result = AvailableInventory.Where(i => SelectedProducts.Any(x => x.ID == i.ID)).GroupBy(o => o.Date)
.Select(g => g.First()).ToList();
This is the test data I used based on your class definition for AvailableInventory and SelectedProduct
// List of selected products from user
List<SelectedProduct> SelectedProducts = new List<SelectedProduct> {
new SelectedProduct { ID = 1, Qty = 2 },
new SelectedProduct { ID = 2, Qty = 4 },
new SelectedProduct { ID = 5, Qty = 10 }
};
// populated from service with all dates for all products
List<AvailableInventory> AvailableInventory = new List<AvailableInventory> {
new AvailableInventory { ID = 1, Date = new DateTime(2014, 04, 01) },
new AvailableInventory { ID = 2, Date = new DateTime(2014, 04, 02) },
new AvailableInventory { ID = 3, Date = new DateTime(2014, 04, 02) },
new AvailableInventory { ID = 4, Date = new DateTime(2014, 04, 10) },
new AvailableInventory { ID = 5, Date = new DateTime(2014, 04, 10) }
};
This should give you only the records with ID = 1, ID = 2 and ID = 5 because that's what common between both AvailableInventory and SelectedProducts lists.
It would help if you actually tried something.
Given this:
List<SelectedProduct> SelectedProducts ;
List<AvailableInventory> AvailableInventory ;
Something like this will probably get what you want:
int[] DatesWithAllSelectedProductsAvailable =
AvailableInventory
.GroupBy( x => x.Date )
.Where ( g => g.All( x => SelectedProducts.Any( p => p.ID == x.ID ) ) )
.Select( x => x.Key )
.Distinct()
.OrderBy( x => x )
.ToArray()
;