Filtering when adding elements by id - ruby

Tell me how to do this, where you can read about it, because I do not understand at all how to implement it. Thanks.
def initialize
#arr = []
end
def items(init)
arrfinish = init - #arr
#arr = (#arr + init).uniq
yield arrfinish
end
def idefine(find_text)
end
The class has a method(items) that connects arrays by removing duplicate elements. I need to make sure that the idefine
method receives the key by which filtering will be performed when adding new elements, I will give an example below.
app_handler.idefine('id')
app_handler.items([{'id' => 1}, {'id' => 1, 'test_key' => 'Some data'}, {'id' => 2}])
From this example, the second element with id = 1 should be ignored.

Ok, putting aside the class definition, what I understand of the question is:
In an array of hashes, remove the hashes that contain a duplicate value of a given key.
The following function filter the hashes and copy the content of the selected ones in a new array:
require 'set'
def no_dup_val key, arr
previous_values = Set[]
arr.each_with_object([]) do |hash,result|
next unless hash.has_key?(key)
next if previous_values.include?(hash[key])
previous_values << hash[key]
result << hash.dup
end
end
Which gives you:
no_dup_val 'id', [{'id' => 1}, {'id' => 1, 'key' => 'data'}, {'id' => 2}, {'stock' => 3}, {'e-stock'=>0}]
#=> [{"id"=>1}, {"id"=>2}]
Note that the hashes that don't contain the key are also removed, that's my choice, which leads to the following questions:
What happens when the key is not present in a hash?
What happens when the item function is called more than once? Do you take into account the hashes already in #arr?
What happens when you call idefine with a new key? Do you filter the existing elements of #arr with the new key?
As you can see, you need to be a little more specific about what you want to do.
Update
If you don't care about copying the contents of the hashes then these may fit your needs.
Hashes without the id key are removed:
def no_dup_val key, arr
arr.filter{ |h| h.has_key?(key) }.uniq{ |h| h[key] }
end
no_dup_val 'id', [{'id' => 1}, {'id' => 1, 'key' => 'data'}, {'id' => 2}, {'stock' => 3}, {'e-stock'=>0}]
#=> [{"id"=>1}, {"id"=>2}]
Hashes without the id key are treated as having "id" => nil (so the first will be kept):
def no_dup_val key, arr
arr.uniq{ |h| h[key] }
end
no_dup_val 'id', [{'id' => 1}, {'id' => 1, 'key' => 'data'}, {'id' => 2}, {'stock' => 3}, {'e-stock'=>0}]
#=> [{"id"=>1}, {"id"=>2}, {"stock"=>3}]
All the hashes without the id key are kept:

Related

I need to convert a string to an integer within a hash

