Reputation: 5916
I have a day
object. It has two attributes: name
and magnitude
where name
is the name of the day ("Monday", "Tuesday" ....) while magnitude
is an integer.
I need to build a week
which must always return an array of 7 day
s, one for each day of the week.
However, sometimes, the array I get has less than 7 day
s. I.e.
days = [<Day name: "Monday", magnitude: 4>,
<Day name: "Friday", magnitude: 3>,
<Day name: "Sunday", magnitude: 8>]
So, to build the week I came up with the following code
default = Date::DAYNAMES.rotate(1).map{ |day| Day.new(name: day, magnitude: 0) }
default.map do |day|
days.detect do |other|
other.day == day.day
end || day
end
This works but I find it pretty ugly. I'd rather perform a union between the two arrays. However, I must preserve the order of the day
s in the array. Monday
must always be the first day while Sunday
the last one.
Any ideas on how to improve the code above?
Upvotes: 1
Views: 65
Reputation: 21110
I would go for a similar answer as already given by Cary Swoveland. However I'd use the following:
day_lookup = days.to_h { |day| [day.name, day] }
day_lookup.default_proc = ->(_, name) { Day.new(name: name, magnitude: 0) }
days = day_lookup.values_at(*Date::DAYNAMES.rotate(1))
So, what did I change? If you use Ruby 2.6.0 or greater you can use Array#to_h
with a block, which makes creating a hash from array a lot cleaner in my opinion.
Secondly I only return a default value if a name is not present, but don't set the value in the day_lookup
hash. If you need to access a day multiple times this might be a good idea to do, however in this scenario we only call values_at
once and then we're done with the hash.
Upvotes: 0
Reputation: 11183
You can consider to map against an array of weekdays:
weekdays = %w(Monday Tuesday Wednesday Thursday Friday Saturday Sunday)
Where you can use the constant Date::DAYNAMES
available requiring the date
library.
Then, given your array (I'm not using the class Day
) for example as
days = [ ['Monday', 4], ['Friday', 3], ['Sunday', 8] ]
you can do:
weekdays.map { |weekday| days.find { |e| e.first == weekday } || [weekday, 0] }
#=> [["Monday", 4], ["Tuesday", 0], ["Wednesday", 0], ["Thursday", 0], ["Friday", 3], ["Saturday", 0], ["Sunday", 8]]
Or, using your class:
weekdays.map { |weekday| days.find { |e| e.name == weekday } || Day.new(weekday, 0) }
Upvotes: 0
Reputation: 110675
Data
class Day
attr_accessor :name, :magnitude
def initialize(name, magnitude)
@name = name
@magnitude = magnitude
end
end
days = [["Monday", 4], ["Friday", 3], ["Sunday", 8]].map {|args|
Day.new(*args) }
#=> [#<Day:0x00005a1be9157548 @name="Monday", @magnitude=4>,
# #<Day:0x00005a1be91574d0 @name="Friday", @magnitude=3>,
# #<Day:0x00005a1be91574a8 @name="Sunday", @magnitude=8>]
Code
require 'date'
h = days.each_with_object({}) { |inst,h| h[inst.name] = inst }
#=> {"Monday"=>#<Day:0x00005a1be9157548 @name="Monday", @magnitude=4>,
# "Friday"=>#<Day:0x00005a1be91574d0 @name="Friday", @magnitude=3>,
# "Sunday"=>#<Day:0x00005a1be91574a8 @name="Sunday", @magnitude=8>}
h.default_proc = ->(h,d) { h[d] = Day.new(d,0) }
#=> #<Proc:0x00005a1be93ce858@(irb):26 (lambda)>
h.values_at(*Date::DAYNAMES.rotate(1))
#=> [#<Day:0x00005a1be9157548 @name="Monday", @magnitude=4>,
# #<Day:0x00005a1be93d8ec0 @name="Tuesday", @magnitude=0>,
# #<Day:0x00005a1be93d8e98 @name="Wednesday", @magnitude=0>,
# #<Day:0x00005a1be93d8e70 @name="Thursday", @magnitude=0>,
# #<Day:0x00005a1be91574d0 @name="Friday", @magnitude=3>,
# #<Day:0x00005a1be93d8e48 @name="Saturday", @magnitude=0>,
# #<Day:0x00005a1be91574a8 @name="Sunday", @magnitude=8>]
Explanation
See Hash#default_proc= and Hash#values_at. If h
does not have a key day
the default proc causes h
to return a new Day
object with @name
equal to the value of day
and @magnitude
equal to zero.
The first two expressions could be combined using (for example) Object#tap:
h = days.each_with_object({}) { |inst,h| h[inst.name] = inst }.
tap { |h| h.default_proc = ->(h,d) { h[d] = Day.new(d,0) }
Upvotes: 3