Group By ranges in Linq - c#

Thanks all, I am sorry but I am very beginner. Let me explain it in details: I have a table "theTable" and has two columns "Date" in DateTime and "Value" in double.
What I want as a result: 1.check which date is the minimum 2. Take this as firstdat
I have tried but seems not working
from cf in theTable.
group t by t.Month.Month / 8 + t.Month.Year into g
select new { sum = g.Sum(x=>x.Amount) ,year = g.Key}

You can try to do something like that:
private static void Main(string[] args)
{
var columns = new List<Column>();
var dateRanges = new List<DateRange>()
{
new DateRange(new DateTime(2013, 09, 03), new DateTime(2014, 09, 03))
};
var result = columns.GroupBy(
column => dateRanges.FirstOrDefault(dr => dr.IsInRange(column.Date)),
(dr, drc) => new KeyValuePair<DateRange, int>(dr, drc.Sum(v => v.Value)));
}
public struct DateRange
{
public DateRange(DateTime from, DateTime to)
: this()
{
From = from;
To = to;
}
public bool IsInRange(DateTime date)
{
return date >= From && date <= To;
}
public DateTime From { get; private set; }
public DateTime To { get; private set; }
}
public class Column
{
public int Value { get; set; }
public DateTime Date { get; set; }
}

theTable.GroupBy(t => t.Date.Month / 8 + t.Date.Year)
.Select(g => g.Sum(x = > x.Value));
If you take the range 8/1991-7/1992 for instance they will both end up in the same group:
8 / 8 + 1991 = 7 / 8 + 1992 = 1992

myList.Where(x=> x.Date < lastDate && x.Date > firstDate).Sum(x=>x.Value)
Not sure of the answer because the question is not really clear but this is the sum of all the property "Value" of your list which property "Date" is between a date firstDate and a date LastDate.

Related

Check if Sequence of Year and Month Is Complete (Without Gaps) Given Start and End

The task is pretty easy and I have an iterative solution but I am thinking that there is probably a more efficient of cleaner solution. I have a list of objects which contain a year and a month property. I want to make sure every month from a given start year + month to a given end year + month is covered.
This is my current solution:
for (int year = startYear; year <= endYear; year++)
{
int startM = year == startYear ? startMonth : 1;
int endM = year == endYear ? endMonth : 12;
for (int month = startM; month <= endM; month++)
{
if (!someList.Any(x => x.Year == year && x.Month == month))
throw new Exception("List contains gaps.");
}
}
The extension methods for DateTime below create a time series that can be used to solve your problem by joining with Linq - works for daily gaps as well.
This is a more extensible solution, not necessarily more efficient given it uses Linq vs interation.
Usage
void Main()
{
var startDate = new DateTime(2014, 1, 1);
var months = 36;
//sample w/ 3 month gaps
var monthsWithGaps = Enumerable.Range(0, months).Where(i=> i % 3 == 0)
.Select(h=> startDate.AddMonths(h))
.Dump();
//usage
startDate.GetTimeSlices(monthsWithGaps.Last(), true)
.Where(d=> d.DayOfMonth == 1)
.GroupJoin(monthsWithGaps, slice => slice.Date, set => set, (slice, set) =>
new {
slice.Date,
set
})
.Where(result => !result.set.Any()) //identify gaps
.Count(result => !result.set.Any()) //count gaps
.Dump();
}
Extension Method Implementation
public static class DateTimeExtensions
{
public static List<TimeSlice> GetTimeSlices(this DateTime date, int numberOfDays)
{
int count = 1;
return Enumerable.Range(0, numberOfDays).Select(x => date.AddDays(x)).Select(x => new TimeSlice
{
Date = x.Date,
Year = x.Year,
MonthOfYear = x.Month,
MonthOfSet = ((count - 1) / 30) + 1,
WeekOfSet = ((count - 1) / 7) + 1,
DayOfMonth = x.Day,
DayOfSet = count++
}).ToList();
}
public static List<TimeSlice> GetTimeSlices(this DateTime date, DateTime endDate, bool includeEndDate = true)
{
return GetTimeSlices(date, (endDate - date).Days + (includeEndDate ? 1 : 0));
}
public class TimeSlice
{
public DateTime Date { get; set; }
public int Year { get; set; }
public int MonthOfYear { get; set; }
public int MonthOfSet { get; set; }
public int WeekOfSet { get; set; }
public int DayOfMonth { get; set; }
public int DayOfSet { get; set; }
}
}

Find periods relations in list using LINQ

