Convert a string to a path of keys in a hash - ruby

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('_');

Related

How can either convert a string into a hash or add it into a hash

So in basic Ruby I am trying to figure out how to either convert a string into a hash or put a string into a hash. I want the pokemon item as the key and a integer for the value.
Something like this:
hash = {}
pokemon_list = "pikachu charizard jigglypuff bulbasaur"
def create_poke_list(string)
hash << string.split
end
create_poke_list
Expected output:
hash
#=> {"pikachu"=>0, "charizard"=>0, "jigglypuff"=>0, "bulbasaur"=>0}
pokemon_list.split.product([0]).to_h
#=> {"pikachu"=>0, "charizard"=>0, "jigglypuff"=>0, "bulbasaur"=>0}
The steps:
a = pokemon_list.split
#=> ["pikachu", "charizard", "jigglypuff", "bulbasaur"]
b = a.product([0])
#=> [["pikachu", 0], ["charizard", 0], ["jigglypuff", 0], ["bulbasaur", 0]]
b.to_h
#=> <hash shown above>
Alternatively,
Hash[pokemon_list.split.product([0])]
Here Array#product is just a short-hand form of pokeman_list.zip(a) where a is an array consisting of pokenman_list.size equal elements, here zero. See Enumerable#zip also.
Or use String#gsub!
This is another way that does not require the string to be converted to an array.
pokemon_list.gsub(/[[:alpha:]]+/).with_object({}) { |w,h| h[w] = 0 }
#=> {"pikachu"=>0, "charizard"=>0, "jigglypuff"=>0, "bulbasaur"=>0}
This works because gsub returns an enumerator when executed without a block. It's admittedly an unusual use of that method (since it does not replace in characters in the string), but one that I've found useful at times.
If you need a hash that is initialised with default value of 0, you could simply do
hash = Hash.new(0)
p hash["pikachu"]
#=> 0

How can I turn an array of keys into an array of values?

I have a hash like
hash = { 'burger': 2, 'kebab': 5, 'pizza': 10 }
and I have an array of its keys like
['burger', 'burger', 'pizza']
How would I create an array of the corresponding values? Would inject method be the best method to sum value array together?
Actually, you don't even need to prepare the key arrays.
To get the keys: hash.keys
To get the values: hash.values
If you want only certain values or values in certain order, then
a = [:burger, :pizza]
hash.values_at(*a) # => [2, 10]
You can use map as you've particularly said of wanting value of particular key in an array in a particular order. Else, you can use hash.values
arr = ['burger', 'burger', 'pizza']
results = arr.map { |key| hash[key] }

How to sort! arrays in ruby

