Compare two hash arrays in ruby except a key - ruby

I have two hash arrays like this:
hashArray1 = [{"id"=>"1","data"=>"data1"},{"id"=>"2","data"=>"data2"}]
hashArray2 = [{"id"=>"3","data"=>"data1"},{"id"=>"4","data"=>"data2"}]
I want to compare both of them and return true if everything else matches without the "id" key.
I have tried something like this:
hashArray1.each do |h1|
hashArray2.each do |h2|
if h1.select{|h| h!= "id"} == h2.select{|b| b!= "id"}
break
else
return false
end
end
end
But this seems to be incorrect. Does anyone have a better solution. I am on plain ruby 1.9.3, not using rails framework.

I would simply do:
hash1.zip(hash2).all? do |h1,h2|
return false unless h1.keys == h1.keys
h1.keys.each do |key|
return false if h1[key] != h2[key] unless key == 'id'
end
end

If hash1.length != hash2.length then you can bail out immediately as they can't be the same. If they have the same length then you could do something like this:
except_id = ->(h) { h.reject { |k, v| k == 'id' } }
same = hash1.zip(hash2).find { |h1, h2| except_id[h1] != except_id[h2] }.nil?
If same is true then they're the same (while ignore 'id's), otherwise they're different. Using Hash#reject is one pure Ruby way to non-destructively look at the Hash without a particular key. You could also use:
except_id = lambda { |h| h = h.dup; h.delete('id'); h }
if "copy and remove" makes more sense to you than filtering. If you don't like find then all? might read better:
same = hash1.zip(hash2).all? { |h1, h2| except_id[h1] == except_id[h2] }
or even:
same_without_id = lambda { |h1, h2| except_id[h1] == except_id[h2] }
same == hash1.zip(hash2).all?(&same_without_id)

The question is not necessarily clear, but I assume the order of the hashes are taken into account.
hash1.map{|h| h.reject{|k, _| k == "id"}} ==
hash2.map{|h| h.reject{|k, _| k == "id"}}

Related

Ruby find atrribute in array of Objects with highest/lowest occurence and return attribute

