Ruby: convert day as decimal to day as name - ruby

Is it possible to convert quickly a strftime("%u") value to a strftime("%A") or do i need to build an equivalence hash like {"Monday" => 1, ......... "Sunday" => 6}
I have an Array with some day as decimal values
class_index=[2,6,7]
and I would like to loop through this array to build and array of days name like this
[nil, "Tuesday", nil, nil, nil, "Saturday", "Sunday"]
so I could do
class_list=[]
class_index.each do |x|
class_list[x-1] = convert x value to day name
end
Is that even possible?

How about:
require "date"
DateTime.parse("Wednesday").wday # => 3
Oh, I now see you've expanded your question. How about:
[2,6,7].inject(Array.new(7)) { |memo,obj| memo[obj-1] = Date::DAYNAMES[obj%7]; memo }
Let me explain that one:
input = [2,6,7]
empty_array = Array.new(7) # => [nil, nil, nil, nil, nil, nil, nil]
input.inject(empty_array) do |memo, obj| # loop through the input, and
# use the empty array as a 'memo'
day_name = Date::DAYNAMES[obj%7] # get the day's name, modulo 7 (Sunday = 0)
memo[obj-1] = day_name # save the day name in the empty array
memo # return the memo for the next iteration
end
The beauty of Ruby.

To go from decimal to weekday:
require 'date'
Date::DAYNAMES[1]
# => "Monday"
So, in your example, you could simply do:
class_list=[]
class_index.each do |x|
class_list[x-1] = Date::DAYNAMES[x-1]
end

Here’s one way that comes to mind:
require "date"
def weekday_index_to_name(index)
date = Date.parse("2011-09-26") # Canonical Monday.
(index - 1).times { date = date.succ }
date.strftime("%A")
end

class_index=[2,6,7]
class_index.map{|day_num| Date::DAYNAMES[day_num%7]}
#=> ["Tuesday", "Saturday", "Sunday"]
note that day names are from 0 to 6, so you can either work from 0 to 6 or have it modulo 7

Related

Remove nil values from array and corresponding entries from reference array

I have two arrays generated from :
#dividends_values = #dividends.historical.map(&:dividend).reverse.last(50)
#dividends_dates = #dividends.historical.map(&:date).reverse.last(50)
The first array is an array of float values and occasional there can be a few nil entries in that. I want to remove those nil entries(which is pretty easy with a compact or something like that), but I also want to move the corresponding entries from the #dividends_dates array.
That is because they the dates array is a 1-1 reference to the values array, so index 0 of array with dates correspondings to index 0 of array with values.
What is a good way to do that?
First, filter by nil. Then break that up into two arrays.
#last_dividends = #dividends.historical.select { |d| d.dividend }
#dividends_values = #last_dividends.map(&:dividend)
#dividends_dates = #last_dividends.map(&:date)
Better yet, turn them into a single array of [[dividend, date], [...]]
#last_dividends = #dividends
.historical
.select { |d| d.dividend }
.map { |d| [d.dividend, d.date] }
First let's create a class-like object for illustration.
Dividend = Struct.new(:value, :date)
historical = [
Dividend.new(nil, "Jan 1"),
Dividend.new(10, "Mar 22"),
Dividend.new(13, "Apr 21"),
Dividend.new(nil, "Aug 7"),
Dividend.new(8, "Oct 11")
]
#=> [#<struct Dividend value=nil, dade="Jan 1">,
# #<struct Dividend value=10, date="Mar 22">,
# #<struct Dividend value=13, date="Apr 21">,
# #<struct Dividend value=nil, date="Aug 7">,
# #<struct Dividend value=8, date="Oct 11">]
Then, for example,
inst = historical[3]
#=> #<struct Dividend value=nil, date="Aug 7">
inst.value
#=> nil
inst.date
#=> "Aug 7"
We may write
historical.filter_map do |inst|
[inst.value, inst.date] unless inst.value.nil?
end.transpose
#=> [[10, 13, 8], ["Mar 22", "Apr 21", "Oct 11"]]
Note that
historical.filter_map do |inst|
[inst.value, inst.date] unless inst.value.nil?
end
#=> [[10, "Mar 22"], [13, "Apr 21"], [8, "Oct 11"]]
See Enumerable#filter_map.
If you have two arrays with corresponding elements, e.g.:
values = [1, nil, 3]
dates = [Date.new(2022, 5, 1), Date.new(2022, 5, 2), Date.new(2022, 5, 3)]
You can turn them into one combined array of [dividend, date] pairs by using transpose:
values_with_dates = [values, dates].transpose
#=> [[1, #<Date: 2022-05-01>], [nil, #<Date: 2022-05-02>], [3, #<Date: 2022-05-03>]]
You can then remove the elements with value of nil via reject!:
values_with_dates.reject! { |value, date| value.nil? }
#=> [[1, #<Date: 2022-05-01>], [3, #<Date: 2022-05-03>]]
And transpose again to separate the pairs:
values_with_dates.transpose
#=> [[1, 3], [#<Date: 2022-05-01>, #<Date: 2022-05-03>]]
The inner arrays can be assigned back to separate variables using Ruby's multiple assignment.
As a one-liner:
values, dates = [values, dates].transpose.reject { |v, _| v.nil? }.transpose

