Am I able to use each_cons(2).any? with hash values? - ruby

If I have an array of integers and wish to check if a value is less than the previous value. I'm using:
array = [1,2,3,1,5,7]
con = array.each_cons(2).any? { |x,y| y < x }
p con
This returns true, as expected as 1 is less than 3.
How would I go about checking if a hash value is less than the previous hash value?
hash = {"0"=>"1", "1"=>"2","2"=>"3","4"=>"1","5"=>"5","6"=>"7"}
I'm still learning Ruby so help would be greatly appreciated.

If you want to find out whether all elements meet the criteria, starting with an array:
array = [1,2,3,1,5,7]
con = array.each_cons(2).all? { |x,y| x < y }
con # => false
Changing the array so the elements are all less than the next:
array = [1,2,3,4,5,7]
con = array.each_cons(2).all? { |x,y| x < y }
con # => true
A lot of the methods behave similarly for array elements and hashes, so the basic code is the same, how they're passed into the block changes. I reduced the hash to the bare minimum to demonstrate the code:
hash = {"3"=>"3","4"=>"1","5"=>"5"}
con = hash.each_cons(2).all? { |(_, x_value), (_, y_value) | x_value < y_value }
con # => false
Changing the hash to be incrementing:
hash = {"3"=>"3","4"=>"4","5"=>"5"}
con = hash.each_cons(2).all? { |(_, x_value), (_, y_value) | x_value < y_value }
con # => true
Using any? would work the same way. If you want to know whether any are >=:
hash = {"3"=>"3","4"=>"1","5"=>"5"}
con = hash.each_cons(2).any? { |(_, x_value), (_, y_value) | y_value >= x_value }
con # => true
Or:
hash = {"3"=>"3","4"=>"4","5"=>"5"}
con = hash.each_cons(2).any? { |(_, x_value), (_, y_value) | x_value >= y_value }
con # => false
I'm creating the hash by
stripped = Hash[x.scan(/(\w+): (\w+)/).map { |(first, second)| [first.to_i, second.to_i] }]
I'm then removing empty arrays by
new = stripped.delete_if { |elem| elem.flatten.empty? }
This isn't a good way to use scan. Consider these:
'1: 23'.scan(/\d+/) # => ["1", "23"]
'1: 23'.scan(/(\d+)/) # => [["1"], ["23"]]
'1: 23'.scan(/(\d+): (\d+)/) # => [["1", "23"]]
In the first, scan returns an array of values. In the second, it returns an array of arrays, where each sub-array is a single element. In the third it returns an array of arrays, where each sub-array contains both elements scanned. You are using the third form, which unnecessarily complicates everything done after that.
Don't complicate the pattern passed to scan, and, instead, rely on its ability to return multiple matching elements as it looks through the string and to return an array of those:
'1: 23'.scan(/\d+/) # => ["1", "23"]
Build on top of that:
'1: 23'.scan(/\d+/).map(&:to_i) # => [1, 23]
Hash[*'1: 23'.scan(/\d+/).map(&:to_i)] # => {1=>23}
Notice the leading * inside Hash[]. That "splat" tells Ruby to burst or explode the array into its components. Here's what happens if it's not there:
Hash['1: 23'.scan(/\d+/).map(&:to_i)] # => {} # !> this causes ArgumentError in the next release
And, finally, if you don't need the hash elements to be integers, which contradicts the hash you gave in your question, just remove .map(&:to_i) from the examples above.

First, isolate the values from the hash:
values = hash.map { |key, value| value.to_i } #=> [1, 2, 3, 1, 5, 7]
or:
values = hash.values.map(&:to_i) #=> to_i is a shortcut for:
values = hash.values.map { |value| value.to_i }
and then do the same thing you did for your array example.

Related

Create a new hash from an existing array of hashes

