Trying to convert strings to symbols - ruby

This creates an infinite loop and i'm blanking as to why this happens. When I don't use the push command, the loop doesn't happen.
#strings = ["HTML", "CSS", "JavaScript", "Python", "Ruby"]
symbols = [ "a", "b", "c" ]
symbols.each do |x|
symbols.push(x.to_sym)
end

The following code appends items to symbols array (the same array), while iterate it; the block provides infinite items by appending to the array. (each use those items for iteration)
symbols.each do |x| symbols.push(x.to_sym) end
#^^^^^^ ^^^^^^^
Use Enumerable#map instead:
symbols.map { |x| x.to_sym }
# => [:a, :b, :c]
symbols.map &:to_sym
# => [:a, :b, :c]
or use another array instead of using the same array.
new_symbols = []
symbols.each do |x| new_symbols.push(x.to_sym) end
new_symbols
# => [:a, :b, :c]

This is because, in each iteration with #each method you keep adding a new element to your original array symbols. Which causes an infinite loop. Rather do as below using Array#each_index:
code - I
symbols = [ "a", "b", "c" ]
symbols.each_index do |i|
symbols[i]=symbols[i].to_sym
end
symbols # => [:a, :b, :c]
But the above code will update your original array. A slight modification in your code will do work as well :
code - II
symbols = [ "a", "b", "c" ]
nw_ary = symbols.each_with_object([]) do |e,a|
a << e.to_sym
end
nw_ary # => [:a, :b, :c]
But you could also use symbols.map(&:to_sym) inplace of code -II and
symbols.map!(&:to_sym) inplace of code -I. Read Array#map and Array#map! .

Related

How to convert an array of strings into values (symbols)?

How can I easily convert
a = [ "b", "c", "d" ]
into
a = [ :b, :c, :d ]
and vice versa?
I went through the docs but didn't find a solution
To iterate over an array, returning a new array with updated values, use Array#map. That's the key method you need to know about.
I'm not sure what you mean by "converting strings into values", but objects like :b in ruby are called Symbols.
You can convert a String into a Symbol by calling to_sym. (And vice-versa by calling to_s.)
So putting this all together, you can convert the array by doing this:
a = [ "b", "c", "d" ]
a.map { |letter| letter.to_sym }
# => [:b, :c, :d]
# Or equivalently, there's a shorthand for this:
a.map(&:to_sym)
If you want to update the original array, rather than return a new array, use Array.map! instead of Array.map:
you can call to_sym on each element in a map:
a.map { |letter| letter.to_sym }
=> [:b, :c, :d]

Is there a cleaner, shorter, built-in way to wrap elements in an iterable in ruby?

This code works:
permission.class.ancestors.include?(Enumerable) ? permission : [permission]
The content in permission can either by a symbol or any sequence (say, an array). If a single element is passed, it is wrapped in an array.
Is there a method already doing this? (May be either ruby-built-in in 2.2.2 or added by rails 4.2.0).
Yes, it seems like you're describing the Array.wrap() extension provided by ActiveSupport in in Rails (active_support/core_ext):
>> list = ['a', :b, /c/]
=> ["a", :b, /c/]
>> Array.wrap(list)
=> ["a", :b, /c/]
>> map = {"a" => :foo, b: :bar, /c/ => 'baz'}
=> {"a"=>:foo, :b=>:bar, /c/=>"baz"}
>> Array.wrap(map)
=> [{"a"=>:foo, :b=>:bar, /c/=>"baz"}]
>> Array.wrap(:symbol_literal)
=> [:symbol_literal]
You can find the documentation here: http://apidock.com/rails/Array/wrap/class.
The Array(...) method in the core Ruby library has similar behavior, but will convert a hash to a nested array:
>> Array(list)
=> ["a", :b, /c/]
>> Array(map)
=> [["a", :foo], [:b, :bar], [/c/, "baz"]] # Probably not what you want
>> Array(:symbol_literal)
=> [:symbol_literal]
The documentation in the link provided above contains a more comprehensive explanation of Array.wrap() vs Array()

Hash with array as key

