Conditionally extract hashes from array of hashes - arrays

From the following array of hashes:
x = [
{"creationDate"=>123456,"createdBy"=>"test_user1"},
{"creationDate"=>123459,"createdBy"=>"test_user1"},
{"creationDate"=>123458,"createdBy"=>"test_user1"},
{"creationDate"=>123454,"createdBy"=>"test_user2"},
{"creationDate"=>123452,"createdBy"=>"test_user2"},
{"creationDate"=>123451,"createdBy"=>"test_user2"}
]
I am trying to find the maximum :creationDate value where :createdBy value is "test_user1". I did this:
x.map {|a| a['creationDate'] if a['createdBy'] == 'test_user1'}
# => [123456,123459,123458,nil,nil,nil]
I want to get rid of the nil so that I can apply max to that array. How do I modify the code above?

What you want to do here is:
x.select { |record| record[:createdBy] == 'test_user1' }.map { |record| record[:creationDate] }.max
# => 123459
In general, to remove nils from an array, you can simply call Array#compact:
[1, nil, nil, 2, 'foo', nil].compact # => [1, 2, "foo"]

It' close to what you plan to do in python:
x.select { |n| n[:createdBy] == "test_user1" }.max_by { |n| n[:creationDate] }
First operation select on records created by "test_user1", while second operation get the maximum of the resulting array based on the creationDate

I'd guess something like :
x.delete_if {|x| x == nil}

This is a good candidate for a map-reduce function such as inject.
x = [{"creationDate" => 123456,"createdBy" => "test_user1"},
{"creationDate" => 123459,"createdBy" => "test_user1"},
{"creationDate" => 123458,"createdBy" => "test_user1"},
{"creationDate" => 123454,"createdBy" => "test_user2"},
{"creationDate" => 123452,"createdBy" => "test_user2"},
{"creationDate" => 123451,"createdBy" => "test_user2"}]
x.inject(nil) do |result, item|
if item["createdBy"] == "test_user1" && (result.nil? or item["creationDate"] > result)
item["creationDate"]
else
result
end
end
Here's a possible implementation of the inject. The block will iterate each item in the collection and will always maintain the highest value.

x.min_by{|h| [h[:createdBy], -h[:creationDate]]}[:creationDate]
# => 123459
[123456, 123459, 123458, nil, nil, nil].compact
# => [123456, 123459, 123458]

This works:
x.map {|a| a['creationDate'] if a['createdBy'] == 'test_user1'}.compact.max
# => 123459

Related

Ruby Hash Values is Array, need to convert to string

I have a hash of integers as keys and arrays of strings as values. I need to convert this to a new hash that inverts this relationship with each item from the array of strings in the original hash values becoming a key in the new hash and each original key becoming the associated value. For example:
original = {1 => ['a', 'b', 'c'], 2 => ['g', 'm', 'z']}
new_hash = {'a' => 1, 'b' => 1, 'c' => 1, 'g' => 2, 'm' => 2, 'z' => 2}
I'm struggling to extract the items from the original array values. It's easy enough to do
original.each { |k, v| new_hash[v] = k }
but this keeps the original array as the new key. I've tried doing something like
original.each { |k, v| new_hash[v.each { |i| i }] = k }
but this also returns the original array for some reason.
Another one, via Array#product:
original.flat_map { |k, v| v.product([k]) }.to_h
#=> {"a"=>1, "b"=>1, "c"=>1, "g"=>2, "m"=>2, "z"=>2}
original.flat_map { |k, vs| vs.map { |v| {v => k} } }.reduce(&:merge)
the below snippet will give what you want, but let me think on a more readable and elegant solution.
newhash = {}
original.each do |k,v|
v.each do |v2|
newhash[v2] = k
end
end
#=> {1=>["a", "b", "c"], 2=>["g", "m", "z"]}
newhash
#=> {"a"=>1, "b"=>1, "c"=>1, "g"=>2, "m"=>2, "z"=>2}
Your approach is close. You'll have to iterate each element in the values array when assigning the new key/value pair to the newHash
newHash = {}
original.each { |k, v| v.each {|i| newHash[i] = k}}
original.map { |number, ary| Hash[ary.map { |char| [char, number] }] }.reduce(&:merge)

How to search for multiple elements in Ruby array using regexp

Given:
my_array = ['america', 'bombay', 'bostwana', 'cameroon']
I can locate the index of the first element that, say, begins with 'bo' with
my_array.find_index { |l| l.start_with? 'bo' }
How can I locate all such elements?
If you want the elements:
my_array.find { |element| element.start_with? 'bo' } # => "bombay"
my_array.select { |element| element.start_with? 'bo' } # => ["bombay", "bostwana"]
If you want the indices:
my_array.index { |element| element.start_with? 'bo' } # => 1
my_array.map.with_index.select { |element, _| element.start_with? 'bo' }.map(&:last)
# => [1, 2]
You can use map.with_index with a conditional and compact the result.
my_array.map.with_index{ |element, index| index if element.start_with? 'bo' }.compact
How this works
map
map will take all of the values and "map" them to the value that is returned when each item is passed into the given block.
my_array.map { |element| element.start_with? 'bo' }
# => [false, true, true, false]
with_index
To get the index values you can use with_index like this:
my_array.map.with_index { |element, index| index }
# => [0, 1, 2, 3]
my_array.map.with_index { |element, index| index if element.start_with? 'bo' }
# => [nil, 1, 2, nil]
compact
Then, to get rid of the nil values you can call compact on the returned array.
my_array.map.with_index{ |element, index| index if element.start_with? 'bo' }.compact
# => [1, 2]
['america', 'bombay', 'bostwana', 'cameroon']
.each_with_index # to get indices
.grep(->(e) { e.first.start_with? "bo" }) # I ❤ Enumerable#grep
.map(&:last) # get indices
#⇒ [1, 2]
I posted this to show the approach that is rarely used. Enumerable#grep accepts anything, that is used to select items basing on triple-equal case comparison.
Passing Proc instance there is cool, because Proc has a default Proc#=== implementation: it’s simply being invoked on receiver.
In the interest of readability, I would use the following:
my_array.each_index.select {|idx| my_array[idx].start_with?("bo") }

