Linq Query - get current month plus previous months - linq

I need to build a Linq query that will show the results as follow:
Data:
Sales Month
----------------------
10 January
20 February
30 March
40 April
50 May
60 June
70 July
80 August
90 September
100 October
110 November
120 December
I need to get the results based on this scenario:
month x = month x + previous month
that will result in:
Sales Month
--------------------
10 January
30 February (30 = February 20 + January 10)
60 March (60 = March 30 + February 30)
100 April (100 = April 40 + March 60)
.........
Any help how to build this query ?
Thanks a lot!

Since you wanted it in LINQ...
void Main()
{
List<SaleCount> sales = new List<SaleCount>() {
new SaleCount() { Sales = 10, Month = 1 },
new SaleCount() { Sales = 20, Month = 2 },
new SaleCount() { Sales = 30, Month = 3 },
new SaleCount() { Sales = 40, Month = 4 },
...
};
var query = sales.Select ((s, i) => new
{
CurrentMonth = s.Month,
CurrentAndPreviousSales = s.Sales + sales.Take(i).Sum(sa => sa.Sales)
});
}
public class SaleCount
{
public int Sales { get; set; }
public int Month { get; set; }
}
...but in my opinion, this is a case where coming up with some fancy LINQ isn't going to be as clear as just writing out the code that the LINQ query is going to generate. This also doesn't scale. For example, including multiple years worth of data gets even more hairy when it wouldn't have to if it was just written out the "old fashioned way".

