How to replace missing key/value with zero using slice - ruby-on-rails

I have a hash. I need to extract some key/value pairs, but some desired keys are missing.
How can I replace the missing pairs with "key" => 0.0 when I call attributes.slice on the record and keys as follows:
record = Model.last
record.attributes.slice('k1','k2','k3','k4','k5') # this returns
=> {"k1"=> 343, k3=> 0.0}
If some keys are missing then they won't appear in the result. How can I get the remaining missing keys assigned with 0.0?

Suppose
h = { 'k2'=>2, 'k1'=>1 }
and
all_keys = ['k1', 'k2', 'k3', 'k4']
then
all_keys.map { |k| h.fetch(k,0.0) }
#=> [1, 2, 0.0, 0.0]
See Hash#fetch.

We can take advantage of the fact that a hash can be overwritten with new key/value pairs:
{a: 0, b: 0}.merge(a: 2) # => {:a=>2, :b=>0}
Knowing that, we can do something like this:
desired_keys = [:a, :b]
foo = {a: 1}
desired_keys.zip([0] * desired_keys.size).to_h.merge(foo.slice(*desired_keys))
# => {:a=>1, :b=>0}
desired_keys is a predefined list of the key/value pairs we want, foo is the actual hash the real values are coming from.
[0] * 2 # => [0, 0] creates an array of a given size.
desired_keys.zip([0] * desired_keys.size).to_h creates a temporary hash of the values being used as filler.
merge(foo.slice(*desired_keys)) grabs the pairs we wanted. In this situation, * AKA "splat" explodes the array into its individual elements, so they're passed as separate parameters to slice. Here's what's happening:
def bar(*a)
a
end
bar(%w[a b]) # => [["a", "b"]]
bar(*%w[a b]) # => ["a", "b"]
Notice that the first call is passing in an array, whereas the second passes separate values.
Breaking it down a little to make it a bit more apparent:
desired_keys.zip([0] * desired_keys.size).to_h # => {:a=>0, :b=>0}
.merge(foo.slice(*desired_keys)) # => {:a=>1, :b=>0}
Because we know the record fields we're retrieving, it's easy to create that temporary hash once, in advance, then reuse it every time, resulting in very fast code:
DESIRED_KEYS = [:a, :b]
ZERO_HASH = DESIRED_KEYS.zip([0] * DESIRED_KEYS.size).to_h # => {:a=>0, :b=>0}
foo = {a: 1}
ZERO_HASH.merge(foo.slice(*DESIRED_KEYS))
# => {:a=>1, :b=>0}
All the methods, including * are part of Array or Hash.
How to use 0.0 instead of 0 is left as an exercise for the reader.

I think this should work in your case
Model.slice('k1','k2','k3','k4','k5').transform_values! { |v| v ? v : 0.0 }

Related

Remove array elements at indices non destructively

Let's say you have something like this:
my_array = ['some_rather', 'long_named', 'array_element', 'entry']
I want to remove arbitrary entries by index from my_array without changing it and I want the filtered (i.e. array with indices removed) to be returned from my call. Furthermore, I want to avoid chaining 4 separate calls and write a block doing so.
Example:
filtered_array = my_array.drop_indices(1,3)
You could chain Enumerable's with_index onto Array's reject method to do what you want, though this might violate your desire to not chain separate method calls or write a block to do this:
my_array = ['some_rather', 'long_named', 'array_element', 'entry', 'long_named']
indices_to_remove = [1, 3]
filtered = my_array.reject.with_index { |_, index| indices_to_remove.include?(index) }
p filtered # => ["some_rather", "array_element", "long_named"]
p my_array # => ["some_rather", "long_named", "array_element", "entry", "long_named"]
If this isn't acceptable, the only other thing I can think of right now, to keep duplicate items (as noted in my comment to your solution), is to change from indices_to_remove to indices_to_keep:
my_array = ['some_rather', 'long_named', 'array_element', 'entry', 'long_named']
indices_to_remove = [1, 3]
indices_to_keep = [*(0...my_array.length)] - indices_to_remove
filtered = my_array.values_at(*indices_to_keep)
p filtered # => ["some_rather", "array_element", "long_named"]
p my_array # => ["some_rather", "long_named", "array_element", "entry", "long_named"]
For Arrays with duplicate Elements the best I know is:
array = [0,15,8,15,8]
indices_to_remove = [1,4]
res = array.reject.with_index{ |_,i| indices_to_remove.include?(i) }
returns
[0,8,15]
Additionally for arrays with unique entries such as a set of users
(using variable definitions from the question)
filtered_array = my_array - my_array.values_at(1,3)
Bonus, if your indices are inside an array themselves:
indices_to_remove = [1,3]
filtered_array = my_array - my_array.values_at(*indices_to_remove)
I think this is rather descriptive, not awkward and not shorter than needed.
One more possible solution with some addition, now it's also will work with negative indexes:
array = %w( a b c d e f )
indexes = [1, 2, -1, -9, -6, 6]
def array_except(array, *indexes)
indexes = indexes.map { |e| e.negative? ? e + array.length : e }
array.values_at(*((0...array.length).to_a - indexes))
end
array_except(array, *indexes)
=> ["d", "e"]
array_except(array, 0, -1)
=> ["b", "c", "d", "e"]

Ruby - Create hash with keys and values as arrays