I have a class which contains date information about period, Start and End:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
}
then I have a List of objects A where I declared a few periods:
List<A> listOfA = new List<A>()
{
new A {Id=1,Name="1", Start = new DateTime (2020,1,1), End = new DateTime(2020,1,20) },
new A {Id=2,Name="2", Start = new DateTime (2020,1,21), End = new DateTime(2020,2,20) },
new A {Id=3,Name="3", Start = new DateTime (2020,5,11), End = new DateTime(2020,5,14) },
new A {Id=4,Name="4", Start = new DateTime (2020,5,15), End = new DateTime(2020,5,20) }
};
I want to find relation (overlapping, containing etc.) betwen given periods and periods in list:
var wrong = new A { Id = 5, Name = "5", Start = new DateTime(2020, 1, 3), End = new DateTime(2020, 4, 20) };
var ok = new A { Id = 6, Name = "6", Start = new DateTime(2020, 4, 3), End = new DateTime(2020, 4, 14) };
In above example wrong object have Start date inside one of the object in list and ok object have no relation. How to find that relation using LINQ?
It's quadratic time complexity and totally untested, however it looks good and that's what counts
var results = list.Where(x =>
list.Any(y =>
x != y &&
(x.Start >= y.Start && x.Start <= y.End ||
x.End <= y.End && x.End >= y.Start)))
.ToList();
Or
Given
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public bool Intersect(A a)
=> this != a && (Start >= a.Start && a.Start <= a.End || End <= a.End && End >= a.Start);
}
Usage
var wrong = list.Where(x => list.Any(x.Intersect)).ToList();
var good = list.Except(wrong).ToList();
You could try :
var wrong2 = listOfA.Any(i => wrong.End > i.Start && wrong.Start < i.End);
var ok2 = listOfA.Any(i => ok.End > i.Start && ok.Start < i.End);
"wrong2" will be true (overlap).
"ok2" will be false (no overlap).
Edit : You should maybe consider to create a "Period" object that would have "StartDate" and "EndDate" and a "IsOverlapping" method. Result would be the same but more readable :-) (See : ValueObject).

How do I return results for one day?

I am trying to create a sales report where the User can select a daterange and get back a sum of sales and a datetime for each day.
I've made a method that takes two datetimes - StartDate and EndDate and loops objects for all days in between those days so that I can return a Date and a TotalSales = 0 even if no sales has been made that day.
I then join this with my GetOrders-query. This works fine if I have a intervall of dates such as 2017-04-01 - 2017-04-30, but if I send in a startdate and a enddate of the same day, my results come back wrong. Any ideas what Im doing wrong?
Classes:
public class Order
{
public int Id { get; set; }
public DateTime OrderDate { get; set; }
public virtual ICollection<OrderItem> OrderItems { get; set; }
}
public class OrderItem
{
public int Id { get; set; }
public decimal UnitPrice { get; set; }
public int Quantity { get; set; }
public int Discount { get; set; }
public virtual Order Order { get; set; }
}
public class DailySalesDto
{
public DateTime Date { get; set; }
public decimal TotalSales { get; set; }
}
Helper method:
public IEnumerable<DateTime> EachDay(DateTime from, DateTime thru)
{
for (var day = from.Date; day.Date <= thru.Date; day = day.AddDays(1))
yield return day;
}
Controller:
public IEnumerable<DailySalesDto> GetOrders(DateTime startDate, DateTime
endDate)
{
var DateRange = new List<DailySalesDto>();
foreach (DateTime day in EachDay(startDate, endDate))
{
DailySalesDto newEmpty = new DailySalesDto()
{
Date = day,
TotalSales = 0
};
DateRange.Add(newEmpty);
}
var salesForPeriod = db.Orders.Where(b => b.OrderDate.Day > startDate.Day && b.OrderDate.Day <= endDate.Day);
var salesByDay = from s in salesForPeriod
group s by s.OrderDate.Day into g
select new { Date = g.Key, totalSales = g.Sum(p => p.OrderItems.Select(x => x.Quantity * x.UnitPrice).Sum()) };
var query = from d in DateRange
join s in salesByDay on d.Date.Day equals s.Date into j
from s in j.DefaultIfEmpty()
select new DailySalesDto { Date = d.Date, TotalSales = (s != null) ? s.totalSales : 0m };
return query.OrderBy(x => x.Date.Day);
}
Results 2017-04-25 - 2017-04-25 (2 orders existing)
/api/SalesVM/?startDate=2017-04-26&endDate=2017-04-26
[
{
"Date": "2017-04-26T00:00:00",
"Day": 0,
"TotalSales": 0
}
]
/api/SalesVM/?startDate=2017-04-25&endDate=2017-04-26
[
{
"Date": "2017-04-25T00:00:00",
"Day": 0,
"TotalSales": 0
},
{
"Date": "2017-04-26T00:00:00",
"Day": 0,
"TotalSales": 247
}
]
use >= for fetching salesForPeriod start date
var salesForPeriod = db.Orders.Where(b => b.OrderDate.Day >= startDate.Day && b.OrderDate.Day <= endDate.Day);

How to optmize linq query for grouping dates by price without merging results

