How to sort! arrays in ruby - 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).

Related

Ruby - how to pop a specific element from an array

What would be the easiest way in Ruby to pop a specific element from an array, similar to the .delete method of
a.delete(element)
rather than popping the first/last element or using .slice?
To make this more specific: for example, I can do
case names.sample when "John", "Dave", "Sam"
a.delete(names.sample)
end
to delete one of those names from a when it appears as a sample from names
However, I intend to use multiple samples and using a.delete()will remove all elements at once, rather than in succession like the result produced from shuffle!.pop where elements are popped in succession, so that the name can no longer be selected as a sample from a after the same name has been selected as a name.sample
I was wondering what the easiest way would be in Ruby to pop off these elements in succession, or if it is even possible at all in this context.
The Array class defines a pop method. It returns and deletes the last element in the array.
a = ["a", "b", "c"]
puts a.pop #=> "c"
puts a #=> ["a", "b"]
You can optionally pass an argument to pop that specifies how many elements to pop off.
a = ["a", "b", "c"]
puts a.pop(2) #=> ["b", "c"]
puts a #=> ["a"]
Addressing your last comment, you can use include?, index, and delete_at methods to achieve this. Assuming you're checking for "b" in an array:
a = ["a", "b", "c"]
value_index = a.index("b") #Returns the first occurring index of "b"
has_value = a.include?("b") #Returns whether "b" is in the list
a.delete_at(a.index("b")) if has_value #Removes "b" from the list
In this sample, "has_value" will be whether the a array contains the value "b", and "value_index" will be the first occurrence of "b". This will also delete the value "b" from the list.
If you want to remove all occurrences of "b", you can use include?, index, and delete_at with a while loop:
a = ["a", "b", "c", "a", "b", "c"]
while a.include?("b")
a.delete_at(a.index("b"))
end
#a will now be ["a", "c", "a", "c"]
See also the documentation for Array.
[..] intend to use multiple samples and using a.delete() will remove all elements at once, rather than in succession like the result produced from shuffle!.pop where elements are popped in succession, so that the name can no longer be selected as a sample from a after the same name has been selected as a name.sample[..]
Maybe you are looking something like this?
names = ["John", "Dave", "Sam"]
names.size.times { p names.delete(names.sample) }
#=> "Sam"
#=> "John"
#=> "Dave"

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

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