Hello I am wondering if anyone may be able to give some assistance with two functions I am working on for a Ruby project. I have an array of Objects, and I need to get a certain attribute with the highest and lowest occurrence in each function respectively.
So far I have this, which works, but seems a bit too verbose:
def most_visited_port(time)
most_visited_port_name = ""
most_visited = 0
ending_port_array = []
#ships.each do |ship|
ending_port_array << ship.ending_port
most_visited = ending_port_array.sort.max_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.max_by { |v| ending_port_array.count(v) } ? false : ending_port_array.max_by { |v| ending_port_array.count(v) }
end
#ships.each do |ship|
if time.to_date === ship.time_arrived.to_date && ship.ending_port == most_visited
most_visited_port_name = ship.ending_port_name
end
end
pp most_visited_port_name
end
def least_visited_port(time)
least_visited_port_name = ""
least_visited = 0
ending_port_array = []
#ships.each do |ship|
ending_port_array << ship.ending_port
least_visited = ending_port_array.sort.min_by { |v| ending_port_array.count(v) } != ending_port_array.sort.reverse.min_by { |v| ending_port_array.count(v) } ? false : ending_port_array.min_by { |v| ending_port_array.count(v) }
end
#ships.each do |ship|
if time.to_date === ship.time_arrived.to_date && ship.ending_port == least_visited
least_visited_port_name = ship.ending_port_name
end
end
pp least_visited_port_name
end
Here is a sample of the array of Objects format:
[#<FleetShip:0x0000000108444450
#average_speed=46.02272727272727,
#beginning_port=7,
#beginning_port_name="Summermill",
#distance=81.0,
#ending_port=3,
#ending_port_name="Seamont",
#id=0,
#ship_name="Alpha",
#time_arrived=2016-06-12 08:05:36 -0500,
#time_left=2016-06-12 06:20:00 -0500>,
#<FleetShip:0x0000000108444400
#average_speed=32.01932579334578,
#beginning_port=7,
#beginning_port_name="Summermill",
#distance=81.0,
#ending_port=3,
#ending_port_name="Seamont",
#id=1,
#ship_name="Sea Ghost",
#time_arrived=2016-06-12 11:07:47 -0500,
#time_left=2016-06-12 08:36:00 -0500>]
But could anyone give some assistance on a possibly simpler or more concise way to pull it off?
For fun, you could use each_with_object to build a hash of ending_port values and their frequency, and then retrieve the most frequent.
I'm going to use a much simpler example.
A = Struct.new(:b)
c = [A.new(3), A.new(2), A.new(1), A.new(3), A.new(1), A.new(3), A.new(3)]
most_freq_b = c.each_with_object({}) { |x, h|
h[x.b] ||= 0
h[x.b] += 1
}.max_by(&:last).first
# => 3
This does not account for situations where more than one value for b occurs equal numbers of times. We can tweak it though, to accomplish this.
A = Struct.new(:b)
c = [A.new(3), A.new(2), A.new(1), A.new(3), A.new(1)]
freq = c.each_with_object({}) { |x, h|
h[x.b] ||= 0
h[x.b] += 1
}
highest_freq = freq.values.max
most_freq_b = freq.select { |_, v| v == highest_freq }.keys
# => [3, 1]
Alternatively, we can provide a default value of 0 for the hash, simplifying part of the code.
freq = c.each_with_object(Hash.new(0)) { |x, h|
h[x.b] += 1
}

How do I remove keys from hash that have a bank value while skipping the first key

I'm trying to remove any keys in a hash that has a blank value besides the first key. I'm also trying to do this on one line.
Here is what I have which gives a whole bunch of syntax errors. I keep fiddling with it to try fix the syntax errors but nothing works. Where am I going wrong?
hash.each_with_index do { |(key, value), i| value.blank? && i != 0 ? key.delete : }
One liner:
hash.delete_if.with_index {|(k, v), i| v.nil? && i != 0 }
Using select:
h = { a: '', b: '', c: 1 }
puts h.select.with_index { |subh, i| !(subh[1].to_s.empty?) || i == 0 }
# => {:a=>'', :c=>1}
You should think carefully if you really want to rely on ordering when using a Hash though.

Find the first differing character between two Strings in Ruby

I have two strings.
str_a = "the_quick_brown_fox"
str_b = "the_quick_red_fox"
I want to find the first index at which the two strings differ (i.e. str_a[i] != str_b[i]).
I know I could solve this with something like the following:
def diff_char_index(str_a, str_b)
arr_a, arr_b = str_a.split(""), str_b.split("")
return -1 unless valid_string?(str_a) && valid_string?(str_b)
arr_a.each_index do |i|
return i unless arr_a[i] == arr_b[i]
end
end
def valid_string?(str)
return false unless str.is_a?(String)
return false unless str.size > 0
true
end
diff_char_index(str_a, str_b) # => 10
Is there a better way to do this?
Something like this ought to work:
str_a.each_char.with_index
.find_index {|char, idx| char != str_b[idx] } || str_a.size
Edit: It works: http://ideone.com/Ttwu1x
Edit 2: My original code returned nil if str_a was shorter than str_b. I've updated it to work correctly (it will return str_a.size, so if e.g. the last index in str_a is 3, it will return 4).
Here's another method that may strike some as slightly simpler:
(0...str_a.size).find {|i| str_a[i] != str_b[i] } || str_a.size
http://ideone.com/275cEU
i = 0
i += 1 while str_a[i] and str_a[i] == str_b[i]
i
str_a = "the_quick_brown_dog"
str_b = "the_quick_red_dog"
(0..(1.0)/0).find { |i| (str_a[i] != str_b[i]) || str_a[i].nil? }
#=> 10
str_a = "the_quick_brown_dog"
str_b = "the_quick_brown_dog"
(0..(1.0)/0).find { |i| (str_a[i] != str_b[i]) || str_a[i].nil? }
#=> 19
str_a.size
#=> 19
This uses a binary search to find the index where a slice of str_a no longer occurs at the beginning of str_b:
(0..str_a.length).bsearch { |i| str_b.rindex(str_a[0..i]) != 0 }

Checking multiple params in Ruby

These params come out of html inputs in erb templates (this code is in the main application.rb), and I am checking if they are filled before I add them to n.requestusers, which will become part of a database entry. It works, but it feels more like a bash script the way it is now. What would be the best way to write something like this?
a route in the main .rb
if params[:user2].empty? && params[:user3].empty? && params[:user4].empty? && params[:user5].empty?
n.requestusers = params[:user1]
elsif params[:user3].empty? && params[:user4].empty? && params[:user5].empty?
n.requestusers = params[:user1], params[:user2]
elsif params[:user4].empty? && params[:user5].empty?
n.requestusers = params[:user1], params[:user2], params[:user3]
elsif params[:user5].empty?
n.requestusers = params[:user1], params[:user2], params[:user3], params[:user4]
else
n.requestusers = params[:user1], params[:user2], params[:user3], params[:user4], params[:user5]
end
Instead of having all of those conditional statements might you be interested in something like:
n.requestusers = params.select { |key, val| not val.empty? }.values
Or a cleaner way as suggested by #theTinMan:
n.requestusers = params.reject { |key, val| val.empty? }.values
select lets you take all of the none empty parameter values and returns them. values lets you grab those values as an array.
I am not experienced with web frameworks, so my suggestion is a bit of a shot in the dark.
This isn't tested, because there are no sample values to test against, but, after some refactoring I have:
if [:user2, :user3, :user4, :user5].all?{ |s| params[s].empty? }
n.requestusers = params[:user1]
elsif [:user3, :user4, :user5].all? { |s| params[s].empty? }
n.requestusers = [:user1, :user2].map{ |s| params[s] }
elsif [:user4, :user5].all? { |s| params[s].empty? }
n.requestusers = [:user1, :user2, :user3].map{ |s| params[s] }
elsif params[:user5].empty?
n.requestusers = [:user1, :user2, :user3, :user4].map{ |s| params[s] }
else
n.requestusers = [:user1, :user2, :user3, :user4, :user5].map{ |s| params[s] }
end
Looking at that further, this seems sensible:
USER_LIST = [:user1, :user2, :user3, :user4, :user5]
USER_LIST.size.times do |i|
user_list = USER_LIST
get_users = user_list.shift(1 + i)
if user_list.all?{ |s| params[s].empty? }
n.requestusers = params.values_at(get_users)
break
end
end
Like I said, that's not tested, but I'd work with something along those lines.
Adjust USER_LIST as necessary.
I am not that familiar with Sinatra, but if you wanted to add elements to the array, you can just do (assuming n.requestusers has already been initialized):
n.requestusers << params[:user1] unless params[:user1].empty?
You can do that for each user parameter.
Edit: It would probably be better just to check if the param exists since if it isn't found, nil is returned -- calling empty? on nil throws a NoMethodError. It would probably better to do:
n.requestusers << params[:user1] unless params[:user1]
This would ensure that it adds the param if it exists.

How to find and return a hash value within an array of hashes, given multiple other values in the hash

I have this array of hashes:
results = [
{"day"=>"2012-08-15", "name"=>"John", "calls"=>"5"},
{"day"=>"2012-08-15", "name"=>"Bill", "calls"=>"8"},
{"day"=>"2012-08-16", "name"=>"Bill", "calls"=>"11"},
]
How can I search the results to find how many calls Bill made on the 15th?
After reading the answers to "Ruby easy search for key-value pair in an array of hashes", I think it might involve expanding upon the following find statement:
results.find { |h| h['day'] == '2012-08-15' }['calls']
You're on the right track!
results.find {|i| i["day"] == "2012-08-15" and i["name"] == "Bill"}["calls"]
# => "8"
results.select { |h| h['day'] == '2012-08-15' && h['name'] == 'Bill' }
.reduce(0) { |res,h| res += h['calls'].to_i } #=> 8
A Really clumsy implementation ;)
def get_calls(hash,name,date)
hash.map{|result| result['calls'].to_i if result['day'] == date && result["name"] == name}.compact.reduce(:+)
end
date = "2012-08-15"
name = "Bill"
puts get_calls(results, name, date)
=> 8
Or another possible way, but a little worse, using inject:
results.inject(0) { |number_of_calls, arr_element| arr_element['day'] == '2012-08-15' ? number_of_calls += 1 : number_of_calls += 0 }
Note that you have to set number_of_calls in each iteration, otherwise it will not work, for example this does NOT work:
p results.inject(0) { |number_of_calls, arr_element| number_of_calls += 1 if arr_element['day'] == '2012-08-15'}
Actually, "reduce" or "inject" is specifically for this exact operation (To reduce the contents of an enumerable down into a single value:
results.reduce(0) do |count, value|
count + ( value["name"]=="Bill" && value["day"] == "2012-08-15" ? value["calls"].to_i : 0)
end
Nice writeup here:
"Understanding map and reduce"

Resources