Deleting multiple key and value pairs from hash in Rails - ruby

number = {:a => 1, :b => 2, :c => 3, :d => 4}
upon evaluation of certain condition i want to delete key-value pair of a,b,c

number.delete "A"
number.delete "B"
number.delete "C"
Or, less performant but more terse:
number.reject! {|k, v| %w"A B C".include? k }

or, more performant than second Chris' solution but shorter than first:
%w"A B C".each{|v| number.delete(v)}

ActiveSupport that is a part of Rails comes with several built-in methods can help you to achieve your goal.
If you just want to delete some key-value pairs, you can use Hash#except!
number.except!(:a, :b, :c)
If you want to keep the original hash, then use Hash#except
new_hash = number.except!(:a, :b, :c)
new_hash # => {:d=>4}
number # => {:a=>1, :b=>2, :c=>3, :d=>4}
You also can go with Rails-free way:
new_hash = number.dup.tap do |hash|
%i[a b c].each {|key| hash.delete(key)}
end
new_hash # => {:d=>4}
number # => {:a=>1, :b=>2, :c=>3, :d=>4}
P.S.: the last code example is very slow, I'm just providing it as an alternative.

Related

hash merge vs assignment

I have a simple case of a hash that's passed along that is missing a field.
I'm wondering which is the preferred way of adding the field with some default and whether or not are there benefits to choosing one over the other and when?
hash['field'] = some_value
vs
hash.merge!({ 'field' => some_value })
edit: meant to use merge!
First, an important note: Hash#merge returns a new object, whereas Hash#[]= and Hash#merge! mutate the existing object:
hash = {a: :b}
hash.merge(c: :d) # => {a: :b, c: :d}
puts hash # => {a: :b} (!!!)
hash[:c] = :d
puts hash # => {a: :b, c: :d}
hash.merge!(e: :f)
puts hash # => {a: :b, c: :d, e: :f}
The main use-case for using Hash#merge! over Hash#[]= is (as the name suggests!) when you have two existing hashes that need to be merged together:
hash1 = {a: :b, c: :d}
hash2 = {e: :f, g: :h}
hash1.merge!(hash2)
puts hash1 # => {a: :b, c: :d, e: :f, g: :h}
This is equivalent to, but more convenient than, the much more verbose approach of looping through all values in hash2:
hash2.each do |key, value|
hash1[key] = value
end
I'm wondering which is the preferred way of adding the field with some default
I'd store the defaults in an extra hash (even if it is just a single value), so I can easily add other values later:
defaults = { foo: 1, bar: 2 }
Then, I'd merge the defaults in a way that preserve the given value, either by creating a new hash:
hash = { foo: 100, baz: 300 }
hash = defaults.merge(hash)
#=> {:foo=>100, :bar=>2, :baz=>300}
or by changing the hash in-place:
hash = { foo: 100, baz: 300 }
hash.merge!(defaults) { |key, given_value, default_value| given_value }
#=> {:foo=>100, :baz=>300, :bar=>2}
the first approach changes the hash itself (no new object is created)
the second creates new instance of Hash class with keys from both original hash and { 'field' => some_value } (compare object_id of hash and of result of hash.merge({ 'field' => some_value }) - they'll be different)
which one to choose?
it depends - if changing state of hash is not a problem for you, then the first approach is ok.

An equivalent .split method for hashes

I need to split a hash based on multiple arguments, which will be keys, and return as an array of hashes. Basically, I need a method that performs the same job as .split() does for strings, but with multiple delimiters - is there such a thing for hashes?
example:
Input
({ :a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6 },:c, :e)
Output
[ {:a=>1, :b=>2}, {:c=>3, :d=>4}, {:e=>5, :f=>6} ]
To slice before elements use Enumerable#slice_before:
{ :a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6 }.slice_before do |e|
%i[c e].include? e.first
end.map(&:to_h)
Generic implementation (without checks):
λ = lambda do |hash, *delimiters|
hash.slice_before do |e|
delimiters.include? e.first
end.map(&:to_h)
end
λ.({ :a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6 }, :c, :e)
#⇒ [ {:a=>1, :b=>2}, {:c=>3, :d=>4}, {:e=>5, :f=>6} ]
To slice a hash by pieces of the same size, use Enumerable#each_slice:
{ :a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6 }.each_slice(2).map &:to_h
With pure ruby (MRI < 1.9) it is not possible to fullfill the task. A ruby Hash has no order of the keys in this versions. So you need ActiveSupport::OrderedHash which supports an order of keys. In MRI versions >=1.9 it works directly
# in case hs is of type ActiveSupport::OrderedHash.new
# ds = delimiters
result = []
result_hash = {}
next_del = delimiters.shift
hs.keys.each do |key|
if next_del == key
result << result_hash
result_hash = {}
next_del = delimiters.shift
end
result_hash[key] = hs[key]
end
result << result_hash
result

How to make a custom proc in a class so I can do: params_hash.downcase_keys instead of downcase_keys(params_hash)?

I have a hash of key values and I want to downcase all of the Keys.
However I don't want to have to create an local variable, I would rather functionally do it.
NOT:
x = downcase_keys(params_hash)
BUT THIS:
params_hash.downcase_keys
How would do this in ruby?
I do not understand why you tagged this question as functional-programming it seems you are looking for a method to call on a Hash object.
Be aware that you may encounter problems doing so because duplicated keys are going to be overwritten.
h = {"a" => 1, "B" => 2}
# Singleton method on the object
def h.downcase_keys
temp = map {|k,v| [k.downcase, v]}.to_h
clear
merge! temp
end
h.downcase_keys()
p h # {"a"=>1, "b"=>2}
# Method available on all Hash objects
class Hash
def downcase_keys
temp = map {|k,v| [k.downcase, v]}.to_h
clear
merge! temp
end
end
h = {"a" => 1, "B" => 2}
h.downcase_keys()
p h # {"a"=>1, "b"=>2}
def downcase_keys(hash)
hash.downcase_keys
end
h = {"C" => 1, "B" => 2, "D" => 3}
downcase_keys(h)
p h # {"c"=>1, "b"=>2, "d"=>3}

Ruby - explain code snippet hash sorter

I use this code for sorting Hash;
I've got no idea how its works.
please explain to me:
def foo(hash)
Hash[hash.sort]
end
irb(main):001:0> h = {1=>'z', 3=>'x', 2=>'y'}
=> {1=>"z", 3=>"x", 2=>"y"}
irb(main):002:0> Hash[h.sort]
=> {1=>"z", 2=>"y", 3=>"x"}
irb(main):003:0>
Enumerable#sort reutrns an sorted array of key-value pairs:
h = {b: 1, a: 2}
h.sort
# => [[:a, 2], [:b, 1]]
Hash::[] create a new hash base on the argument:
Hash[h.sort]
# => {:a=>2, :b=>1}
BTW, if you use Ruby 2.1+, you can use Array#to_h instead:
h.sort.to_h
# => {:a=>2, :b=>1}

Array of hashes to hash

For example, I have array of single hashes
a = [{a: :b}, {c: :d}]
What is best way to convert it into this?
{a: :b, c: :d}
You may use
a.reduce Hash.new, :merge
which directly yields
{:a=>:b, :c=>:d}
Note that in case of collisions the order is important. Latter hashes override previous mappings, see e.g.:
[{a: :b}, {c: :d}, {e: :f, a: :g}].reduce Hash.new, :merge # {:a=>:g, :c=>:d, :e=>:f}
You can use .inject:
a.inject(:merge)
#=> {:a=>:b, :c=>:d}
Demonstration
Which initiates a new hash on each iteration from the two merged. To avoid this, you can use destructive :merge!( or :update, which is the same):
a.inject(:merge!)
#=> {:a=>:b, :c=>:d}
Demonstration
These two are equivalent (reduce/inject are the same method):
total_hash = hs.reduce({}) { |acc_hash, hash| acc_hash.merge(hash) }
total_hash = hs.reduce({}, :merge)
Note that Hash#merge creates a new hash on each iteration, which may be a problem if you are building a big one. In that case, use update instead:
total_hash = hs.reduce({}, :update)
Alternatively, you can convert the hashes to pairs and then build the final hash:
total_hash = hs.flat_map(&:to_a).to_h
I came across this answer and I wanted to compare the two options in terms of performance to see which one is better:
a.reduce Hash.new, :merge
a.inject(:merge)
using the ruby benchmark module, it turns out that option (2) a.inject(:merge) is faster.
code used for comparison:
require 'benchmark'
input = [{b: "c"}, {e: "f"}, {h: "i"}, {k: "l"}]
n = 50_000
Benchmark.bm do |benchmark|
benchmark.report("reduce") do
n.times do
input.reduce Hash.new, :merge
end
end
benchmark.report("inject") do
n.times do
input.inject(:merge)
end
end
end
the results were
user system total real
reduce 0.125098 0.003690 0.128788 ( 0.129617)
inject 0.078262 0.001439 0.079701 ( 0.080383)
Just use
a.reduce(:merge)
#=> {:a=>:b, :c=>:d}
Try this
a.inject({}){|acc, hash| acc.merge(hash)} #=> {:a=>:b, :c=>:d}
You can transform it to array [[:a, :b]] and after that translate everything to hash {:a=>:b}
# it works like [[:a, :b]].to_h => {:a=>:b}
[{a: :b}, {c: :d}].map { |hash| hash.to_a.flatten }.to_h
# => {:a=>:b, :c=>:d}

Resources