I have a nested hash and I need to return the inside hash, which is a value of the key. The problem is one of the values is a string and it needs to be returned as an integer.
def player_stats(player_name)
game_hash.keys.each do |data|
if game_hash[data][:players].keys.include?(player_name)
return game_hash[data][:players][player_name]
end
end
end
game_hash = {
:home => {team_name:"Brooklyn Nets", colors:["Black", "White"],
:players => {"Alan Anderson" => {:number => 0, :shoe => "16",
:points => 22, :rebounds => 12, :assists =>12, :steals => 3,
:blocks => 1, :slam_dunks => 1},
The code is correct, the only thing is that I need to input a line that will convert the string**(:shoe)** to an integer.
game_hash = {
:home => {:team_name =>"Brooklyn Nets",
:colors => ["Black", "White"],
:players => {
"Alan Anderson" => {
:number => 0,
:shoe => "16",
:points => 22,
}
}
}
}
In this case, assuming you do not want to mutate (modify) the original hash, it's easiest to first make a deep copy of game_hash:
h = Marshal.load(Marshal.dump(game_hash))
#=> {:home=>{:team_name=>"Brooklyn Nets", :colors=>["Black", "White"],
# :players=>{"Alan Anderson"=>{:number=>0, :shoe=>"16", :points=>22}}}}
See Marshall::load and Marshall::dump.
Then just modify the value of interest:
h[:home][:players]["Alan Anderson"][:shoe] =
h[:home][:players]["Alan Anderson"][:shoe].to_i
h #=> {:home=>{:team_name=>"Brooklyn Nets", :colors=>["Black", "White"],
# :players=>{"Alan Anderson"=>{:number=>0, :shoe=>16, :points=>22}}}}
Lastly, let's confirm the original hash was not mutated:
game_hash
#=> {:home=>{:team_name=>"Brooklyn Nets", :colors=>["Black", "White"],
# :players=>{"Alan Anderson"=>{:number=>0, :shoe=>"16", :points=>22}}}}
Following your code, just a couple of changes (see inline comments in the option below).
Given the game_hash, which could have integers as string somewhere:
game_hash = {:home => { team_name:"Brooklyn Nets", colors:["Black", "White"],:players => {"Alan Anderson" => {:number => "0", :shoe => "16", :points => 22, :rebounds => 12, :assists =>12, :steals => 3, :blocks => 1, :slam_dunks => 1},"Reggie Evans" => {:number => "30",:shoe => "14",:points => 12, :rebounds => 12, :assists => 12, :steals => 12, :blocks => 12, :slam_dunks => 7}}}}
This is the option:
def player_stats(player_name, game_hash) # <-- pass game_hash as parameter
game_hash.keys.each do |data|
if game_hash[data][:players].keys.include?(player_name)
return game_hash[data][:players][player_name].transform_values { |v| v.to_i } # <-- transform values to integer
end
end
end
Then, you can call:
player_stats("Reggie Evans", game_hash)
#=> {:number=>30, :shoe=>14, :points=>12, :rebounds=>12, :assists=>12, :steals=>12, :blocks=>12, :slam_dunks=>7}
There are a few "issues" that I would like to point out in your #player_stats method, but I will focus on your problem with converting the player hash values to integers. To start off, I am making the following assumptions:
There is no consistency in whether the values are formatted as a string or integer.
All values are of integer form, in either the Integer type, or String type.
One thing I would like to point out is that shoe size could be 9.5 or 10.5, etc. This could be the reason why the shoe value is formatted as a string. If this is the case, then you would need to take this into account and convert to a float using #to_f instead of #to_i. But since your question is asking for it to be an integer, I will use #to_i in my examples below.
Given my assumptions, you would simply loop over all the values in the player hash and call #to_i on the value to convert it to an integer. This can be done using the following method:
def convert_hash_values_to_int(hash)
hash.each do |key, value|
hash[key] = value.to_i
end
end
You can call this method inside your #player_stats method. Note that this method is mutating the original hash values in place without creating a new hash. If this is not desirable, then you should use #inject:
def convert_hash_values_to_int(hash)
hash.inject({}) do |result, (key, value)|
result[key] = value.to_i
result
end
end
This creates a new hash with the transformed values, and returns it.
This can be simplified even further using #each_with_object, which also creates a new hash instead of mutating the original hash:
def convert_hash_values_to_int(hash)
hash.each_with_object({}) do |(key, value), result|
result[key] = value.to_i
end
end
This approach does not require that you return result in each loop. Note that the arguments are switched for #each_with_object - |(key, value), result| instead of |result, (key, value)|.
There is one more approach that you could use which is the most succinct, but it is only available in Ruby 2.4+:
def convert_hash_values_to_int(hash)
hash.transform_values do |value|
value.to_i
end
end
Which can be even more succinct:
def convert_hash_values_to_int(hash)
hash.transform_values(&:to_i)
end
#transform_values does not mutate the original hash. If you would like to mutate the original hash, you would need to use a bang:
def convert_hash_values_to_int(hash)
hash.transform_values!(&:to_i)
end

Merge hashes based on particular key/value pair in ruby

I am trying to merge an array of hashes based on a particular key/value pair.
array = [ {:id => '1', :value => '2'}, {:id => '1', :value => '5'} ]
I would want the output to be
{:id => '1', :value => '7'}
As patru stated, in sql terms this would be equivalent to:
SELECT SUM(value) FROM Hashes GROUP BY id
In other words, I have an array of hashes that contains records. I would like to obtain the sum of a particular field, but the sum would grouped by key/value pairs. In other words, if my selection criteria is :id as in the example above, then it would seperate the hashes into groups where the id was the same and the sum the other keys.
I apologize for any confusion due to the typo earlier.
Edit: The question has been clarified since I first posted my answer. As a result, I have revised my answer substantially.
Here are two "standard" ways of addressing this problem. Both use Enumerable#select to first extract the elements from the array (hashes) that contain the given key/value pair.
#1
The first method uses Hash#merge! to sequentially merge each array element (hashes) into a hash that is initially empty.
Code
def doit(arr, target_key, target_value)
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
return nil if qualified.empty?
qualified.each_with_object({}) {|h,g|
g.merge!(h) {|k,gv,hv| k == target_key ? gv : (gv.to_i + hv.to_i).to_s}}
end
Example
arr = [{:id => '1', :value => '2'}, {:id => '2', :value => '3'},
{:id => '1', :chips => '4'}, {:zd => '1', :value => '8'},
{:cat => '2', :value => '3'}, {:id => '1', :value => '5'}]
doit(arr, :id, '1')
#=> {:id=>"1", :value=>"7", :chips=>"4"}
Explanation
The key here is to use the version of Hash#merge! that uses a block to determine the value for each key/value pair whose key appears in both of the hashes being merged. The two values for that key are represented above by the block variables hv and gv. We simply want to add them together. Note that g is the (initially empty) hash object created by each_with_object, and returned by doit.
target_key = :id
target_value = '1'
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
#=> [{:id=>"1", :value=>"2"},{:id=>"1", :chips=>"4"},{:id=>"1", :value=>"5"}]
qualified.empty?
#=> false
qualified.each_with_object({}) {|h,g|
g.merge!(h) {|k,gv,hv| k == target_key ? gv : (gv.to_i + hv.to_i).to_s}}
#=> {:id=>"1", :value=>"7", :chips=>"4"}
#2
The other common way to do this kind of calculation is to use Enumerable#flat_map, followed by Enumerable#group_by.
Code
def doit(arr, target_key, target_value)
qualified = arr.select {|h|h.key?(target_key) && h[target_key]==target_value}
return nil if qualified.empty?
qualified.flat_map(&:to_a)
.group_by(&:first)
.values.map { |a| a.first.first == target_key ? a.first :
[a.first.first, a.reduce(0) {|tot,s| tot + s.last}]}.to_h
end
Explanation
This may look complex, but it's not so bad if you break it down into steps. Here's what's happening. (The calculation of qualified is the same as in #1.)
target_key = :id
target_value = '1'
c = qualified.flat_map(&:to_a)
#=> [[:id,"1"],[:value,"2"],[:id,"1"],[:chips,"4"],[:id,"1"],[:value,"5"]]
d = c.group_by(&:first)
#=> {:id=>[[:id, "1"], [:id, "1"], [:id, "1"]],
# :value=>[[:value, "2"], [:value, "5"]],
# :chips=>[[:chips, "4"]]}
e = d.values
#=> [[[:id, "1"], [:id, "1"], [:id, "1"]],
# [[:value, "2"], [:value, "5"]],
# [[:chips, "4"]]]
f = e.map { |a| a.first.first == target_key ? a.first :
[a.first.first, a.reduce(0) {|tot,s| tot + s.last}] }
#=> [[:id, "1"], [:value, "7"], [:chips, "4"]]
f.to_h => {:id=>"1", :value=>"7", :chips=>"4"}
#=> {:id=>"1", :value=>"7", :chips=>"4"}
Comment
You may wish to consider makin the values in the hashes integers and exclude the target_key/target_value pairs from qualified:
arr = [{:id => 1, :value => 2}, {:id => 2, :value => 3},
{:id => 1, :chips => 4}, {:zd => 1, :value => 8},
{:cat => 2, :value => 3}, {:id => 1, :value => 5}]
target_key = :id
target_value = 1
qualified = arr.select { |h| h.key?(target_key) && h[target_key]==target_value}
.each { |h| h.delete(target_key) }
#=> [{:value=>2}, {:chips=>4}, {:value=>5}]
return nil if qualified.empty?
Then either
qualified.each_with_object({}) {|h,g| g.merge!(h) { |k,gv,hv| gv + hv } }
#=> {:value=>7, :chips=>4}
or
qualified.flat_map(&:to_a)
.group_by(&:first)
.values
.map { |a| [a.first.first, a.reduce(0) {|tot,s| tot + s.last}] }.to_h
#=> {:value=>7, :chips=>4}

Ruby : How to sort an array of hash in a given order of a particular key

I have an array of hashes, id being one of the keys in the hashes. I want to sort the array elements according to a given order of ID values.
Suppose my array(size=5) is:
[{"id"=>1. ...}, {"id"=>4. ...}, {"id"=>9. ...}, {"id"=>2. ...}, {"id"=>7. ...}]
I want to sort the array elements such that their ids are in the following order:
[1,3,5,7,9,2,4,6,8,10]
So the expected result is:
[{'id' => 1},{'id' => 7},{'id' => 9},{'id' => 2},{'id' => 4}]
Here is a solution for any custom index:
def my_index x
# Custom code can be added here to handle items not in the index.
# Currently an error will be raised if item is not part of the index.
[1,3,5,7,9,2,4,6,8,10].index(x)
end
my_collection = [{"id"=>1}, {"id"=>4}, {"id"=>9}, {"id"=>2}, {"id"=>7}]
p my_collection.sort_by{|x| my_index x['id'] } #=> [{"id"=>1}, {"id"=>7}, {"id"=>9}, {"id"=>2}, {"id"=>4}]
Then you can format it in any way you want, maybe this is prettier:
my_index = [1,3,5,7,9,2,4,6,8,10]
my_collection.sort_by{|x| my_index.index x['id'] }
I would map the hash based on the values like so:
a = [{"id"=>1}, {"id"=>4}, {"id"=>9}, {"id"=>2}, {"id"=>7}]
[1,3,5,7,9,2,4,6,8,10].map{|x| a[a.index({"id" => x})] }.compact
#=> [{"id"=>1}, {"id"=>7}, {"id"=>9}, {"id"=>2}, {"id"=>4}]
General note on sorting. Use #sort_by method of the ruby's array class:
[{'id' => 1},{'id'=>3},{'id'=>2}].sort_by {|x|x['id'] }
# => [{"id"=>1}, {"id"=>2}, {"id"=>3}]
Or with usage #values method as a callback:
[{'id' => 1},{'id'=>3},{'id'=>2}].sort_by(&:values)
# => [{"id"=>1}, {"id"=>2}, {"id"=>3}]
or you can use more obvious version with #sort method:
[{'id' => 1},{'id'=>3},{'id'=>2}].sort {|x,y| x['id'] <=> y['id'] }
# => [{"id"=>1}, {"id"=>2}, {"id"=>3}]
For your case, to sort with extended condition use #% to split even and odd indexes:
[{'id' => 1},{'id'=>4},{'id'=>9},{'id'=>2},{'id'=>7}].sort do |x,y|
u = y['id'] % 2 <=> x['id'] % 2
u == 0 && y['id'] <=> x['id'] || u
end
# => [{"id"=>1}, {"id"=>7}, {"id"=>9}, {"id"=>2}, {"id"=>4}]
For your case, to sort with extended condition use #% to split according the index, even id value is absent in the index array:
index = [1,3,5,7,4,2,6,8,10] # swapped 2 and 4, 9 is absent
[{'id' => 1},{'id'=>4},{'id'=>9},{'id'=>2},{'id'=>7}].sort do |x,y|
!index.rindex( x[ 'id' ] ) && 1 || index.rindex( x[ 'id' ] ) <=> index.rindex( y[ 'id' ] ) || -1
end
# => [{"id"=>1}, {"id"=>7}, {"id"=>4}, {"id"=>2}, {"id"=>9}]
Why not just sort?
def doit(arr, order)
arr.sort { |h1,h2| order.index(h1['id']) <=> order.index(h2['id']) }
end
order = [1,3,5,7,9,2,4,6,8,10]
arr = [{'id' => 1}, {'id' => 4}, {'id' => 9}, {'id' => 2}, {'id' => 7}]
doit(arr, order)
#=> [{'id' => 1}, {'id' => 7}, {'id' => 9}, {'id' => 2}, {'id' => 4}]
a= [{"id"=>1}, {"id"=>4}, {"id"=>9}, {"id"=>2}, {"id"=>7}]
b=[1,3,5,7,9,2,4,6,8,10]
a.sort_by{|x| b.index (x['id'])}

Given array of hashes, how can I use select on one key of the hash while evaluating on another key?

With my array of hashes:
data = [{:bool => true, :val => 5}, {:bool => false, :val => 9}, {:bool => true, :val => 1}]
I would like to iterate through the data and retrieve an array of values only. I can do:
data.map{|x| x[:val] if x[:bool]}
which returns:
[5, nil, 1]
But this method requires an additional .compact call to get rid of the nil values.
Is there a better way of achieving this?
Use chaining instead to first select only those where :bool is true, then map the results to :val:
data.select { |h| h[:bool] }.map { |h| h[:val] } #=> [5, 1]
data.map { |x| x[:val] if x[:bool] }.compact is probably the easiest to read, but you can go down to one function call via reduce:
data.reduce([]) { |m,x| m << x[:val] if x[:bool]; m }
It does not seem an elegant way but you can try this:
data = [{:bool => true, :val => 5}, {:bool => false, :val => 9}, {:bool => true, :val => 1}]
result = Array.new
data.each do |b|
val = Hash.try_convert(b)[:val]
result.unshift(val)
end

How to add new item to hash

I don't know how to add new item to already existing hash. For example, first I construct hash:
hash = {item1: 1}
After that, I want to add item2, so after this I have hash like this:
{item1: 1, item2: 2}
I don't know what method to do on hash. Could someone help me?
Create the hash:
hash = {:item1 => 1}
Add a new item to it:
hash[:item2] = 2
If you want to add new items from another hash - use merge method:
hash = {:item1 => 1}
another_hash = {:item2 => 2, :item3 => 3}
hash.merge(another_hash) # {:item1=>1, :item2=>2, :item3=>3}
In your specific case it could be:
hash = {:item1 => 1}
hash.merge({:item2 => 2}) # {:item1=>1, :item2=>2}
but it's not wise to use it when you should to add just one element more.
Pay attention that merge will replace the values with the existing keys:
hash = {:item1 => 1}
hash.merge({:item1 => 2}) # {:item1=>2}
exactly like hash[:item1] = 2
Also you should pay attention that merge method (of course) doesn't effect the original value of hash variable - it returns a new merged hash. If you want to replace the value of the hash variable then use merge! instead:
hash = {:item1 => 1}
hash.merge!({:item2 => 2})
# now hash == {:item1=>1, :item2=>2}
hash.store(key, value) - Stores a key-value pair in hash.
Example:
hash #=> {"a"=>9, "b"=>200, "c"=>4}
hash.store("d", 42) #=> 42
hash #=> {"a"=>9, "b"=>200, "c"=>4, "d"=>42}
Documentation
It's as simple as:
irb(main):001:0> hash = {:item1 => 1}
=> {:item1=>1}
irb(main):002:0> hash[:item2] = 2
=> 2
irb(main):003:0> hash
=> {:item1=>1, :item2=>2}
hash[key]=value
Associates the value given by value with the key given by key.
hash[:newKey] = "newValue"
From Ruby documentation:
http://www.tutorialspoint.com/ruby/ruby_hashes.htm
hash_items = {:item => 1}
puts hash_items
#hash_items will give you {:item => 1}
hash_items.merge!({:item => 2})
puts hash_items
#hash_items will give you {:item => 1, :item => 2}
hash_items.merge({:item => 2})
puts hash_items
#hash_items will give you {:item => 1, :item => 2}, but the original variable will be the same old one.
Create hash as:
h = Hash.new
=> {}
Now insert into hash as:
h = Hash["one" => 1]

Resources