Convert row of string dates to array of Date objects

I have a 2d array that contains some nil values.
require 'date'
array = [["2014-01-12", "2014-01-12", "2014-01-12"],
["2012-08-26", "2012-10-18", nil],
["2013-04-09", "2013-05-22", "2013-07-01"]]
The desired result is an array of date objects. The resulting array should look like this
(Date objects for display purposes):
changed_array = [#<Date: 2014-01-12 ((2456874j,0s,0n),+0s,2299161j)>, nil, #<Date: 2012-07-31 ((2456874j,0s,0n),+0s,2299161j)>]
I considered something like:
changed_array = array.map { |due| (Date.strptime(due[2], "%Y-%m-%d")) unless (due[2] == nil) }
Any input is appreciated.
EDIT:
As a newbie to coding I would appreciate any input on alternative approaches to this solution!
Similar to Uri's solution, but with only one map
array.map {|*_, d| Date.parse(d) if d}
# => [#<Date: 2014-01-12 ((2456670j,0s,0n),+0s,2299161j)>, nil, #<Date: 2013-07-01 ((2456475j,0s,0n),+0s,2299161j)>]
If you want the last element to be parsed into date you can do the following:
changed_date = array.map(&:last).map { |d| Date.parse(d) if d }
# => [#<Date: 2014-01-12 ((2456670j,0s,0n),+0s,2299161j)>, nil, #<Date: 2013-07-01 ((2456475j,0s,0n),+0s,2299161j)>]
The first map takes only the last element of each array, and the second parses the date, unless it is nil.

Ruby: How to add to new hash of array?

I have the following code and it works fine.
months = {"Feb"=>["day1", "day2"]}
i = 0
while i < 5 do
months["Feb"][i] = "day#{i}"
i += 1
end
puts months
$> {"Feb"=>["day0", "day1", "day2", "day3", "day4"]}
But if I remove the first line where I intialize the hash or try to add values to a different hash key on the fly I get an error that 'months' in undefined.
So I am confused. Will Ruby not allow you to arbitrarily add keys to a hash? I am used to Perl where you can just start making hashes and arrays as you please. But I know that Perl treats hashes & arrays as seperate objects where as Ruby everything is considered the same, so I didn't know if it had something to do with that (although the Perl way is probably "sloppy" ^_^ )
In Ruby you must initialize a variable before using it (looks like a sound policy...). Note also that what you wrote is not idiomatic Ruby, an alternative:
months = {"Feb" => 0.upto(4).map { |i| "day#{i}" }}
To update:
months["Feb"] = 0.upto(4).map { |i| "day#{i}" }
You always need to initialize variables in Ruby.
But you can initialize hashes as following:
# Using {} to creates an empty hash
months = {}
# or create a new empty hash object from Hash class
months = Hash.new
# instead of
months = {"Feb"=>["day1", "day2"]}
To initialize a Array within a Hash:
# Array.new creates a new Array with size 5
# And values within Hash.new block are the default values for the hash
# i.e. When you call the Hash#[], it creates a new array of size 5
months = Hash.new { |hash, key| hash[key] = Array.new(5) }
puts months #=> {}
puts months["Feb"] # Here the Hash creates a new Array inside "Feb" key
puts months #=> {"Feb" => [nil, nil, nil, nil, nil]}
puts months["Feb"][3] = "Day3"
puts months #=> {"Feb" => [nil, nil, nil, "Day3", nil]}
To do the same using an undefined array size:
months = Hash.new { |hash, key| hash[key] = [] }
puts months #=> {}
months["Feb"].push "Day0"
puts months #=> {"Feb" => ["Day0"]}
months["Feb"].push "Day1"
puts months #=> {"Feb" => ["Day0", "Day1"]}
I think a more elegant approach is to use the map method to build your array of days before bind it to "Feb" key:
months = {"Feb" => (0..4).map{ |i| "day#{i}" }}
# => {"Feb"=>["day0", "day1", "day2", "day3", "day4"]}
If you don't want to type the month name you can require the Date class and get the month name passing an Fixnum:
require 'date'
months = {Date::ABBR_MONTHNAMES[2] => (0..4).map{ |i| "day#{i}"}}
# => {"Feb"=>["day0", "day1", "day2", "day3", "day4"]}
To generate the same structure for all months you can do:
days = (0..4).map{ |d| "day#{d}"}
months = (1..12).map{ |m| {Date::ABBR_MONTHNAMES[m] => days }}
# => {"Jan"=>["day0", "day1", "day2", "day3", "day4"],
# "Feb"=>["day0", "day1", "day2", "day3", "day4"],
# ...
# "Dec"=>["day0", "day1", "day2", "day3", "day4"]}
Some useful documentation:
Hash#new
Array
Enumerable#map
Range
Date
# the ||= set this to an empty hash({}) if it hasn't been initialized yet
months ||= {}
#updated as you like, use symbols :feb over string "feb" as the key (read this in other questions in this website)
months[:feb] = (0..4).map {|n| "day#{n}"}
p months #==> {:feb=>["day0", "day1", "day2", "day3", "day4"]}
Based on what you guys said I messed around and it looks like Ruby wants the key to exist before you can start assigning values to that key.
hashname = Hash.new
hashname[:foo] = {}
hashname[:foo][3] = "test"
hashname[:foo][1] = "test1"
hashname[:bar] = {}
hashname[:bar][3] = "test3"
puts hashname
puts hashname[:foo][1]
C:\Ruby>scratch.rb
{:foo=>{3=>"test", 1=>"test1"}, :bar=>{3=>"test3"}}
test1
Once the key exists, you can start assigning values as you please. Although in this case you can see that I am creating a "hash of hashes" instead of a "hash of arrays" as the .map method does which still eludes me as to why.
Thanks for the help!

Changing a few values in a hash

Say I have a hash:
h = {"upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4 }
and I want to get:
{"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 }
so I create a method that takes a hash:
def edge_adjust(hash)
hash["upper_left", nil, "upper_right", nil]
end
but I get the error:
wrong number of arguments (4 for 1)
I know it's giving the elements of the hash one at a time or my method is broke, not sure how to get what I want.
You may want to use merge method to replace first hash values with the values from the second hash:
def edge_adjust(hash)
hash.merge( {"upper_left", nil, "upper_right", nil})
end
edge_adjust({"upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4 })
# returns: {"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 }
Please not that if first hash does not contain some values from the second hash then these values will be created:
edge_adjust({"lower_left", 3, "lower_right", 4 })
# returns: {"upper_left", nil, "upper_right", nil, "lower_left", 3, "lower_right", 4 } as well
Your Hash initialization is wrong. I suppose you want something like:
h = Hash["upper_left", 1, "upper_right", 2, "lower_left", 3, "lower_right", 4]
["upper_left", "upper_right"].each{|k| h[k] = nil}
In this case, Hash#[] is an accessor method, not something that will modify the data. It takes only one argument, the key, and will return the value stored in that location, if any. This is not to be confused with Hash.[] which is a class method to create new hashes.
If you want to mass-assign values to the hash, you have a few options, but the most straight-forward is:
# Spin through a list of keys to remove...
%w[ upper_left upper_right ].each do |k|
# ...and nil out each entry.
h[k] = nil
end
You might also try and use a pattern to zap out any entries you don't want:
# Delete all keys that begin with "upper_"
h.delete_if { |k| k.match(/^upper_/) }
Note that this actually deletes the keys as well, so you can still get nil when fetching, but they are not present in h.keys.

index of first non-nil value in array

What's the best way (in terms of both idiom and efficiency) to find the index of the first non-nil value in an array?
I've come up with first_non_null_index = array.index(array.dup.compact[0])...but is there a better way?
Ruby 1.9 has the find_index method:
ruby-1.9.1-p378 > [nil, nil, false, 5, 10, 20].find_index { |x| not x.nil? } # detect false values
=> 2
ruby-1.9.1-p378 > [nil, nil, false, 5, 10, 20].find_index { |x| x }
=> 3
find_index seems to be available in backports if needed in Ruby earlier than 1.8.7.
I think the best answer is in the question only.
Only change
first_non_null_index = (array.compact.empty?) "No 'Non null' value exist" : array.index(array.dup.compact[0]
Consider following example
array = [nil, nil, nil, nil, nil]
first_non_null_index = array.index(array.dup.compact[0]) #this will return '0' which is wrong

Resources