2

I have some data for a some graphs arranged in an array of arrays that looks like:

[[date1, value1], [date2, value2], [date3, value3]]
i.e. [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11]]

My problem is that my graphing utility isn't graphing any data for "6-02-13", while I would like it to graph 0.

I have another array of all the valid dates, e.g. ["6-01-13", "6-02-13", "6-03-13", ...]

What's the best way to insert [date, 0] into my data array for all dates that aren't already present in my data array? I don't care about the array's ordering.

I figured I'd do something along the lines of:

dates_array.each do |date|
  unless data_array.has_date(date)
    data_array.push([date, 0])
  end
end

But I can't think of how this has_date(date) method should work without looping through all the dates and checking that that date is represented in my data array (which would naively be a loop of loops and therefore not ideal).

edit: Existing data (and dates) are pulled from the database as arrays.

7 Answers 7

5

Convert your array of arrays into a hash with a default value of zero:

def data_to_hash(data)
  Hash.new(0).merge(Hash[data])
end

data = [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11]]

hashed_data = data_to_hash(data)
p hashed_data['6-02-13']
p hashed_data['6-01-13']

Output:

0
5

Any date not in the array will return 0. Using a hash as your data structure is much faster on large data sets than iterating through the array looking for a date.

Answer update

To make the invalid keys "stick" to the hash, the block variant of Hash#new may be used:

def data_to_hash(data)
  Hash.new { |h,k| h[k] = 0 }.merge(Hash[data])
end

data = [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11]]
hashed_data = data_to_hash(data)

p hashed_data
%w{6-03-13 7-8-99}.each do |d|
  p hashed_data[d]
end
p hashed_data

Output:

{"6-01-13"=>5, "6-03-13"=>2, "6-04-13"=>11}
2
0
{"6-01-13"=>5, "6-03-13"=>2, "6-04-13"=>11, "7-8-99"=>0}

In this example, 7-8-99 is not present in the original data set, but is set to 0 when that key is accessed.

2
  • This is great but doesn't work in my specific (not-stated-in-question) use case, because when I pass the hash as a JSON it doesn't read the default values. That was a fault of me simplifying the question, not your thorough and otherwise correct answer.
    – AlexQueue
    Commented Jun 8, 2013 at 2:34
  • Updated the answer so that default values are set in the hash upon first access.
    – Catnapper
    Commented Jun 8, 2013 at 3:26
1
dates_array.each do |date|
  data.push [date, 0] unless data.map(&:first).include? date
end 

This works. Perhaps someone can improve upon it.

4
  • Picking this one for A) its simplicity, and B) not needing to convert to/from hashes.
    – AlexQueue
    Commented Jun 7, 2013 at 20:54
  • 3
    This creates the exact same array of dates, dates_array.size times. You could select the zero_dates in one go with zero_dates = dates - data.map(&:first) and then just add those to the data.
    – steenslag
    Commented Jun 7, 2013 at 21:49
  • data.flatten.include? date may be faster than mapping it
    – Narfanator
    Commented Jun 7, 2013 at 22:23
  • data.first_index{|i| i[0] == date} may be faster still
    – Narfanator
    Commented Jun 7, 2013 at 22:31
1

If you know your date ranges, you can just pre-fill an array with all the dates of that range and provide a default value of zero to them. array = Array.new(31) {['date', 0]}

Just provide actual info instead of date.

Or do it this way: (date..date+31).to_a.map!(&:to_s).zip([0]*32)

If you provide me with the way you get existing date items, I'd make something more suitable for you, I suppose.

1

An alternative without using looping

dates_present = data.map(&:first)
dates_missing = dates_array - dates_present
data += dates_missing.map { |date| [date, 0] }
0
0

You can use the below strategy :

dates_array = [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11]]
dates_array.push(["16-03-13",0]) unless dates_array.find{|i,j| i == "16-03-13"}
dates_array # => [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11], ["16-03-13", 0]]

Here is the complete approach:

def date_check(arr,date)
  arr.push(["16-03-13",0]) unless arr.find{|i,j| i == date}
end

dates_array = [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11]]
date_check(dates_array,"16-03-13") # => [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11], ["16-03-13", 0]]
date_check(dates_array,"6-01-13") # => nil
0

Make sure you get your data sorted from the database, or dates_array.sort!{|a,b| Date.parse(a[0]) <=> Date.parse(b[0])}

(Date.parse(dates_array.first[0])..Date.parse(dates_array.last[0])).collect do |date|
  dates_array.find{|i| i[0] == date} || [date, 0]
end 

I didn't like the accepted answer b/c it requires an existing array.

0
data = [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11]]
valid_dates = ["6-01-13", "6-02-13", "6-03-13"]

data + ( valid_dates - data.map(&:first) ).map { |d| [d, 0] }
#=> [["6-01-13", 5], ["6-03-13", 2], ["6-04-13", 11], ["6-02-13", 0]]

Explanation

  • data.map(&:first) returns just the dates, i.e. ["6-01-13", "6-03-13", "6-04-13"]
  • valid_dates - … calculates the difference, i.e. the missing dates ["6-02-13"]
  • .map { |d| [d, 0] } converts these into [<date>, 0] pairs
  • data + … concatenates the data array and the missing date pairs array

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.