I have this linq query as stated below. The problem is when the data is grouped by price, it groups dates by price without considering a case where same price can occur for nonconsecutive dates
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
//Console.WriteLine("Hello World");
List<Prices> list = new List<Prices>();
list.Add(new Prices() { Date = DateTime.Parse("2017-06-17"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-18"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-19"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-20"), Price = Double.Parse("100")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-21"), Price = Double.Parse("100")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-22"), Price = Double.Parse("100")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-23"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-24"), Price = Double.Parse("50")});
list.Add(new Prices() { Date = DateTime.Parse("2017-06-25"), Price = Double.Parse("50")});
var baseservices = list
.GroupBy(l => l.Price)
.Select(g => new
{
Price = g.Key,
PeriodStart = g.Select(l=>l.Date).Min(),
PeriodEnd = g.Select(l => l.Date).Max(),
});
foreach(var item in baseservices)
{
Console.WriteLine(item.Price + " " + item.PeriodStart + " " + item.PeriodEnd);
}
}
}
public class Prices
{
public DateTime Date {get;set;}
public double Price {get;set;}
}
public class Quote
{
public DateTime PeriodStart {get;set;}
public DateTime PeriodEnd {get;set;}
public double Price {get;set;}
}
The result is
50 6/17/2017 12:00:00 AM 6/25/2017 12:00:00 AM
100 6/20/2017 12:00:00 AM 6/22/2017 12:00:00 AM
How can I get the following result
50 6/17/2017 12:00:00 AM 6/29/2017 12:00:00 AM
100 6/20/2017 12:00:00 AM 6/22/2017 12:00:00 AM
50 6/23/2017 12:00:00 AM 6/25/2017 12:00:00 AM
LINQ is not well suited for such operations. The only standard LINQ operator that can be used to do such processing is Aggregate, but it's no more than LINQ-ish foreach loop:
var baseservices = list
.OrderBy(e => e.Date)
.Aggregate(new List<Quote>(), (r, e) =>
{
if (r.Count > 0 && r[r.Count - 1].Price == e.Price && r[r.Count - 1].PeriodEnd.AddDays(1) == e.Date)
r[r.Count - 1].PeriodEnd = e.Date;
else
r.Add(new Quote { Price = e.Price, PeriodStart = e.Date, PeriodEnd = e.Date });
return r;
});
Note that in contrast with many LINQ methods, this executes immediate and does not return until the whole result is ready.
If you create help class for your DateRange:
public class DateRange
{
public DateTime PeriodStart { get; set; }
public DateTime PeriodEnd { get; set; }
}
and help method to convert your list of dates:
public static IEnumerable<DateRange> Convert(IEnumerable<DateTime> dates)
{
var ret = new DateRange();
foreach (var date in dates)
{
if (ret.PeriodEnd == default(DateTime))
{
ret.PeriodStart = date;
ret.PeriodEnd = date;
}
else if (ret.PeriodEnd.AddDays(1) == date)
{
ret.PeriodEnd = date;
}
else
{
yield return ret;
ret = new DateRange();
}
}
yield return ret;
}
You will be able to sort your dates to periods:
var baseservices = list
.GroupBy(l => l.Price)
.Select(g => new
{
Price = g.Key,
Dates = Convert(g.Select(d=>d.Date)).ToList()
})
.SelectMany(r=>r.Dates, (a,b)=>new Quote {
Price = a.Price,
PeriodStart = b.PeriodStart,
PeriodEnd = b.PeriodEnd})
.ToList();

Fetch month and Year Inside a List using Linq

I have list like this
public class Result
{
public string id { get; set; }
public string name { get; set; }
public string startDate { get; set; } //smaple date 2014-03-31T12:30:03
}
List<Result>
I want to fetch all distinct month comes inside this list .
I have tried something like this
List<string> monthNamesList = eventListResponse.result.Select(s => Convert.ToDateTime(s.startDate).ToString("MMMM")).Distinct().ToList();
And it does the job, Only Problem is that if the list comtains two elements
2014-03-31T12:30:03
2013-03-31T12:30:03
My code will return only one month, where I want to get it like 2014 March and 2013 March.
So I created a new model class with year and month
public class MonthYearMOdel
{
public string month;
public string year;
}
Can any one point out how I can fetch distinct months from my first list and store in List<MonthYearMOdel>.
Where 2014 March and 2013 March both will be stored.
try this :
List<MonthYearMOdel> monthNamesList = eventListResponse.result.Select(s => new
{
M = Convert.ToDateTime(s.startDate).ToString("MMMM"),
Y = Convert.ToDateTime(s.startDate).ToString("yyyy")
})
.Distinct()
.Select(u => new MonthYearMOdel()
{
month = u.M,
year = u.Y,
})
.ToList();
Simple way (each string contains month and year):
List<string> monthNamesList = eventListResponse.result.Select(s => Convert.ToDateTime(s.startDate).ToString("yyyy MMMM")).Distinct().ToList();
With MonthYearModel:
public class MonthYearModel
{
public string month;
public string year;
public MonthYearModel(string dateTime)
{
var date = Convert.ToDateTime(dateTime);
this.month = date.ToString("MMMM");
this.year = date.ToString("yyyy");
}
public bool Equals(object arg)
{
var model = arg as MonthYearModel;
return (model != null) && model.month == this.month && model.year == this.year;
}
public int GetHashCode()
{
return (month.GetHashCode() * 397) ^ year.GetHashCode();
}
}
List<MonthYearModel> = eventListResponse.result.Select(s => new MonthYearModel(s.startDate)).Distinct().ToList();

Categories