Can I iterate through an array of arrays and compare it to an array of integers

I have an array of arrays [[1,2,3],[4,5,6],[7,8,9]]. I also have an array of integers [3,4,5,6,8].
Is it possible for me to check if my integers match a complete array in the array of arrays?
So I have 4,5,6 in the int array, and it matches the middle array [4,5,6].
This should work
a = [[1,2,3],[4,5,6],[7,8,9]]
integers = [3,4,5,6,8]
a.any? { |sub_array| sub_array.all? { |item| integers.include? item } }
Try this:
array_1 = [[1,2,3],[4,5,6],[7,8,9]]
array_2 = [3,4,5,6,8]
array_1.any? { |e| (e - array_2).empty? }
# => true
array1 = [[1,2,3],[4,5,6],[7,8,9]]
array2 = [4,5,6]
result = array1.map{|inner_array| inner_array - array2}
# => [[1, 2, 3], [], [7, 8, 9]]
result.any?{|inner_array| inner_array.empty?}
# => true
Assuming you expect a true or false and order doesn't matter, the following works:
require 'set'
a1 = [[1,2,3],[4,5,6],[7,8,9]]
a2 = [3,4,5,6,8]
a1.any? { |item| item.to_set.subset? a2.to_set } #=> true
Assuming you want the index into a1 or nil
a1.index { |item| item.to_set.subset? a2.to_set }
Assuming you want the subset itself or nil
index = a1.index { |item| item.to_set.subset? a2.to_set }
index && a1[index]

Count how many times an element appears in an array in Ruby

This is the array that is going to be processed:
[{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
and I would like the result to be like this:
[{:name => "blake", :count => 2}, {:name => "ashley", :count => 1}]
I created a new hash called "count" then i used .each to iterate through the array and then count[:count] += 1, but it's not giving me what I'm looking for.
It can be done with built-in library calls like this:
a = [{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
a.group_by(&:itself) # group all of the identical elements together
.map{|k, v| k.merge(count: v.length)} # add the "count" element to the hash
Explanation
.group_by(&:itself) will take all the identical components and put them in a hash together under the same key:
>> h = a.group_by(&:itself)
=> {
{:name=>"blake"} => [{:name=>"blake"}, {:name=>"blake"}],
{:name=>"ashley"} => [{:name=>"ashley"}]
}
Notice how the first entry in the hash has an array of two identical elements, and the second entry has an array of one element. To create counts of those, we can use the .length method on the arrays in the hash's values:
>> k, v = h.first
>> v
=> [{:name=>"blake"}, {:name=>"blake"}]
>> v.length
=> 2
Then we can use .map to apply that code to every element in the hash:
>> h.map{|k, v| [k, v.length]}.to_h
=> {
{:name=>"blake"}=>2,
{:name=>"ashley"}=>1
}
Finally, .merge will take two hashes and piece them together into a single hash:
>> {:name=>"blake"}.merge({:count => 1})
=> {:name=>"blake", :count=>1}
Here the new Enumerable#tally introduced with Ruby 2.7.0 comes in hand:
h = [{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
h.tally.map {|k, v| k.merge({count: v}) }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
There are three main ways of doing this.
Use Enumerable#group_by
That is what #user12341234 has done in his or her answer.
Use a counting hash
If arr is your array,
arr.each_with_object(Hash.new(0)) { |g,h| h[g[:name]] += 1 }.
map { |name, count| { :name=>name, :count=>count } }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
See Hash::new for details.
Use the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged.
arr.each_with_object({}) { |g,h| h.update(g[:name]=>1) { |_,o,n| o+n } }.
map { |name, count| { :name=>name, :count=>count } }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
See the doc Hash#update for details.
Notice that last line is the same for #2 and #3.
I cannot say that one of these approaches is preferable to the others.

Hash whose values are array size

I need a method that will take a hash and return a hash whose keys are from the old hash and values are the size of the arrays in the old hash. I.e.,
{ 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
# return
{ 1 => 3, 2 => 3, 7 => 2 }
Is there any way to implement this?
One way:
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
h.merge(h) { |*_,a| a.size }
#=> { 1 => 3, 2 => 3, 7 => 2 }
You can use map to build a new array of [key, value] pairs and then convert it back to a hash using to_h:
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.map { |key, value| [key, value.length] }.to_h
# => {1=>3, 2=>3, 7=>2}
This is quite straightforward: you can use inject to process all the items one by one and compose the result.
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.inject({}) do |result, (key, value)|
result.merge(key => value.size)
end
# => {1=>3, 2=>3, 7=>2}
Even without inject, just use .each to loop all the items and construct the result using a temporary support Hash.
Just for the sake of completeness, a solution using each_with_object:
input = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
input.each_with_object({}) { |(k, vs), h| h[k] = vs.size }
#=> {1=>3, 2=>3, 7=>2}
One way is to insert the expected keys and values into the new Hash:
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
h2 = {}
h.each {|k, v| h2[k] = v.length}
h2
# => {1=>3, 2=>3, 7=>2}
h.keys.zip(h.values.map &:size).to_h
Or you can try this,
h = { 1 => [1,1,1], 2 => [3,4,5], 7 => [9,12] }
Hash[h.map {|key, value| [key, value.length]}]
# => {1=>3, 2=>3, 7=>2}

Resources