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

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]

Related

Convert a string to a path of keys in a hash

I have a string like string = "this_is_a_test" and a hash hash. How can I convert the string to a path of keys, so that it returns the value located at: hash['this']['is']['a']['test']?
I don't want to simply split the string into an array; I want to use the string as keys to access in a hash. The keys already exist in the hash.
Inject works nicely here:
hash = "this_is_a_test".split('_').reverse.inject("final value") { |h, s| {s => h} }
This returns:
{"this"=>{"is"=>{"a"=>{"test"=>"final value"}}}}
and:
hash['this']['is']['a']['test']
=> "final value"
The explanation here is that each iteration of inject returns a hash that contains the current string as a key, and the previous hash as a value, so each new key contains recursively all the hashes up to this point, including the deepest value that is passed as argument of inject.
This is why the array of keys needs to be reversed, because the hash is created from the inside-out.
edit: I believe I didn't understand the question correctly.
You actually meant to access an existing recursive hash.
Assuming the hash has been built using my previous method, accessing the innermost value can be achieved with inject too:
"this_is_a_test".split('_').inject(hash) { |h,v| h[v] }
=> "final value"
Also note that Ruby 2.3 implements the new Hash#dig method, which does exactly this, in a secure way:
path = "this_is_a_test".split('_')
hash.dig(*path) # => "final value"
could be something like this ?
keys = ["a", "b", "c"]
values = [1, 2, 3]
zipped = keys.zip(values)
=> [["a", 1], ["b", 2], ["c", 3]]
Hash[zipped]
=> {"a"=>1, "b"=>2, "c"=>3}
you are looking for String.split() function
var array = "this_is_a_test".split('_');

Trying to convert strings to symbols

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! .

Who can explain this?

I am having trouble understanding this comment.
Array({:a => "a", :b => "b"}) #=> [[:a, "a"], [:b, "b"]]
Could you explain how it works in detail?
{:a => "a", :b => "b"} creates a Hash.
Passing that to Array will create an array of arrays. Each array element of the outer array will be another array containing the key and the value of one item of the hash.
The Array methods transforms your hash into an array.
Therefore, for each entry of the hash, ruby will create an array with two elements : the key and the value of the entry in the hash.
You have two entries in your array :
:a => "a" which becomes [:a, "a"]
:b => "b" which becomes [:b, "b"]
It's actually a method provided by Kernel module.
Firstly it tries to call to_ary(return self for array), then to_a on argument.
You'll get the same result by using corresponding methods to_ary and to_a.

How to remove elements of array in place returning the removed elements

I have an array arr. I want to destructively remove elements from arr based on a condition, returning the removed elements.
arr = [1,2,3]
arr.some_method{|a| a > 1} #=> [2, 3]
arr #=> [1]
My first try was reject!:
arr = [1,2,3]
arr.reject!{|a| a > 1}
but the returning blocks and arr's value are both [1].
I could write a custom function, but I think there is an explicit method for this. What would that be?
Update after the question was answered:
partition method turns out to be useful for implementing this behavior for hash as well. How can I remove elements of a hash, returning the removed elements and the modified hash?
hash = {:x => 1, :y => 2, :z => 3}
comp_hash, hash = hash.partition{|k,v| v > 1}.map{|a| Hash[a]}
comp_hash #=> {:y=>2, :z=>3}
hash #=> {:x=>1}
I'd use partition here. It doesn't modify self inplace, but returns two new arrays. By assigning the second array to arr again, it gets the results you want:
comp_arr, arr = arr.partition { |a| a > 1 }
See the documentation of partition.
All methods with a trailing bang ! modify the receiver and it seems to be a convention that these methods return the resulting object because the non-bang do so.
What you can to do though is something like this:
b = (arr.dup - arr.reject!{|a| a>1 })
b # => [2,3]
arr #=> [1]
Here is a link to a ruby styleguide which has a section on nameing - although its rather short
To remove (in place) elements of array returning the removed elements one could use delete method, as per Array class documentation:
a = [ "a", "b", "b", "b", "c" ]
a.delete("b") #=> "b"
a #=> ["a", "c"]
a.delete("z") #=> nil
a.delete("z") { "not found" } #=> "not found"
It accepts block so custom behavior could be added, as needed

Why is this Ruby 1.9 code resulting in an empty hash?

I'm trying to zip 3 arrays into a hash. The hash is coming up empty, though. Here's sample code to reproduce using Ruby 1.9:
>> foo0 = ["a","b"]
=> ["a", "b"]
>> foo1 = ["c","d"]
=> ["c", "d"]
>> foo2 = ["e", "f"]
=> ["e", "f"]
>> h = Hash[foo0.zip(foo1, foo2)]
=> {}
I'd like to zip these and then do something like:
h.each_pair do |letter0, letter1, letter2|
# process letter0, letter1
end
It's not clear what you expect the output to be but the [] operator of the Hash class is intended to take an even number of arguments and return a new hash where each even numbered argument is the key for the corresponding odd numbered value.
For example, if you introduce foo3 = ["d"] and you want to get a hash like {"a"=>"b", "c"=>"d"} you could do the following:
>> Hash[*foo0.zip(foo1, foo2, foo3).flatten]
=> {"a"=>"b", "c"=>"d"}
Hash[] doesn't work quite like you're assuming. Instead, try this:
>> Hash[*foo0, *foo1, *foo2]
=> {"a"=>"b", "c"=>"d", "e"=>"f"}
or, my preferred approach:
>> Hash[*[foo0, foo1, foo2].flatten]
=> {"a"=>"b", "c"=>"d", "e"=>"f"}
Basically, Hash[] is expecting an even number of arguments as in Hash[key1, val1, ...]. The splat operator * is applying the arrays as arguments.
It looks like foo0.zip(foo1,foo2) generates:
[["a", "b", "c"]]
Which is not an acceptable input for Hash[]. You need to pass it a flat array.
you don't need Hash for what you are trying to accomplish, zip does it for you
foo0.zip(foo1, foo2) do |f0, f1, f2|
#process stuff here
end

Resources