If you don't want add up all of the previous sales for each month, you will have to keep track of the total sales somehow. The Aggregate function works okay for this because we can build a list and use its last element as the current total for calculating the next element.
var sales = Enumerable.Range(1,12).Select(x => x * 10).ToList();
var sums = sales.Aggregate(new List<int>(), (list, sale) => list.Concat(new List<int>{list.LastOrDefault() + sale});

Related

Finding if a date is on a range of two weekends between another date

I'm making a application that give discounts for users and one of the discounts is given on his birthday. As is really hard to use a discount only on your birthday, we decide to give the discount all the week, including both weekends around.
Some rules
Given the birth date, the discount will start on the previous friday of the birthday;
The discount will end 10 days after the start, on the sunday;
If the birthday it's on a friday, still get the last friday before this one;
Ok, so we need a method that receive the birthdayDate and the todayDate and return true or false telling if the date is on the birthday range.
The problem
Everything seens fine until you start to look at the dates near the change of the year.
If your birthday is on December 31 of 2018, you can use the discount on January 6 of 2019, the same way, if your birthday is on January 1 of 2019, on December 28 of 2018 you already can use the discount.
So look only to the birthday of current year is not enough and, as the days of the week change every year, the last friday of your birthday this year will not be same day on the next one.
There is a elegant way to easily find this range and create the method returning true or false?
The real problem is find which birthday is relevant birthday, as you can receive the discount after your last birthday and before your next birthday.
The original code is in PHP, but as it's a algorithm problem, I can accept answers in any language
Test cases
| Today | Birth | Output |
|-------------------|-------------------|:------:|
| February 15, 2019 | February 22, 2000 | true |
| February 24, 2019 | February 22, 2000 | true |
| February 25, 2019 | February 22, 2000 | false |
| December 28, 2018 | January 03, 2000 | true |
| December 27, 2018 | January 03, 2000 | false |
| December 27, 2019 | January 03, 2000 | true |
| January 01, 2019 | January 03, 2000 | true |
| January 01, 2019 | December 31, 2000 | true |
| January 01, 2019 | December 28, 2000 | false |
Here's some C# code using my Noda Time library that passes all the tests you've specified.
The basic idea is simple:
Go back three months from today
Find the birthday after that date
Construct the discount period around that birthday
Check whether today's date is in that period
The choice of three months is somewhat arbitrary; it's just intended to get around the case where you've just had a birthday. (It's possible that anything more than 10 days and less than 355 days would be fine. I just find 3 months easier to reason about.)
using System;
using NodaTime;
using NodaTime.Text;
class Test
{
static void Main()
{
RunTest("2019-02-15", "2000-02-22", true);
RunTest("2019-02-24", "2000-02-22", true);
RunTest("2019-02-25", "2000-02-22", false);
RunTest("2018-12-28", "2000-01-03", true);
RunTest("2019-01-01", "2000-01-03", true);
RunTest("2018-12-27", "2000-01-03", false);
RunTest("2019-12-27", "2000-01-03", true);
}
static void RunTest(string todayText, string birthdayText, bool expectedResult)
{
var pattern = LocalDatePattern.Iso;
RunTest(pattern.Parse(todayText).Value,
pattern.Parse(birthdayText).Value,
expectedResult);
}
static void RunTest(LocalDate today, LocalDate birthday, bool expectedResult)
{
// Work out "the birthday that comes after 3 months ago".
// That can be:
// - A recent birthday before the closest birthday discount period,
// in which case the *next* birthday will be after the current closest
// discount period
// - A birthday *in* the current closest birthday discount period
// - A birthday *after* the current closest birthday discount period,
// in which case the *previous* birthday was before the current
// closest discount period
LocalDate threeMonthsAgo = today.PlusMonths(-3);
int ageThreeMonthsAgo = Period.Between(birthday, threeMonthsAgo).Years;
// Note: this will use Feb 28th for a Feb 29th birthday in a non leap year.
LocalDate relevantBirthday = birthday.PlusYears(ageThreeMonthsAgo + 1);
// Find the strictly-previous Friday to start the discount interval
LocalDate discountStart = relevantBirthday.With(DateAdjusters.Previous(IsoDayOfWeek.Friday));
LocalDate discountEndInclusive = discountStart.PlusDays(9);
DateInterval discountInterval = new DateInterval(discountStart, discountEndInclusive);
bool actualResult = discountInterval.Contains(today);
Console.WriteLine($"{today} / {birthday} / {(actualResult == expectedResult ? "PASS" : "FAIL")}");
}
}
proposal solution (using JS and timestamps)
// Helper constants
const day = 1000 * 60 * 60 * 24
const friday = 5
const $input = document.getElementById('datepicker')
const $result = document.getElementById('result')
const $tests = document.getElementById('tests')
// Generate the period based on birthday and now (optional)
function getPeriod(birthday, today) {
const now = new Date(today || new Date())
// reset to start at 00:00
now.setHours(0)
now.setMinutes(0)
now.setMilliseconds(0)
// Available beggining date
const begin = new Date(now - (10 * day))
birthday = new Date(birthday)
// fix birthday year
birthday.setFullYear(begin.getFullYear())
// if it already passed, jump to next year
if (birthday < begin)
birthday.setFullYear(birthday.getFullYear() + 1)
// Start date
const start = new Date(birthday)
// if the birthday is already on friday, jump to previous friday (3th condition)
if(start.getDay() === friday)
start.setTime(start.getTime() - (day * 7))
// go to the last friday
while (start.getDay() !== friday)
start.setTime(start.getTime() - day)
// return found date + 10 days
return [start, new Date(start.getTime() + (10 * day)-1)]
}
function calculatePeriod() {
const birthday = $input.value
const [begin, end] = getPeriod(birthday)
$result.innerHTML = begin.toString() + '<br>' + end.toString()
}
const testCases = [
['February 15, 2019', 'February 22, 2000'],
['February 24, 2019', 'February 22, 2000'],
['February 25, 2019', 'February 22, 2000'],
['December 28, 2018', 'January 03, 2000'],
['December 27, 2018', 'January 03, 2000'],
['December 27, 2019', 'January 03, 2000'],
['January 01, 2019 ', 'January 03, 2000'],
['January 01, 2019 ', 'December 31, 2000'],
['January 01, 2019 ', 'December 28, 2000'],
]
testCases.map(([now, birthday]) => {
const [begin, end] = getPeriod(birthday, now)
$tests.innerHTML += `BIRTH: ${birthday}<br>NOW: ${now}<br>BEGIN: ${begin}<br>END : ${end}<br><br>`
})
<h3>Select an date</h3>
<input type="date" id="datepicker" value="2019-01-01"/>
<button onclick="calculatePeriod()">Calculate</button>
<p>Result: <pre id="result">...</pre></p>
<hr />
<h3>Tests</h3>
<pre id="tests"></pre>
Here the same solution from Jon Skeet, but in PHP using Carbon.
use Carbon\Carbon;
class UserBirthdayTest extends TestCase
{
/**
* Setup this test
*/
public function setUp()
{
parent::setUp();
}
/**
* Test create methods on basic CRUDs controllers
*/
public function testCreate()
{
$this->validate("2019-02-15", "2000-02-22", true);
$this->validate("2019-02-24", "2000-02-22", true);
$this->validate("2019-02-25", "2000-02-22", false);
$this->validate("2018-12-28", "2000-01-03", true);
$this->validate("2019-01-01", "2000-01-03", true);
$this->validate("2018-12-27", "2000-01-03", false);
$this->validate("2019-12-27", "2000-01-03", true);
}
/**
* #param \PHPUnit\Framework\TestResult $today
* #param $birthday
* #param $expectedResult
* #return \PHPUnit\Framework\TestResult|void
*/
public function validate($today, $birthday, $expectedResult)
{
$today = new Carbon($today);
$birthday = new Carbon($birthday);
// closest discount period
$threeMonthsAgo = (new Carbon($today))->subMonth(3);
$ageThreeMonthsAgo = $birthday->diffInYears($threeMonthsAgo);
// Note: this will use Feb 28th for a Feb 29th birthday in a non leap year.
$relevantBirthday = $birthday->addYears($ageThreeMonthsAgo + 1);
// Find the strictly-previous Friday to start the discount interval
$discountStart = Carbon::createFromTimestamp(strtotime('last friday', $relevantBirthday->timestamp));
$discountEndInclusive = (new Carbon($discountStart))->addDays(9);
$actualResult = $today->greaterThanOrEqualTo($discountStart) && $today->lessThanOrEqualTo($discountEndInclusive); // between($discountStart, $discountEndInclusive, false);
$this->assertEquals($expectedResult, $actualResult);
}
}

Swift 2.0+ finding the day of the week a month starts

I am using Swift 2.0+ and need it to be compatible with iOS 8.0+. I want to find out what day of the week a month starts. For example, November 15 would return Sunday, December 15 would return Tuesday, and January 16 would return Friday. I think the best approach would be to get the current day of the week and day of the month then count backwards until you reach 1 and get that day. Currently I have
// Returns the day of the week as an integer 1 being Sunday and 7 being Saturday
func dayOfWeek() -> Int {
let components = calender.component(NSCalendarUnit.Weekday, fromDate: t)
return components
}
Which calculates the current day of the week, after that I hit a roadblock.
I found the answer to be
// This function should return the value I need 0 being Sunday and 6 being Saturday
func dayMonthStarted() -> Int {
// 0 -> Sunday 6 -> Saturday
var component = calender.component(NSCalendarUnit.Day, fromDate: date)
var day = dayOfWeek()
while component > 1 {
component--
if day == 0 {
day = 6
} else {
day--
}
}
return day - 1
}

How can I use Linq to get correctly group these results?

I have the following data set:
Year Category Score
2011 A 83
2012 A 86
2013 A 62
2011 B 89
2012 B 86
2013 B 67
2011 C 85
2012 C 73
2013 C 79
2011 D 95
2012 D 78
2013 D 67
I want to transform to the following structure.
categories: [2011, 2012, 2013],
series: [
{ data: [83, 86, 62], name: 'A' },
{ data: [85, 73, 79], name: 'B' },
{ data: [83, 86, 62], name: 'C' },
{ data: [95, 78, 67], name: 'D' }]
I'd like the code to be tolerant of 'missing' data in the source data set. It's a safe assumption that at least 1 of each year and category is represented in the source data.
Example of 'sketchy' data
Year Category Score
2011 A 83
// 2012 A is missing
2013 A 62
// 2011 B is missing
2012 B 86
2013 B 67
2011 C 85
// 2012 C is missing
2013 C 79
2011 D 95
2012 D 78
2013 D 67
Should yield this:
categories: [2011, 2012, 2013],
series: [
{ data: [83, 0, 62], name: 'A' },
{ data: [ 0, 73, 79], name: 'B' },
{ data: [83, 0, 62], name: 'C' },
{ data: [95, 78, 67], name: 'D' }]
Created the following LINQPad code from pastebin code - see notes that follow implementation:
void Main()
{
var scores = new [] {
new CScore { Year = 2011, Category = 'A', Score = 83 },
// 2012 A is missing
new CScore { Year = 2013, Category = 'A', Score = 62 },
// 2011 B is missing
new CScore { Year = 2012, Category = 'B', Score = 86 },
new CScore { Year = 2013, Category = 'B', Score = 67 },
new CScore { Year = 2011, Category = 'C', Score = 85 },
// 2012 C is missing
new CScore { Year = 2013, Category = 'C', Score = 79 },
new CScore { Year = 2011, Category = 'D', Score = 95 },
new CScore { Year = 2012, Category = 'D', Score = 78 },
new CScore { Year = 2013, Category = 'D', Score = 67 },
};
int[] years = scores.Select(i => i.Year).Distinct()
.OrderBy(i=>i).ToArray();
char[] categories = scores.Select(i => i.Category).Distinct()
.OrderBy(i=>i).ToArray();
var series =
from year in years
from cat in categories
join score in scores
on new { Year = year, Category = cat }
equals new { score.Year, score.Category } into scoreGroup
select scoreGroup.SingleOrDefault() ??
new CScore { Year = year, Category = cat } into scoreWithDefault
group scoreWithDefault.Score by scoreWithDefault.Category into g
select new Series { Name = g.Key.ToString(), Data = g.ToArray() };
years.Dump(); // categories
series.Dump(); // series
}
class CScore
{
public char Category {get;set;}
public int Year {get;set;}
public int Score {get;set;}
}
class Series
{
public string Name {get;set;}
public int[] Data {get;set;}
}
Comments
CScore - renamed to avoid naming error I encountered
Sorted distinct items to avoid potential ordering challenges depending on input data.
series query:
The from clauses form a cross product of all category/year combinations.
The join..into permits the default CScore generation for missing years
I chose SingleOrDefault so that IF the input data had more than one matching CScore item on the join, the query would throw an InvalidOperationException indicating that something more ought to be done to deal with the redundancy. I find this preferable to a FirstOrDefault that wouldn't fail in this bad-data/strange-data case.
Omitted Score = 0 in the CScore initializer block since 0 is the default.
select..into query continuation permitted the query to be fed into the group..by that groups the score by the category/name. I really appreciated the null coalesce operator here.
group..by..into g -- the Series type is analogous to the IGrouping<char,int> that I would used if I had stopped with the group-by. Instead the final select projects that IGrouping type to the desired Series type.
I verified the answer in the LINQPad output - and discovered a couple of flaws in the 'should yield this' sample output data. Also, this code executes in about a millisecond on my machine, so unless we have lots more data than this to process, I wouldn't be tempted to improve it.
Despite there being more we could talk about - I'll leave it right there. Hopefully I didn't lose anybody.

EF Linq query comparing data from multiple rows

I would like to create a Linq query that compares date from multiple rows in a single table.
The table consists of data that polls a web-services for balance data for account. Unfortunately the polling interval is not a 100% deterministic which means there can be 0-1-more entries for each account per day.
For the application i would need this data to be reformatted in a certain formatted (see below under output).
I included sample data and descriptions of the table.
Can anybody help me with a EF Linq query that will produce the required output?
table:
id The account id
balance The available credits in the account at the time of the measurement
create_date The datetime when the data was retrieved
Table name:Balances
Field: id (int)
Field: balance (bigint)
Field: create_date (datetime)
sample data:
id balance create_date
3 40 2012-04-02 07:01:00.627
1 55 2012-04-02 13:41:50.427
2 9 2012-04-02 03:41:50.727
1 40 2012-04-02 16:21:50.027
1 49 2012-04-02 16:55:50.127
1 74 2012-04-02 23:41:50.627
1 90 2012-04-02 23:44:50.427
3 3 2012-04-02 23:51:50.827
3 -10 2012-04-03 07:01:00.627
1 0 2012-04-03 13:41:50.427
2 999 2012-04-03 03:41:50.727
1 50 2012-04-03 15:21:50.027
1 49 2012-04-03 16:55:50.127
1 74 2012-04-03 23:41:50.627
2 -10 2012-04-03 07:41:50.727
1 100 2012-04-03 23:44:50.427
3 0 2012-04-03 23:51:50.827
expected output:
id The account id
date The data component which was used to produce the date in the row
balance_last_measurement The balance at the last measurement of the date
difference The difference in balance between the first- and last measurement of the date
On 2012-04-02 id 2 only has 1 measurement which sets the difference value equal to the last(and only) measurement.
id date balance_last_measurement difference
1 2012-04-02 90 35
1 2012-04-03 100 10
2 2012-04-02 9 9
2 2012-04-03 -10 -19
3 2012-04-02 3 -37
3 2012-04-03 0 37
update 2012-04-10 20:06
The answer from Raphaƫl Althaus is really good but i did make a small mistake in the original request. The difference field in the 'expected output' should be either:
the difference between the last measurement of the previous day and the last measurement of the day
if there is no previous day then first measurement of the day should be used and the last measurement
Is this possible at all? It seems to be quite complex?
I would try something like that.
var query = db.Balances
.OrderBy(m => m.Id)
.ThenBy(m => m.CreationDate)
.GroupBy(m => new
{
id = m.Id,
year = SqlFunctions.DatePart("mm", m.CreationDate),
month = SqlFunctions.DatePart("dd", m.CreationDate),
day = SqlFunctions.DatePart("yyyy", m.CreationDate)
}).ToList()//enumerate there, this is what we need from db
.Select(g => new
{
id = g.Key.id,
date = new DateTime(g.Key.year, g.Key.month, g.Key.day),
last_balance = g.Select(m => m.BalanceValue).LastOrDefault(),
difference = (g.Count() == 1 ? g.First().BalanceValue : g.Last().BalanceValue - g.First().BalanceValue)
});
Well, a probable not optimized solution, but just see if it seems to work.
First, we create a result class
public class BalanceResult
{
public int Id { get; set; }
public DateTime CreationDate { get; set; }
public IList<int> BalanceResults { get; set; }
public int Difference { get; set; }
public int LastBalanecResultOfDay {get { return BalanceResults.Last(); }}
public bool HasManyResults {get { return BalanceResults != null && BalanceResults.Count > 1; }}
public int DailyDifference { get { return HasManyResults ? BalanceResults.Last() - BalanceResults.First() : BalanceResults.First(); } }
}
then we change a little bit our query
var query = db.Balances
.GroupBy(m => new
{
id = m.Id,
year = SqlFunctions.DatePart("mm", m.CreationDate),
month = SqlFunctions.DatePart("dd", m.CreationDate),
day = SqlFunctions.DatePart("yyyy", m.CreationDate)
}).ToList()//enumerate there, this is what we need from db
.Select(g => new BalanceResult
{
Id = g.Key.id,
CreationDate = new DateTime(g.Key.year, g.Key.month, g.Key.day),
BalanceResults = g.OrderBy(l => l.CreationDate).Select(l => l.BalanceValue).ToList()
}).ToList();
and finally
foreach (var balanceResult in balanceResults.ToList())
{
var previousDayBalanceResult = balanceResults.FirstOrDefault(m => m.Id == balanceResult.Id && m.CreationDate == balanceResult.CreationDate.AddDays(-1));
balanceResult.Difference = previousDayBalanceResult != null ? balanceResult.LastBalanecResultOfDay - previousDayBalanceResult.LastBalanecResultOfDay : balanceResult.DailyDifference;
}
as indicated, performance (use of dictionaries, for example), code readability should of course be improved, but... that's the idea !

Mac dayofweek issue

Would anyone know why the following code works correctly on Windows and not on Mac??
Today (24/11/2010) should return 47 not 48 as per MacOS
def fm_date = '24/11/2010'
import java.text.SimpleDateFormat
def lPad = {it ->
st = '00' + it.toString()
return st.substring(st.length()-2, st.length())
}
dfm = new SimpleDateFormat("dd/MM/yyyy")
cal=Calendar.getInstance()
cal.setTime( dfm.parse(fm_date) )
now = cal.get(Calendar.WEEK_OF_YEAR)
cal.add(Calendar.DAY_OF_MONTH,-7)
prev = cal.get(Calendar.WEEK_OF_YEAR)
cal.add(Calendar.DAY_OF_MONTH,14)
next = cal.get(Calendar.WEEK_OF_YEAR)
prev = 'diary' + lPad(prev) + '.shtml'
next = 'diary' + lPad(next) + '.shtml'
return 'diary' + lPad(now) + '.shtml'
I believe it's an ISO week number issue...
If I use this code adapted (and groovyfied) from yours:
import java.text.SimpleDateFormat
def fm_date = '24/11/2010'
Calendar.getInstance().with { cal ->
// We want ISO Week numbers
cal.firstDayOfWeek = MONDAY
cal.minimalDaysInFirstWeek = 4
setTime new SimpleDateFormat( 'dd/MM/yyyy' ).parse( fm_date )
now = cal[ WEEK_OF_YEAR ]
}
"diary${"$now".padLeft( 2, '0' )}.shtml"
I get diary47.shtml returned
As the documentation for GregorianCalendar explains, if you want ISO Month numbers:
Values calculated for the WEEK_OF_YEAR
field range from 1 to 53. Week 1 for a
year is the earliest seven day period
starting on getFirstDayOfWeek() that
contains at least
getMinimalDaysInFirstWeek() days from
that year. It thus depends on the
values of getMinimalDaysInFirstWeek(),
getFirstDayOfWeek(), and the day of
the week of January 1. Weeks between
week 1 of one year and week 1 of the
following year are numbered
sequentially from 2 to 52 or 53 (as
needed).
For example, January 1, 1998 was a
Thursday. If getFirstDayOfWeek() is
MONDAY and getMinimalDaysInFirstWeek()
is 4 (these are the values reflecting
ISO 8601 and many national standards),
then week 1 of 1998 starts on December
29, 1997, and ends on January 4, 1998.
If, however, getFirstDayOfWeek() is
SUNDAY, then week 1 of 1998 starts on
January 4, 1998, and ends on January
10, 1998; the first three days of 1998
then are part of week 53 of 1997.
Edit
Even Groovier (from John's comment)
def fm_date = '24/11/2010'
Calendar.getInstance().with { cal ->
// We want ISO Week numbers
cal.firstDayOfWeek = MONDAY
cal.minimalDaysInFirstWeek = 4
cal.time = Date.parse( 'dd/MM/yyyy', fm_date )
now = cal[ WEEK_OF_YEAR ]
}
"diary${"$now".padLeft( 2, '0' )}.shtml"
Edit2
Just ran this on Windows using VirtualBox, and got the same result

Resources