I am newbie to ruby . I have an array of hashes input_array
[{
"name"=>"test1",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>1000,
"name"=>"test2",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>5000,
"name"=>"test3",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>3000,
"name"=>"test4",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>2000}]
and an array
existing_names_array = ["test1","test2"]
The below line gets me all the names into input_names
input_array.each_with_index {|val, index| input_names << input_array[index]['name'] }
But how can I get the input_names to be a hash with name as key and its respective users as value?
Because my final goal is to check the names which are in input_names, but not in existing_names_array and get a count of those names and users
As the names tes1,test2 exists in existing_names_array, I need the count of rest of names and count of their respective users in input_array
Expected output:
output_names = test3, test 4
total_output_names = 2
output_users = 3000,2000
total_output_users = 5000
If you use ActiveSupport's Enumerable#index_by with Ruby core's Hash#transform_values it's pretty easy, if I'm understanding your question correctly:
# add gem 'activesupport' to Gemfile, or use Rails, then ..
require 'active_support/all'
users_and_counts = input_array.
index_by { |hsh| hsh["name"] }.
transform_values { |hsh| hsh["users"] }
# => { "test1" => 1000, "test2" => 5000, ... }
You can do this with Enumerable#reduce (or Enumerable#each_with_object) as well:
users_and_counts = input_array.reduce({}) do |memo, hsh|
memo[hsh["name"]] = hsh["users"]
memo
end
Or, the simplest way, with good old each:
users_and_counts = {}
input_array.each do |hsh|
users_and_counts[hsh["name"]] = hsh["users"]
end
in response to comment
In general, there are a few ways to check whether an element is found in an array:
array = ["foo", "bar"]
# 1. The simple standard way
array.include?("foo") # => true
# 2. More efficient way
require 'set'
set = Set.new(array)
set.member?("foo") # => true
So with this knowledge we can break up our task into a few steps:
Make a new hash which is a copy of the one we built above, but without the key-vals corresponding to users in the existing_names_array (see Hash#select and Hash#reject):
require 'set'
existing_names_set = Set.new(existing_names_array)
new_users_and_counts = users_and_counts.reject do |name, count|
existing_names_set.member?(name)
end
# => { "test3" => 3000, "test4" => 2000 }
Use Hash#keys to get the list of user names:
new_user_names = new_users_and_counts.keys
# => ["test3", "test4"]
Use Hash#values to get the list of counts:
new_user_counts = new_users_and_counts.values
# => [3000, 2000]
I assume input_array is to be as follows.
input_array = [
{ "name"=>"test1", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>1000 },
{ "name"=>"test2", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>5000 },
{ "name"=>"test3", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>3000 },
{ "name"=>"test4", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>2000}
]
We are also given:
names = ["test1", "test2"]
The information of interest can be represented nicely by the following hash (which I will construct):
summary = { "test1"=>1000, "test2"=>5000 }
We might use that to compute the following.
names = summary.keys
#=> ["test1", "test2"]
users = summary.values
#=> [1000, 5000]
total_users = users.sum
#=> 6000
There are many ways to construct the hash summary. I prefer the following.
summary = input_array.each_with_object({}) do |g,h|
key = g["name"]
h[key] = g["users"] if names.include?(key)
end
#=> {"test1"=>1000, "test2"=>5000}
Another way is as follows.
summary = input_array.map { |g| g.values_at("name", "users") }.
.to_h
.slice(*names)
See [Hash#values_at(https://ruby-doc.org/core-2.7.0/Hash.html#method-i-values_at), Array#to_h and Hash#slice. The steps are as follows.
arr = input_array.map { |g| g.values_at("name", "users") }
#=> [["test1", 1000], ["test2", 5000], ["test3", 3000], ["test4", 2000]]
h = arr.to_h
#=> {"test1"=>1000, "test2"=>5000, "test3"=>3000, "test4"=>2000}
summary = h.slice(*names)
#=> {"test1"=>1000, "test2"=>5000}
The splat operator converts h.slice(*names), which is h.slice(*["test1", "test2"]), to h.slice("test1", "test2"). That's because Hash#slice was defined to accept a variable number of keys as arguments, rather than an array of keys as the argument.

Merging Three hashes and getting this resultant hash

I have read the xls and have formed these three hashes
hash1=[{'name'=>'Firstname',
'Locator'=>'id=xxx',
'Action'=>'TypeAndWait'},
{'name'=>'Password',
'Locator'=>'id=yyy',
'Action'=>'TypeAndTab'}]
Second Hash
hash2=[{'Test Name'=>'Example',
'TestNumber'=>'Test1'},
{'Test Name'=>'Example',
'TestNumber'=>'Test2'}]
My Thrid Hash
hash3=[{'name'=>'Firstname',
'Test1'=>'four',
'Test2'=>'Five',
'Test3'=>'Six'},
{'name'=>'Password',
'Test1'=>'Vicky',
'Test2'=>'Sujin',
'Test3'=>'Sivaram'}]
Now my resultant hash is
result={"Example"=>
{"Test1"=>
{'Firstname'=>
["id=xxx","four", "TypeAndWait"],
'Password'=>
["id=yyy","Vicky", "TypeAndTab"]},
"Test2"=>
{'Firstname'=>
["id=xxx","Five", "TypeAndWait"],
'Password'=>
["id=yyy","Sujin", "TypeAndTab"]}}}
I have gotten this result, but I had to write 60 lines of code in my program, but I don't think I have to write such a long program when I use Ruby, I strongly believe some easy way to achieve this. Can some one help me?
The second hash determines the which testcase has to be read, for an example, test3 is not present in the second testcase so resultant hash doesn't have test3.
We are given three arrays, which I've renamed arr1, arr2 and arr3. (hash1, hash2 and hash3 are not especially good names for arrays. :-))
arr1 = [{'name'=>'Firstname', 'Locator'=>'id=xxx', 'Action'=>'TypeAndWait'},
{'name'=>'Password', 'Locator'=>'id=yyy', 'Action'=>'TypeAndTab'}]
arr2 = [{'Test Name'=>'Example', 'TestNumber'=>'Test1'},
{'Test Name'=>'Example', 'TestNumber'=>'Test2'}]
arr3=[{'name'=>'Firstname', 'Test1'=>'four', 'Test2'=>'Five', 'Test3'=>'Six'},
{'name'=>'Password', 'Test1'=>'Vicky', 'Test2'=>'Sujin', 'Test3'=>'Sivaram'}]
The drivers are the values "Test1" and "Test2" in the hashes that are elements of arr2. Nothing else in that array is needed, so let's extract those values (of which there could be any number, but here there are just two).
a2 = arr2.map { |h| h['TestNumber'] }
#=> ["Test1", "Test2"]
Next we need to rearrange the information in arr3 by creating a hash whose keys are the elements of a2.
h3 = a2.each_with_object({}) { |test,h|
h[test] = arr3.each_with_object({}) { |f,g| g[f['name']] = f[test] } }
#=> {"Test1"=>{"Firstname"=>"four", "Password"=>"Vicky"},
# "Test2"=>{"Firstname"=>"Five", "Password"=>"Sujin"}}
Next we need to rearrange the content of arr1 by creating a hash whose keys match the keys of values of h3.
h1 = arr1.each_with_object({}) { |g,h| h[g['name']] = g.reject { |k,_| k == 'name' } }
#=> {"Firstname"=>{"Locator"=>"id=xxx", "Action"=>"TypeAndWait"},
# "Password"=>{"Locator"=>"id=yyy", "Action"=>"TypeAndTab"}}
It is now a simple matter of extracting information from these three objects.
{ 'Example'=>
a2.each_with_object({}) do |test,h|
h[test] = h3[test].each_with_object({}) do |(k,v),g|
f = h1[k]
g[k] = [f['Locator'], v, f['Action']]
end
end
}
#=> {"Example"=>
# {"Test1"=>{"Firstname"=>["id=xxx", "four", "TypeAndWait"],
# "Password"=>["id=yyy", "Vicky", "TypeAndTab"]},
# "Test2"=>{"Firstname"=>["id=xxx", "Five", "TypeAndWait"],
# "Password"=>["id=yyy", "Sujin", "TypeAndTab"]}}}
What do you call hash{1-2-3} are arrays in the first place. Also, I am pretty sure you have mistyped hash1#Locator and/or hash3#name. The code below works for this exact data, but it should not be hard to update it to reflect any changes.
hash2.
map(&:values).
group_by(&:shift).
map do |k, v|
[k, v.flatten.map do |k, v|
[k, hash3.map do |h3|
# lookup a hash from hash1
h1 = hash1.find do |h1|
h3['name'].start_with?(h1['Locator'])
end
# can it be nil btw?
[
h1['name'],
[
h3['name'][/.*(?=-id)/],
h3[k],
h1['Action']
]
]
end.to_h]
end.to_h]
end.to_h

Weird behaviour with hashes in Ruby

I have a method that tries to parse a query into a a hash.
CONTACT_SEARCH_FIELDS = ['LastUpdate','Name','RecAdd','PhoneNumber','Tag_Group','FirstName','LastName','FamilyName','FamilyHead','ClientStatus','ContactType','ClientSource','TaxId']
CONTACT_SEARCH_OPERANDS = ['=','>','<','!=','Like','BeginsWith','IsEmpty']
def search (query, page = 1)
body = [{}]*query.length
query.each_with_index do |expr, i|
body[i]["Field"] = CONTACT_SEARCH_FIELDS.index expr[0]
body[i]["Operand"] = CONTACT_SEARCH_OPERANDS.index expr[1]
body[i]["Value"] = expr[2]
end
return body
end
The method is called like this
search([["FirstName", "=", "John"], ["LastName", "=", "Smith"]])
The problem is that running this gives a very weird output.
search([["FirstName", "=", "John"], ["LastName", "=", "Smith"]])
=> [{"Operand"=>0, "Value"=>"Smith", "Field"=>6}, {"Operand"=>0, "Value"=>"Smith", "Field"=>6}]
I did some debugging and the problem is that all the hashes in the array are get set on every iteration.
I dont understand what is the reason behind this. I would also welcome any shorter or better versions of this code.
Change the line
body = [{}]*query.length
The above means, you are creating an Array, whose elements are same Hash objects.
Example :
a = [{}]*3 # => [{}, {}, {}]
a.map(&:object_id) # => [18499356, 18499356, 18499356]
a[0]["a"] = 2
a # => [{"a"=>2}, {"a"=>2}, {"a"=>2}]
to
body = Array.new(query.length) { {} }
But the above means, you are creating an Array, whose elements are different Hash objects.
Example :
a = Array.new(3) { {} } # => [{}, {}, {}]
a.map(&:object_id) # => [17643864, 17643852, 17643840]
a[0]["a"] = 2
a # => [{"a"=>2}, {}, {}]

Consolidate nested arrays and erase the subarrays that have been consolidated?

I'm trying to take a bunch of number-word pairs and group the words according to common numbers. I can match the numbers, merge the subarrays that share the number, and erase the first of those subarrays. But when I try to delete the second, I get this error:
"in block in <main>': undefined method[]' for nil:NilClass (NoMethodError)"
The guilty line -- ary.delete_at(i+1) -- has been commented out. Secondary problem: MRBTree is not taking the nested arrays as input...
ary = [[2.28, "cat"], [2.28, "bat"], [2.327, "bear"], [2.68, "ant"], [2.68, "anu"]]
i = 0
for i in 0 ... ary.size - 1
if ary[i][0] == ary[i+1][0]
b = (ary[i]+ary[i+1]).uniq
ary.delete_at(i)
# ary.delete_at(i+1)
c = [b.first], b.pop(b.length - 1)
h = Hash[*c]
ary.push(*h)
# mrbtree = MultiRBTree[c]
end
end
puts ary.inspect
output:
# => [
# => [2.28, "bat"],
# => [2.327, "bear"],
# => [2.68, "anu"],
# => [
# => [2.28], ["cat", "bat"]
# => ],
# => [
# => [2.68], ["ant", "anu"]
# => ]
# => ]
Any help appreciated!
Your attempt is failing because you are modifying the array (which has impact on a.size) in the loop. The loop end condition is not adjusted automagically. You are accessing things you have deleted before.
If your array is not too big, this will do:
p Hash[ary.group_by(&:first).map { | k, v | [k, v.map(&:last)] }]
# => {2.28=>["cat", "bat"], 2.327=>["bear"], 2.68=>["ant", "anu"]}
It works this way:
ary.group_by(&:first) # group the 2-elem arrays by the number, creating a hash
# like {2.28=>[[2.28, "cat"], [2.28, "bat"]], ...}
.map { | k, v | ... } # change the array of key-value pairs to
[k, v.map(&:last)] # pairs where the value-array contains just the strings
Hash[ ... ] # make the whole thing a hash again
Creating an intermediate array and transferring it back to a hash is some overhead. If this turns out to be an issue, something like this might be better:
h = Hash.new { | a, k | a[k] = [] } # a hash with [] as default value
p ary.inject(h) { | a, (k, v) | a[k] << v; a }
It looks like after
ary.delete_at(i)
the size of array is decreased by one, hence i is better than i+1:
# ary.delete_at(i+1)
ary.delete_at(i)
Alternate version for converting to hash:
ary = [[2.28, "cat"], [2.28, "bat"], [2.327, "bear"], [2.68, "ant"], [2.68, "anu"]]
hsh = {}
ary.each {|pair| hsh[pair[0]].nil? ? hsh[pair[0]] = [pair[1]] : hsh[pair[0]] << pair[1]}
puts hsh.inspect # => {2.28 => ["cat", "bat"], 2.327 => ["bear"], 2.68 => ["ant", "anu"]}

Finding N keys with highest value in hash, keeping order

In a Ruby script,
I have a hash that has sentences as keys and relevance scores as values.
I want to retrieve an array containing the N most relevant sentences (highest scores).
I want to retain the order in which these sentences are extracted.
Given:
hash = {
'This is the first sentence.' => 5,
'This is the second sentence.' => 1,
'This is the last sentence.' => 6
}
Then:
choose_best(hash, 2)
Should return:
['This is the first sentence.', 'This is the last sentence.']
All the methods I can think of involve reordering the hash, thus losing the order of the sentences. What would be the best way to tackle this?
Try the following monster:
hash.map(&:reverse).each_with_index
.sort_by(&:first).reverse
.take(2)
.sort_by(&:last)
.map { |(_,s),_| s }
Another functional one:
hash.to_a.values_at(*hash.values.each_with_index
.sort.reverse
.map(&:last)
.sort.take(2))
.map(&:first)
Note however, that as an unordered data structure, a hash table is not really suitable for this use case (although the order is remembered in Ruby 1.9). You should use an array instead (the sorting code remains the same):
sentences = [
['This is the first sentence.', 5],
['This is the second sentence.', 1],
['This is the last sentence.', 6],
]
def extract hash, n
min = hash.values.sort[-n]
a = []
i = 0
hash.each{|k, v| (a.push(k) and i += 1) if i < n and v >= min}
a
end
hash = {
'This is the first sentence.' => 5,
'This is the second sentence.' => 1,
'This is the last sentence.' => 6
}
cutoff_val = hash.values.sort[-2] #cf. sawa
p hash.select{|k,v| v >= cutoff_val }
# =>{"This is the first sentence."=>5, "This is the last sentence."=>6}
Starting in Ruby 2.2.0, Enumerable#max_by takes an optional integer argument that makes it return an array instead of just a single element. Therefore, we can do:
hash = {
'This is the first sentence.' => 6,
'This is the second sentence.' => 1,
'This is the last sentence.' => 5
}
p hash.max_by(2, &:last).map(&:first).sort_by { |k| hash.keys.index k }
# => ["This is the first sentence.", "This is the last sentence."]
The call to sort_by at the end guarantees the sentences are in the right order, as you requested.
a = hash.sort_by { |sentence, score| score }.reverse
The array a now contains pairs of values of your top scoring sentences. You can select the first N of them.
hash = {"foo" => 7, "bar" => 2, "blah" => 3 }
a = hash.sort_by { |sentence, score| score }.reverse
=> [["foo", 7], ["blah", 3], ["bar", 2]]

Resources