This is the code graciously helped out by another community member.
require 'date'
today = Date.today
hires = [
{ name: 'Jerry', date: Date.new(2021, 8, 15) },
{ name: 'Berry', date: Date.new(2021, 8, 13) },
{ name: 'Jack', date: Date.new(2021, 8, 19) }
]
def report(hires, today)
hires.each do |hire|
puts(
format(
'%s , needs vacation',
hire[:name],
hire[:date],
today - hire[:date]
)
)
end
end
report(hires,today)
This code will output
Jerry was hired on 2021-05-13, which is X days ago
Berry was hired on 2021-05-06, which is X days ago # The X's are examples :)
Jack was hired on 2021-05-17, which is X days ago
Basically what I would like it to do is run the code, and it only show me people who have been hired 91-97 days ago from todays date, and output their name. So they output would preferably post something like.
Jerry, needs vacation
Berry, needs vacation
# it would only say Jerry and Berry Because Jack had not rolled over yet.
Thank you for your help!
You can use the 'select' enumerator for arrays
https://ruby-doc.org/core-3.0.2/Enumerable.html#method-i-select
require 'date'
VACATION_INTERVAL = (91..97).freeze
hires = [
{ name: 'Jerry', date: Date.new(2021, 8, 15) },
{ name: 'Berry', date: Date.new(2021, 8, 13) },
{ name: 'Jack', date: Date.new(2021, 8, 19) }
]
def report(hires, today)
hires = hires.select do |hire|
# Do not use "!" after select
# Unless you want to keep the variable value after method's run
day_interval = (today - hire[:date]).to_i # The default return is Rational
VACATION_INTERVAL.include?(day_interval)
end
hires.each { |hire| puts(format('%s, needs vacation', hire[:name])) }
end
today = Date.today
As it depends on your code context, I just added the 'select' lines, and removed the lines that your 'format' method wasn't using
I changed your "hires" variable to match 91 to 97 days of interval from today to give you the following example (because for today, August 17th, none matched from your example);
hires = [
{ name: 'Jerry', date: Date.new(2021, 8, 15) },
{ name: 'Berry', date: Date.new(2021, 5, 14) },
{ name: 'Jack', date: Date.new(2021, 5, 16) }
]
and executing
report(hires,today)
Outputs;
Berry, needs vacation
Jack, needs vacation
But the method is still returning the array, so be careful with that
You can write that as follows.
require 'date'
def need_vacation(hires, reference_date, days_ago_range)
target_date_range = reference_date - days_ago_range.last..
reference_date-days_ago_range.first
hires.each { |hire| puts "#{hire[:name]} needs a vacation" if
target_date_range.cover?(hire[:date]) }
end
Let's try it.
hires = [
{ name: 'Jerry', date: Date.new(2021, 5, 12) },
{ name: 'Berry', date: Date.new(2021, 5, 21) },
{ name: 'Jack', date: Date.new(2021, 5, 17) }
]
days_ago_range = 91..97
reference_date = Date.new(2021, 8, 17)
#=> #<Date: 2021-08-17 ((2459444j,0s,0n),+0s,2299161j)>
need_vacation(reference_date, days_ago_range)
Jerry needs a vacation
Jack needs a vacation
In the example, target_date_range is found to be 2021-05-12..2021-05-18, a range of Date objects. Computing this range once is preferable to making date calculations for each element of hires, especially when hires is large.
I set reference_date to today's date, rather than to Date.today so that my example will endure beyond midnight tonight.
See Range#cover?.
Related
I have an array of Dates. I need to check if it follows a month sequence, e.g.:
[Mar 2010, Apr 2010, May 2010, Jun 2010, ..., Jan 2012]
Since a Date object should have day, month and year, I want to ignore the day, and just worry about month and year.
I want to get true if there are no months "missing" on the sequence. In other words, after April or the vector ends, or I have a May; after a May either the vector ends or there is a June.
I want to get false if the months are not ordered correctly (from older to newer) or if there are months missing.
I can easily check if the dates are ordered by using the "<" operator. But I'm not sure how to check if there are missing months. How can I do that?
Here's one way
require 'date'
>> dates
=> ["Nov 2010", "Dec 2010", "Jan 2011"]
>> date_objs = dates.map{|d| Date.parse d }
=> [#<Date: 2010-03-01 ((2455257j,0s,0n),+0s,2299161j)...]
>> date_objs.each_cons(2).all?{|d1, d2| d1.next_month == d2 }
=> true
This handles missing months as well:
>> dates = ["Nov 2010", "Dec 2010", "Feb 2011"]
>> date_objs = dates.map{|d| Date.parse(d) }
>> date_objs.each_cons(2).all?{|d1, d2| d1.next_month == d2 }
=> false
require 'date'
ar =["Mar 2010","Apr 2010", "May 2010", "Jun 2010"]
p ar.map{|d| Date.parse(d)}.each_cons(2).all?{|(d1,d2)| (d1 >> 1) == d2} #=> true
How to find the dates which are there in a week or month till date.
days_for_week should return 19,20,21 (assuming current date is 21st)
days_for_month should return 1..21 (assuming current date is 21st)
For the first, you could use Time.now.wday to get the current week day, then minus that will give you the date of beginning of this week.
For the second, it's much simpler, every month begin with 1st, right?
Assuming I'm reading your question correctly...
The second is simple:
def days_for_month
1..Date.today.day
end
The first requires a little algorithm to work back to Saturday:
def days_for_week
days = []
day = Date.today
until day.saturday?
days.unshift(day.day)
day -= 1
end
days
end
Active support provides a lot of useful methods like at_beginning_of_week, at_end_of_week, at_beginning_of_month etc ..
> Date.today.at_beginning_of_week
=> Mon, 20 May 2013
For this particular case, you could do
> (Date.today.at_beginning_of_week..Date.today).map &:day
=> [20, 21]
Similarly
> (Date.today.at_beginning_of_month..Date.today).map &:day
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
or simply
> 1..Date.today.day
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.
I would like to know how to get the current week number from Rails and how do I manipulate it:
Translate the week number into date.
Make an interval based on week number.
Thanks.
Use strftime:
%U - Week number of the year. The week starts with Sunday. (00..53)
%W - Week number of the year. The week starts with Monday. (00..53)
Time.now.strftime("%U").to_i # 43
# Or...
Date.today.strftime("%U").to_i # 43
If you want to add 43 weeks (or days,years,minutes, etc...) to a date, you can use 43.weeks, provided by ActiveSupport:
irb(main):001:0> 43.weeks
=> 301 days
irb(main):002:0> Date.today + 43.weeks
=> Thu, 22 Aug 2013
irb(main):003:0> Date.today + 10.days
=> Sun, 04 Nov 2012
irb(main):004:0> Date.today + 1.years # or 1.year
=> Fri, 25 Oct 2013
irb(main):005:0> Date.today + 5.months
=> Mon, 25 Mar 2013
You are going to want to stay away from strftime("%U") and "%W".
Instead, use Date.cweek.
The problem is, if you ever want to take a week number and convert it to a date, strftime won't give you a value that you can pass back to Date.commercial.
Date.commercial expects a range of values that are 1 based.
Date.strftime("%U|%W") returns a value that is 0 based. You would think you could just +1 it and it would be fine. The problem will hit you at the end of a year when there are 53 weeks. (Like what just happened...)
For example, let's look at the end of Dec 2015 and the results from your two options for getting a week number:
Date.parse("2015-12-31").strftime("%W") = 52
Date.parse("2015-12-31").cweek = 53
Now, let's look at converting that week number to a date...
Date.commercial(2015, 52, 1) = Mon, 21 Dec 2015
Date.commercial(2015, 53, 1) = Mon, 28 Dec 2015
If you blindly just +1 the value you pass to Date.commercial, you'll end up with an invalid date in other situations:
For example, December 2014:
Date.commercial(2014, 53, 1) = ArgumentError: invalid date
If you ever have to convert that week number back to a date, the only surefire way is to use Date.cweek.
date.commercial([cwyear=-4712[, cweek=1[, cwday=1[, start=Date::ITALY]]]]) → date
Creates a date object denoting the given week date.
The week and the day of week should be a negative
or a positive number (as a relative week/day from the end of year/week when negative).
They should not be zero.
For the interval
require 'date'
def week_dates( week_num )
year = Time.now.year
week_start = Date.commercial( year, week_num, 1 )
week_end = Date.commercial( year, week_num, 7 )
week_start.strftime( "%m/%d/%y" ) + ' - ' + week_end.strftime("%m/%d/%y" )
end
puts week_dates(22)
EG: Input (Week Number): 22
Output: 06/12/08 - 06/19/08
credit: Siep Korteling http://www.ruby-forum.com/topic/125140
Date#cweek seems to get the ISO-8601 week number (a Monday-based week) like %V in strftime (mentioned by #Robban in a comment).
For example, the Monday and the Sunday of the week I'm writing this:
[ Date.new(2015, 7, 13), Date.new(2015, 7, 19) ].map { |date|
date.strftime("U: %U - W: %W - V: %V - cweek: #{date.cweek}")
}
# => ["U: 28 - W: 28 - V: 29 - cweek: 29", "U: 29 - W: 28 - V: 29 - cweek: 29"]
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});