1

I am working on iteration: time targeting section of this (the Event Manager exercise from Jumpstart Lab). I figured out how to iterate over the array and turn it into a hash from this thread. This is what I have:

require "date"

    time = ["11/12/08 10:47","11/12/08 13:23","11/12/08 13:30","11/12/08 14:04","11/12/08 14:46"]

    #makes array of all times
    time.each do |time|
        @array = Array(DateTime.strptime(time,'%m/%d/%Y %H:%M').hour)
    end


    #makes hash of :time => number of instances
     result = Hash.new(0)
      @array.each do |time|
        result[time] += 1  
      end
     puts result

The hash I get when I run it is { 14 => 1 }, which is not what I'm looking for. I suspect this happens because my array is made of integers, not strings like the example. Is there a way to accomplish the same effect on integers? Or do I have to convert my integers to strings? If I have to convert to strings, where do I put to_s?

Community
  • 1
  • 1
kaxla
  • 47
  • 7

3 Answers3

2

This logic is incorrect:

time.each do |time|
    @array = Array(DateTime.strptime(time,'%m/%d/%Y %H:%M').hour)
end

The body of the loop is replacing the contents of @array every time, so you only get the last element when you're done. Also, using time as the inner variable name is confusing (and destructive in older versions of Ruby).

What you want is to append to the array with << inside the loop, instead of assigning to it with =:

@array = []
time.each do |t|
     @array << DateTime.strptime(t, '%m/%d/%Y %H:%M').hour
end

But that's a very procedural way of building an array, and not very Rubyish. The idiomatic way would be to use map to construct the new array all at once:

@array = time.map { |t| DateTime.strptime(t, '%m/%d/%Y %H:%M').hour }

As a side note, I'm not sure why you decided to make @array an instance variable. You could just call it array; using @ for arrays is a Perl thing, not Ruby.

Anyway, once you fix the creation of @array, your logic for building the count Hash should work as-is. You could, however, build it in a similar way by using reduce; this is just one possibility:

result = @array.reduce({}) { |h, t| h.merge({t => h[t]+1}) }

You can further simplify the logic by using a built-in method of Ruby arrays called group_by. This call:

time.group_by { |t| DateTime.strptime(t, '%m/%d/%Y %H:%M').hour }

returns this Hash:

{10=>["11/12/08 10:47"], 13=>["11/12/08 13:23", "11/12/08 13:30"], 14=>["11/12/08 14:04", "11/12/08 14:46"]}

That's close to what you want in result; all you have to do is replace those array values with their lengths. Fortunately, map works on Hashes, too, but what it returns is an array of arrays instead of another Hash, so you have to convert it back when you're done. This will do the trick:

result = Hash[time.group_by { |t| DateTime.strptime(t, '%m/%d/%Y %H:%M').hour }.map{|k,v| [k, v.length]}]
Mark Reed
  • 91,912
  • 16
  • 138
  • 175
1

You could get the result with one line:

result = Hash[time.group_by{|str| DateTime.strptime(str,'%m/%d/%Y %H:%M').hour}.map{|k,v| [k, v.count]}]

Explanation:

Actually, there are three steps:

> step_1 = time.group_by{|str| DateTime.strptime(str,'%m/%d/%Y %H:%M').hour}
=> {10=>["11/12/08 10:47"], 13=>["11/12/08 13:23", "11/12/08 13:30"], 14=>["11/12/08 14:04", "11/12/08 14:46"]}

> step_2 = step_1.map{|k,v| [k, v.count]}
=> [[10, 1], [13, 2], [14, 2]]

> step_3 = Hash[step_2]
=> {10=>1, 13=>2, 14=>2}
xdazz
  • 158,678
  • 38
  • 247
  • 274
1

It is important to choose the right data structure for the job. In this case, the right data structure is not a Hash, it is a Multiset. [Unfortunately, there is no Multiset in the standard library or the core library, but there is a multiset gem.]

require 'date'
require 'multiset'

time = [
  '11/12/08 10:47', 
  '11/12/08 13:23', 
  '11/12/08 13:30', 
  '11/12/08 14:04', 
  '11/12/08 14:46'
]

Multiset[*time.map {|t| DateTime.strptime(t, '%m/%d/%Y %H:%M').hour }]
# => #<Multiset:#1 10, #2 13, #2 14>

As you can see, there is one entry at the tenth hour, and two entries each at the 13th and 14th hour.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653