I want to sort my_array and then reverse the order.
Which markup is correct?
my_array.sort.reverse!
or
my_array.sort!.reverse
Or does it make any difference?
Thanks
You have to decompose the chain :
First, let's understand the difference between the sort and the sort! method.
If I write
array = [7,2,4]
array.sort!
array # => [2,4,7]
If you write
array = [7,2,4]
foo = array.sort
array # => [7,2,4]
foo # => [2,4,7]
The sort method sort the array and returns the result as the output of the function, whereas the sort! one directly modifies the existing array.
So if you write :
my_array.sort.reverse!
It is like writing :
(my_array.sort). # => Here we create a new array who is calculated by sorting the existing one
reverse! # => Then we reverse this new array, who is not referenced by a variable.
If you write :
(my_array.sort!). #=> Here you sort my_array and reinject the result into my_array !
reverse # Then reverse it and inject the result into a NEW array
So in both cases, you will not obtain what you want ! What you want to do is either :
my_array.sort!.reverse!
or :
new_array = my_array.sort.reverse
You'll get the same output, but one will modify the initial array. See this:
2.1.1 :001 > my_array = ["a","d","b","c"]
=> ["a", "d", "b", "c"]
Just declaring an array with a, b, c, and d in completely wrong orders.
2.1.1 :002 > my_array.sort.reverse!
=> ["d", "c", "b", "a"]
Running your first command on it returns a reverse-sorted array
2.1.1 :003 > my_array
=> ["a", "d", "b", "c"]
... but doesn't modify the original array itself.
2.1.1 :004 > my_array.sort!.reverse
=> ["d", "c", "b", "a"]
Running the second command returns the same result, the array sorted backwards
2.1.1 :005 > my_array
=> ["a", "b", "c", "d"]
But the array itself has been modified, but only by the sort! call. A ! after a method call 'saves' the changes to the object it's called on and returns the result. So in the second one:
You sort the array, saving the changes to it and returning the sorted array, then
Run reverse on the sorted array, which doesn't save to anything and only returns the result.
my_array.sort!.reverse will modify the receiver so my_array will be sorted after this call e.g.
my_array = [1,4,3,5,2]
my_array.sort!.reverse
#=> [5,4,3,2,1]
my_array
#=> [1,2,3,4,5]
the second form my_array.sort.reverse! will not modify my_array because sort will dup the array first then reverse! will modify this duped copy which is not being stored. This would have the same impact as my_array.sort.reverse without the !
my_array = [1,4,3,5,2]
my_array.sort.reverse!
#=> [5,4,3,2,1]
my_array
#=> [1,4,3,5,2]
Thirdly, something like my_array.sort!.reverse! will modify the receiver twice meaning my_array.sort! will sort the array in place and then .reverse! will reverse the array in place. so my_array will now be sorted and reversed.
my_array = [1,4,3,5,2]
my_array.sort!.reverse!
#=> [5,4,3,2,1]
my_array
#=> [5,4,3,2,1]
Although in this case I do not think you will need either bang ! method as the second form has no impact. bang ! in ruby means "dangerous", may alter the data or have other unexpected results.
my_array.sort!.reverse!
is the correct answer, since you want to change the array you already have.
my_array.sort.reverse!
creates a sorted copy and reverses it (the original doesn't change).
my_array.sort!.reverse
sorts the original and creates a reversed copy (the original isn't reversed).

Ruby how to return an element of a dictionary?

# dictionary = {"cat"=>"Sam"}
This a return a key
#dictionary.key(x)
This returns a value
#dictionary[x]
How do I return the entire element
"cat"=>"Sam"
#dictionary
should do the trick for you
whatever is the last evaluated expression in ruby is the return value of a method.
If you want to return the hash as a whole. the last line of the method should look like the line I have written above
Your example is a bit (?) misleading in a sense it only has one pair (while not necessarily), and you want to get one pair. What you call a "dictionary" is actually a hashmap (called a hash among Rubyists).
A hashrocket (=>) is a part of hash definition syntax. It can't be used outside it. That is, you can't get just one pair without constructing a new hash. So, a new such pair would look as: { key => value }.
So in order to do that, you'll need a key and a value in context of your code somewhere. And you've specified ways to get both if you have one. If you only have a value, then:
{ #dictionary.key(x) => x }
...and if just a key, then:
{ x => #dictionary[x] }
...but there is no practical need for this. If you want to process each pair in a hash, use an iterator to feed each pair into some code as an argument list:
#dictionary.each do |key, value|
# do stuff with key and value
end
This way a block of code will get each pair in a hash once.
If you want to get not a hash, but pairs of elements it's constructed of, you can convert your hash to an array:
#dictionary.to_a
# => [["cat", "Sam"]]
# Note the double braces! And see below.
# Let's say we have this:
#dictionary2 = { 1 => 2, 3 => 4}
#dictionary2[1]
# => 2
#dictionary2.to_a
# => [[1, 2], [3, 4]]
# Now double braces make sense, huh?
It returns an array of pairs (which are arrays as well) of all elements (keys and values) that your hashmap contains.
If you wish to return one element of a hash h, you will need to specify the key to identify the element. As the value for key k is h[k], the key-value pair, expressed as an array, is [k, h[k]]. If you wish to make that a hash with a single element, use Hash[[[k, h[k]]]].
For example, if
h = { "cat"=>"Sam", "dog"=>"Diva" }
and you only wanted to the element with key "cat", that would be
["cat", h["cat"]] #=> ["cat", "Sam"]
or
Hash[[["cat", h["cat"]]]] #=> {"cat"=>"Sam"}
With Ruby 2.1 you could alternatively get the hash like this:
[["cat", h["cat"]]].to_h #=> {"cat"=>"Sam"}
Let's look at a little more interesting case. Suppose you have an array arr containing some or all of the keys of a hash h. Then you can get all the key-value pairs for those keys by using the methods Enumerable#zip and Hash#values_at:
arr.zip(arr.values_at(*arr))
Suppose, for example,
h = { "cat"=>"Sam", "dog"=>"Diva", "pig"=>"Petunia", "owl"=>"Einstein" }
and
arr = ["dog", "owl"]
Then:
arr.zip(h.values_at(*arr))
#=> [["dog", "Diva"], ["owl", "Einstein"]]
In steps:
a = h.values_at(*arr)
#=> h.values_at(*["dog", "owl"])
#=> h.values_at("dog", "owl")
#=> ["Diva", "Einstein"]
arr.zip(a)
#=> [["dog", "Diva"], ["owl", "Einstein"]]
To instead express as a hash:
Hash[arr.zip(h.values_at(*arr))]
#=> {"dog"=>"Diva", "owl"=>"Einstein"}
You can get the key and value in one go - resulting in an array:
#h = {"cat"=>"Sam", "dog"=>"Phil"}
key, value = p h.assoc("cat") # => ["cat", "Sam"]
Use rassoc to search by value ( .rassoc("Sam") )

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

Resources