I'm defining a hash with an array as a key and another array as its value. For example:
for_example = {[0,1] => [:a, :b, :c]}
Everything is as expected below.
my_hash = Hash.new([])
an_array_as_key = [4,2]
my_hash[an_array_as_key] #=> []
my_hash[an_array_as_key] << "the" #=> ["the"]
my_hash[an_array_as_key] << "universal" #=> ["the", "universal"]
my_hash[an_array_as_key] << "answer" #=> ["the", "universal", "answer"]
But if I try to access the keys:
my_hash #=> {}
my_hash.keys #=> []
my_hash.count #=> 0
my_hash.values #=> []
my_hash.fetch(an_array_as_key) # KeyError: key not found: [4, 2]
my_hash.has_key?(an_array_as_key) #=> false
Rehash doesn't help:
my_hash #=> {}
my_hash.rehash #=> {}
my_hash.keys #=> []
But the values are saved:
my_hash[an_array_as_key] #=> ["the", "universal", "answer"]
Am I missing something?
To understand this, You need to understand the difference between Hash::new and Hash::new(ob). Suppose you define a hash object using Hash::new or hash literal {}. Now whenever you will write a code hsh[any_key], there is two kind of output may be seen, if any_key don't exist, then default value nil will be returned,otherwise whatever value is associated with the key will be returned. The same explanation will be applicable if you create any Hash object using Hash.new.
Now Hash.new(ob) is same as Hash.new, with one difference is, you can set any default value you want, for non existent keys of that hash object.
my_hash = Hash.new([])
my_hash[2] # => []
my_hash[2].object_id # => 83664630
my_hash[4] # => []
my_hash[4].object_id # => 83664630
my_hash[3] << 4 # => [4]
my_hash[3] # => [4]
my_hash[3].object_id # => 83664630
my_hash[5] << 8 # => [4, 8]
my_hash[5] # => [4, 8]
my_hash[5].object_id # => 83664630
Now see in the above example my_hash has no keys like 2,3 and 4. But the object_id proved that, all key access results in to return the same array object. my_hash[2] is not adding the key to the hash my_hash, rather trying to access the value of the key 2 if that key exist, otherwise it is returning the default value of my_hash. Remember all lines like my_hash[2],my_hash[3] etc is nothing but a call to Hash#[] method.
But there is a third way to go, may be you are looking for, which is Hash::new {|hash, key| block }.With this style you can add key to the hash object if that key doesn't exist, with a default value of same class instance,but not the same instance., while you are doing actually Hash#[] method call.
my_hash = Hash.new { |hash, key| hash[key] = []}
my_hash[2] # => []
my_hash[2].object_id # => 76312700
my_hash[3] # => []
my_hash[3].object_id # => 76312060
my_hash.keys # => [2, 3]

Hash invert in Ruby?