I am new to Ruby and trying to find if I can create a hash which has keys as keys_Array and values as val_array.
Currently I have the following but it gives empty array.
key_hash = Hash.new { |hash, key|
hash.key = ["#{csv['values']} #{csv['keys']}"]
}
p key_hash.keys #empty array here
If you're trying to create a hash from two corresponding arrays for keys and values, this is quick way:
keys = ["a", "b", "c"]
values = [1, 2, 3]
hash = Hash[keys.zip(values)]
# => {"a"=>1, "b"=>2, "c"=>3}
# for symbols
hash = Hash[keys.map(&:to_sym).zip(values)]
# => {:a=>1, :b=>2, :c=>3}
If you want to make a new hash from two hashes (one contains keys for new hash and other contains values with respect to keys in first hash then only)
you can use something like below :
keys = ['k1', 'k2', 'k3']
values = ['b1', 'b2']
h = {}
keys.zip(values) { |a,b| h[a.to_sym] = b }
# => nil
p h
# => {:k1=>"b1", :k2=>"b2", :k3=>nil}
Keep in mind that if the keys are more and values are less in number then key will have nil value as mentioned in the e.g. but if the keys are less as compare to values then it will now consider the remaining values for e.g.
keys =['b1', 'b2']
=> ["b1", "b2"]
values = ['k1', 'k2', 'k3']
=> ["k1", "k2", "k3"]
h = {}
=> {}
keys.zip(values) { |a,b| h[a.to_sym] = b }
=> nil
p h
{:b1=>"k1", :b2=>"k2"}

Convert array to single object if array is a single object

Really simple question.
Is there a method to do the following?
["a"] => "a"
[1] => 1
[1,"a"] => [1, "a"]
i.e. if an array is a single object, return the object, otherwise return the array.
Without doing something ugly like
array.length == 1 ? array[0] : array
basically you should stick to what you wrote - it's simple and does what it should.
of course you may always monkey patch the Array definition... (unrecommended, but it does what you expect)
class Array
def first_or_array
length > 1 ? self : self[0]
end
end
[1].first_or_array # 1
[1, 2].first_or_array # [1, 2]

How to convert an array with a single element that is a hash?

I have a minor but annoying issue: my API call often returns an array of a single element, which is a hash as follows:
foo = [{"key1" => 1, "key2" => 10}]
I have to extract a value from the hash as follows: foo[0]["key2"]. Is there a more optimal/correct way of doing this?
You can get the first element (the hash) either by [0] / first:
foo = [{"key1" => 1, "key2" => 10}]
foo[0]
# => {"key1" => 1, "key2" => 10}
foo.first
# => {"key1" => 1, "key2" => 10}
or by using Ruby's parallel assignment:
foo, _ = [{"key1" => 1, "key2" => 10}]
foo
# => {"key1" => 1, "key2" => 10}
This assigns just the first element to foo instead of the whole array.
If you are doing this over and over again, it might be a good idea to implement a custom method on top of the API you are using.
Like Tilo's:
results.each{|r| do_something_with( r['key2] )}
You could also do:
results = [{"key1"=>1, "key2"=>10}]
results, _ = results
results.each{|key, result| do_something_with( result )}
Replacing result['key2'] with just result
Depending on what you want to do with the extracted values, either .each or .collect sounds like your best option. Here's an example with collect:
results = [{"key1"=>1, "key2"=>10}]
key2_array = results.collect{|result| result["key2"]}
# key2_array contains [10]
results = [{"key1"=>1, "key2"=>10},{"key1"=>2, "key2"=>20},{"key1"=>3, "key2"=>30}]
key2_array = results.collect{|result| result["key2"]}
# key2_array contains [10,20,30]
If the question is just of making your code look a bit cleaner, you can write a wrapper method around your API call:
def my_api_wrapper(x, y, z)
fb_api_call(x, y, z).first
end
# Later...
foo = my_api_wrapper(x, y, z)
foo["key2"]

Ruby mixed array to nested hash

I have a Ruby array whose elements alternate between Strings and Hashes. For example-
["1234", Hash#1, "5678", Hash#2]
I would like to create a nested hash structure from this. So,
hash["1234"]["key in hash#1"] = value
hash["5678"]["key in hash#2"] = value
Does anyone have/now a nice way of doing this? Thank you.
Simply use
hsh = Hash[*arr] #suppose arr is the array you have
It will slice 2 at a time and convert into hash.
I don't think there is a method on array to do this directly. The following code works and is quite easy to read.
hsh = {}
ary.each_slice(2) do |a, b|
hsh[a] = b
end
# Now `hsh` is as you want it to be
Guessing at what you want, since "key in hash#1" is not clear at all, nor have you defined what hash or value should be:
value = 42
h1 = {a:1}
h2 = {b:2}
a = ["1234",h1,"5678",h2]
a.each_slice(2).each{ |str,h| h[str] = value }
p h1, #=> {:a=>1, "1234"=>42}
h2 #=> {:b=>2, "5678"=>42}
Alternatively, perhaps you mean this:
h1 = {a:1}
h2 = {b:2}
a = ["1234",h1,"5678",h2]
hash = Hash[ a.each_slice(2).to_a ]
p hash #=> {"1234"=>{:a=>1}, "5678"=>{:b=>2}}
p hash["1234"][:a] #=> 1
let's guess, using facets just for fun:
require 'facets'
xs = ["1234", {:a => 1, :b => 2}, "5678", {:c => 3}]
xs.each_slice(2).mash.to_h
#=> {"1234"=>{:a=>1, :b=>2}, "5678"=>{:c=>3}}

Resources