AlexQueue
AlexQueue

Reputation: 6541

Check if value does not exist in array of arrays in Ruby

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.

Upvotes: 2

Views: 2980

Answers (7)

EJAg
EJAg

Reputation: 3298

An alternative without using looping

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

Upvotes: 1

Catnapper
Catnapper

Reputation: 1905

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.

Upvotes: 5

Stefan
Stefan

Reputation: 114138

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

Upvotes: 0

Narfanator
Narfanator

Reputation: 5803

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.

Upvotes: 0

Arup Rakshit
Arup Rakshit

Reputation: 118261

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

Upvotes: 0

tkroman
tkroman

Reputation: 4798

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.

Upvotes: 1

Bram Jetten
Bram Jetten

Reputation: 282

dates_array.each do |date|
  data.push [date, 0] unless data.map(&:first).include? date
end 

This works. Perhaps someone can improve upon it.

Upvotes: 1

Related Questions