I've got a hash of the format:
{key1 => [a, b, c], key2 => [d, e, f]}
and I want to end up with:
{ a => key1, b => key1, c => key1, d => key2 ... }
What's the easiest way of achieving this?
I'm using Ruby on Rails.
UPDATE
OK I managed to extract the real object from the server log, it is being pushed via AJAX.
Parameters: {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
first variant
hash.map{|k, v| v.map{|f| {f => k}}}.flatten
#=> [{"a"=>:key1}, {"b"=>:key1}, {"c"=>:key1}, {"d"=>:key2}, {"e"=>:key2}, {"f"=>:key2}]
or
hash.inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
UPD
ok, your hash is:
hash = {"status"=>{"1"=>["1", "14"], "2"=>["7", "12", "8", "13"]}}
hash["status"].inject({}){|h, (k,v)| v.map{|f| h[f] = k}; h}
#=> {"12"=>"2", "7"=>"2", "13"=>"2", "8"=>"2", "14"=>"1", "1"=>"1"}
Lots of other good answers. Just wanted to toss this one in too for Ruby 2.0 and 1.9.3:
hash = {apple: [1, 14], orange: [7, 12, 8, 13]}
Hash[hash.flat_map{ |k, v| v.map{ |i| [i, k] } }]
# => {1=>:apple, 14=>:apple, 7=>:orange, 12=>:orange, 8=>:orange, 13=>:orange}
This is leveraging: Hash::[] and Enumerable#flat_map
Also in these new versions there is Enumerable::each_with_object which is very similar to Enumerable::inject/Enumerable::reduce:
hash.each_with_object(Hash.new){ |(k, v), inverse|
v.each{ |e| inverse[e] = k }
}
Performing a quick benchmark (Ruby 2.0.0p0; 2012 Macbook Air) using an original hash with 100 keys, each with 100 distinct values:
Hash::[] w/ Enumerable#flat_map
155.7 (±9.0%) i/s - 780 in 5.066286s
Enumerable#each_with_object w/ Enumerable#each
199.7 (±21.0%) i/s - 940 in 5.068926s
Shows that the each_with_object variant is faster for that data set.
Ok, let's guess. You say you have an array but I agree with Benoit that what you probably have is a hash. A functional approach:
h = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
h.map { |k, vs| Hash[vs.map { |v| [v, k] }] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
Also:
h.map { |k, vs| Hash[vs.product([k])] }.inject(:merge)
#=> {"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
In the case where a value corresponds to more than one key, like "c" in this example...
{ :key1 => ["a", "b", "c"], :key2 => ["c", "d", "e"]}
...some of the other answers will not give the expected result. We will need the reversed hash to store the keys in arrays, like so:
{ "a" => [:key1], "b" => [:key1], "c" => [:key1, :key2], "d" => [:key2], "e" => [:key2] }
This should do the trick:
reverse = {}
hash.each{ |k,vs|
vs.each{ |v|
reverse[v] ||= []
reverse[v] << k
}
}
This was my use case, and I would have defined my problem much the same way as the OP (in fact, a search for a similar phrase got me here), so I suspect this answer may help other searchers.
If you're looking to reverse a hash formatted like this, the following may help you:
a = {:key1 => ["a", "b", "c"], :key2 => ["d", "e", "f"]}
a.inject({}) do |memo, (key, values)|
values.each {|value| memo[value] = key }
memo
end
this returns:
{"a"=>:key1, "b"=>:key1, "c"=>:key1, "d"=>:key2, "e"=>:key2, "f"=>:key2}
new_hash={}
hash = {"key1" => ['a', 'b', 'c'], "key2" => ['d','e','f']}
hash.each_pair{|key, val|val.each{|v| new_hash[v] = key }}
This gives
new_hash # {"a"=>"key1", "b"=>"key1", "c"=>"key1", "d"=>"key2", "e"=>"key2", "f"=>"key2"}
If you want to correctly deal with duplicate values, then you should use the Hash#inverse
from Facets of Ruby
Hash#inverse preserves duplicate values,
e.g. it ensures that hash.inverse.inverse == hash
either:
use Hash#inverse from here: http://www.unixgods.org/Ruby/invert_hash.html
use Hash#inverse from FacetsOfRuby library 'facets'
usage like this:
require 'facets'
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
The code looks like this:
# this doesn't looks quite as elegant as the other solutions here,
# but if you call inverse twice, it will preserve the elements of the original hash
# true inversion of Ruby Hash / preserves all elements in original hash
# e.g. hash.inverse.inverse ~ h
class Hash
def inverse
i = Hash.new
self.each_pair{ |k,v|
if (v.class == Array)
v.each{ |x|
i[x] = i.has_key?(x) ? [k,i[x]].flatten : k
}
else
i[v] = i.has_key?(v) ? [k,i[v]].flatten : k
end
}
return i
end
end
h = {:key1 => [:a, :b, :c], :key2 => [:d, :e, :f]}
=> {:key1=>[:a, :b, :c], :key2=>[:d, :e, :f]}
h.inverse
=> {:a=>:key1, :b=>:key1, :c=>:key1, :d=>:key2, :e=>:key2, :f=>:key2}
One way to achieve what you're looking for:
arr = [{["k1"] => ["a", "b", "c"]}, {["k2"] => ["d", "e", "f"]}]
results_arr = []
arr.each do |hsh|
hsh.values.flatten.each do |val|
results_arr << { [val] => hsh.keys.first }···
end
end
Result: [{["a"]=>["k1"]}, {["b"]=>["k1"]}, {["c"]=>["k1"]}, {["d"]=>["k2"]}, {["e"]=>["k2"]}, {["f"]=>["k2"]}]

How to change Hash values?

I'd like to replace each value in a hash with value.some_method.
For example, for given a simple hash:
{"a" => "b", "c" => "d"}`
every value should be .upcased, so it looks like:
{"a" => "B", "c" => "D"}
I tried #collect and #map but always just get arrays back. Is there an elegant way to do this?
UPDATE
Damn, I forgot: The hash is in an instance variable which should not be changed. I need a new hash with the changed values, but would prefer not to define that variable explicitly and then loop over the hash filling it. Something like:
new_hash = hash.magic{ ... }
my_hash.each { |k, v| my_hash[k] = v.upcase }
or, if you'd prefer to do it non-destructively, and return a new hash instead of modifying my_hash:
a_new_hash = my_hash.inject({}) { |h, (k, v)| h[k] = v.upcase; h }
This last version has the added benefit that you could transform the keys too.
Since ruby 2.4.0 you can use native Hash#transform_values method:
hash = {"a" => "b", "c" => "d"}
new_hash = hash.transform_values(&:upcase)
# => {"a" => "B", "c" => "D"}
There is also destructive Hash#transform_values! version.
You can collect the values, and convert it from Array to Hash again.
Like this:
config = Hash[ config.collect {|k,v| [k, v.upcase] } ]
This will do it:
my_hash.each_with_object({}) { |(key, value), hash| hash[key] = value.upcase }
As opposed to inject the advantage is that you are in no need to return the hash again inside the block.
There's a method for that in ActiveSupport v4.2.0. It's called transform_values and basically just executes a block for each key-value-pair.
Since they're doing it with a each I think there's no better way than to loop through.
hash = {sample: 'gach'}
result = {}
hash.each do |key, value|
result[key] = do_stuff(value)
end
Update:
Since Ruby 2.4.0 you can natively use #transform_values and #transform_values!.
Try this function:
h = {"a" => "b", "c" => "d"}
h.each{|i,j| j.upcase!} # now contains {"a" => "B", "c" => "D"}.
You may want to go a step further and do this on a nested hash. Certainly this happens a fair amount with Rails projects.
Here's some code to ensure a params hash is in UTF-8:
def convert_hash hash
hash.inject({}) do |h,(k,v)|
if v.kind_of? String
h[k] = to_utf8(v)
else
h[k] = convert_hash(v)
end
h
end
end
# Iconv UTF-8 helper
# Converts strings into valid UTF-8
#
# #param [String] untrusted_string the string to convert to UTF-8
# #return [String] your string in UTF-8
def to_utf8 untrusted_string=""
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
ic.iconv(untrusted_string + ' ')[0..-2]
end
I do something like this:
new_hash = Hash[*original_hash.collect{|key,value| [key,value + 1]}.flatten]
This provides you with the facilities to transform the key or value via any expression also (and it's non-destructive, of course).
Ruby has the tap method (1.8.7, 1.9.3 and 2.1.0) that's very useful for stuff like this.
original_hash = { :a => 'a', :b => 'b' }
original_hash.clone.tap{ |h| h.each{ |k,v| h[k] = v.upcase } }
# => {:a=>"A", :b=>"B"}
original_hash # => {:a=>"a", :b=>"b"}
Rails-specific
In case someone only needs to call to_s method to each of the values and is not using Rails 4.2 ( which includes transform_values method link), you can do the following:
original_hash = { :a => 'a', :b => BigDecimal('23.4') }
#=> {:a=>"a", :b=>#<BigDecimal:5c03a00,'0.234E2',18(18)>}
JSON(original_hash.to_json)
#=> {"a"=>"a", "b"=>"23.4"}
Note: The use of 'json' library is required.
Note 2: This will turn keys into strings as well
If you know that the values are strings, you can call the replace method on them while in the cycle: this way you will change the value.
Altohugh this is limited to the case in which the values are strings and hence doesn't answer the question fully, I thought it can be useful to someone.
new_hash = old_hash.merge(old_hash) do |_key, value, _value|
value.upcase
end
# old_hash = {"a" => "b", "c" => "d"}
# new_hash = {"a" => "B", "c" => "D"}

Resources