Stable #sort taking a block - ruby

We find here an implementation of a stable sort_by in Ruby, which works for the general case (i.e. I can supply my own comparision algorithm), and in this thread user tokland describes a very elegant way to do a stable sort_by:
module Enumerable
def stable_sort_by
sort_by.with_index { |x, idx| [yield(x), idx] }
end
end
The idea of using an Enumerator object together with with_index is surprisingly simple! I would like to find a similar elegant solution to create a stable version of the #sort function where it is given a comparison block. It would be used like this:
sorted_people = people.stable_sort do |person|
person.name
end

Here's a solution (but far from elegant):
module Enumerable
def stable_sort
each_with_index.sort { |(x, i), (y, j)|
r = yield(x, y)
r == 0 ? i <=> j : r
}.map(&:first)
end
end
It generates an array of [element, index] pairs and sorts them by passing each two elements to the given block (just like sort does). If the block returns 0, it compares the indices, otherwise, it returns the block's result. Afterwards, the elements are extracted from the generated array.
Example:
arr = [[2, :baz], [1,:foo], [1, :bar]]
arr.sort { |x, y| x[0] <=> y[0] }
#=> [[1, :bar], [1, :foo], [2, :baz]]
arr.stable_sort { |x, y| x[0] <=> y[0] }
#=> [[1, :foo], [1, :bar], [2, :baz]]

Related

Using Refinements Hierarchically

Refinements was an experimental addition to v2.0, then modified and made permanent in v2.1. It provides a way to avoid "monkey-patching" by providing "a way to extend a class locally".
I attempted to apply Refinements to this recent question which I will simplify thus:
a = [[1, "a"],
[2, "b"],
[3, "c"],
[4, "d"]]
b = [[1, "AA"],
[2, "B"],
[3, "C"],
[5, "D"]]
The element at offset i in a matches the element at offset i in b if:
a[i].first == b[i].first
and
a[i].last.downcase == b[i].last.downcase
In other words, the matching of the strings is independent of case.
The problem is to determine the number of elements of a that match the corresponding element of b. We see that the answer is two, the elements at offsets 1 and 2.
One way to do this is to monkey-patch String#==:
class String
alias :dbl_eql :==
def ==(other)
downcase.dbl_eql(other.downcase)
end
end
a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } }
#=> 2
or instead use Refinements:
module M
refine String do
alias :dbl_eql :==
def ==(other)
downcase.dbl_eql(other.downcase)
end
end
end
'a' == 'A'
#=> false (as expected)
a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } }
#=> 0 (as expected)
using M
'a' == 'A'
#=> true
a.zip(b).count { |ae,be| ae.zip(be).all? { |aee,bee| aee==bee } }
#=> 2
However, I would like to use Refinements like this:
using M
a.zip(b).count { |ae,be| ae == be }
#=> 0
but, as you see, that gives the wrong answer. That's because I'm invoking Array#== and the refinement does not apply within Array.
I could do this:
module N
refine Array do
def ==(other)
zip(other).all? do |ae,be|
case ae
when String
ae.downcase==be.downcase
else
ae==be
end
end
end
end
end
using N
a.zip(b).count { |ae,be| ae == be }
#=> 2
but that's not what I want. I want to do something like this:
module N
refine Array do
using M
end
end
using N
a.zip(b).count { |ae,be| ae == be }
#=> 0
but clearly that does not work.
My question: is there a way to refine String for use in Array, then refine Array for use in my method?
Wow, this was really interesting to play around with! Thanks for asking this question! I found a way that works!
module M
refine String do
alias :dbl_eql :==
def ==(other)
downcase.dbl_eql(other.downcase)
end
end
refine Array do
def ==(other)
zip(other).all? {|x, y| x == y}
end
end
end
a = [[1, "a"],
[2, "b"],
[3, "c"],
[4, "d"]]
b = [[1, "AA"],
[2, "B"],
[3, "C"],
[5, "D"]]
using M
a.zip(b).count { |ae,be| ae == be } # 2
Without redefining == in Array, the refinement won't apply. Interestingly, it also doesn't work if you do it in two separate modules; this doesn't work, for instance:
module M
refine String do
alias :dbl_eql :==
def ==(other)
downcase.dbl_eql(other.downcase)
end
end
end
using M
module N
refine Array do
def ==(other)
zip(other).all? {|x, y| x == y}
end
end
end
a = [[1, "a"],
[2, "b"],
[3, "c"],
[4, "d"]]
b = [[1, "AA"],
[2, "B"],
[3, "C"],
[5, "D"]]
using N
a.zip(b).count { |ae,be| ae == be } # 0
I'm not familiar enough with the implementation details of refine to be totally confident about why this behavior occurs. My guess is that the inside of a refine block is treated sort of as entering a different top-level scope, similarly to how refines defined outside of the current file only apply if the file they are defined in is parsed with require in the current file. This would also explain why nested refines don't work; the interior refine goes out of scope the moment it exits. This would also explain why monkey-patching Array as follows works:
class Array
using M
def ==(other)
zip(other).all? {|x, y| x == y}
end
end
This doesn't fall prey to the scoping issues that refine creates, so the refine on String stays in scope.

Ruby: Mapping a Hash

In Python, I can create a test hash with list comprehension that I check against a suite of test(s). How can I achieve the same thing in ruby? (I'm running on ruby 1.9.3)
Python:
test = {x: self.investor.annual_return(x) for x in xrange(1, 6)}
Ruby (attempt):
test = Hash[(1..5).map { |x| [x, #investor.annual_return(x)] }]
You want something like:
test = {}
(1..5).map { |i| test[i] = #investor.annual_return(i) }
I think your Ruby code is fine, depending on what version of Ruby you're running.
Starting with:
class Investor
def annual_return(i)
i * i
end
end
investor = Investor.new
In Ruby 1.9+, this will work:
test = Hash[ (1..5).map { |x| [x, investor.annual_return(x)] } ]
test # => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
However, prior to 1.9, Hash wouldn't convert an array of arrays containing key/value pairs, so we had to get a bit fancier, and flatten the nested elements into a single array, then "explode" those elements for Hash:
test = Hash[ *(1..5).map { |x| [x, investor.annual_return(x)] }.flatten ]
test # => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25}
The result is the same, it's just less hassle these days.
And, just to show what Ruby does as we build a hash this way:
(1..5).map { |x| [x, investor.annual_return(x)] }
# => [[1, 1], [2, 4], [3, 9], [4, 16], [5, 25]]
(1..5).map { |x| [x, investor.annual_return(x)] }.flatten
# => [1, 1, 2, 4, 3, 9, 4, 16, 5, 25]
You often see:
test = (1..5).reduce({}) {|h, x| h[x] = #investor.annual_return(x); h}
but (since Ruby 1.9) many prefer Enumerable#each_with_object:
test = (1..5).each_with_object({}) {|x, h| h[x] = #investor.annual_return(x)}
in part because there is no need to return the object h to the iterator, as there is with Enumerable#reduce (aka inject).
If I understand correctly what you're trying to do, you could try this:
{}.tap { |x| (1..5).each do |y| x[y] = #investor.annual_return(i) end }
You can do it easily with:
(1..5).map { |x| [x, #investor.annual_return(x)] }.to_h
(Doc: Array#to_h)
Hash[*array] is used to construct a hash from a flat array ([key1, value1, key2, value2, keyN, valueN]), whereas Array#to_h is used to construct a hash from an array of key-value pairs ([ [key1, value1], [key2, value2], [keyN, valueN] ]).

Ruby - mapping an array to hashmap

I have an array, and a function that returns a value given a value. Ultimately I want to create a hashmap that has the values of the array as key value, and the result of f(key_value) as the value. Is there a clean, simple way, like similar to each/map of Array, of doing this using block?
So something that is equivalent to
hsh = {}
[1,2,3,4].each do |x|
hsh[x] = f(x)
end
but looks more similar to this, in that it's simple and one line?
results = array.map { | x | f(x) }
Note that since Ruby 2.1.0 you can also use Array#to_h, like this:
[1,2,3,4].map{ |x| [x, f(x)] }.to_h
Ruby 2.6.0 enables passing a block to the to_h-method. This enables an even shorter syntax for creating a hash from an array:
[1, 2, 3, 4].to_h { |x| [x, f(x)] }
You could also define the function as the hash's default value:
hash = Hash.new {|hash, key| hash[key] = f(key) }
Then when you lookup a value, the hash will calculate and store it on the fly.
hash[10]
hash.inspect #=> { 10 => whatever_the_result_is }
You need each_with_object.
def f x
x * 2
end
t = [1, 2, 3, 4].each_with_object({}) do |x, memo|
memo[x] = f(x)
end
t # => {1=>2, 2=>4, 3=>6, 4=>8}
Another one:
t2 = [1, 2, 3, 4].map{|x| [x, f(x)]}
Hash[t2] # => {1=>2, 2=>4, 3=>6, 4=>8}
Check out the Hash::[] method.
Hash[ [1,2,3,4].collect { |x| [x, f(x)] } ]
Using Facets' mash (method to convert enumerable to hashes):
[1, 2, 3, 4].mash { |x| [x, f(x)] }
From Ruby 2.1:
[1, 2, 3, 4].map { |x| [x, f(x)] }.to_h
Also, Rails method index_with would be helpful:
a = ['a', 'bsdf', 'wqqwc']
a.index_with(&:size)
=> {"a"=>1, "bsdf"=>4, "wqqwc"=>5}
You're looking for reduce()|inject() method:
elem = [1,2,3,4]
h = elem.reduce({}) do |res, x|
res[x] = x**2
res
end
puts h
The argument passed to reduce({}) is the initial value of an intermediate object that is passed to the block as res variable. In each iteration we're adding new pair key: value to the res Hash and returing the Hash to be used in next iteration.
The method above precomputes a very practical hash of squared values:
{1=>1, 2=>4, 3=>9, 4=>16}

Overriding elements of an array in "each" loop

a = [1, 2, 3]
a.each do |x| x+=10 end
After this operation array a is still [1, 2, 3]. How to convert it into [11, 12, 13]?
Use the collect! method:
a = [1, 2, 3]
a.collect!{ |x| x + 10 }
There are two general classes of solutions:
Imperative object-mutating code
a.map! { |x| x + 10 }
An almost functional solution
a = a.map { |x| x + 10 }
Both techniques have their place.
I like the aliased name "map" myself. It has less characters.
The difference with these methods as compared to what you've done is two fold. One is that you have to use a method that modifies the initial array (typically these are the bang methods, or the methods which have a name ending in a ! (map!, collect!, ...) The second thing is that a.each is the method typically used for just going through the array to use the individual elements. Map or Collect methods return an array containing a return from each iteration of the block.
Hence, you could have done the following:
a = [1,2,3]
b = []
a.each do |x|
b << x+10
end
or you could use the map or collect method as demonstrated by dmarko or as here:
a = [1,2,3]
a = a.map {|x| x+10}

How can I shuffle an array/hash in Ruby?

For learning purposes, what is this called? Is the object being created an array or a hash?
stack_of_cards = []
This is how I'm filling it:
stack_of_cards << Card.new("A", "Spades", 1)
stack_of_cards << Card.new("2", "Spades", 2)
stack_of_cards << Card.new("3", "Spades", 3)
...
Here is my Card class:
class Card
attr_accessor :number, :suit, :value
def initialize(number, suit, value)
#number = number
#suit = suit
#value = value
end
def to_s
"#{#number} of #{#suit}"
end
end
I'd like to shuffle the elements in this array/hash (what is this called? :S)
Any suggestions?
stack_of_cards.shuffle
It is an Array, see http://www.ruby-doc.org/core-1.8.7/classes/Array.html for more information.
I've written the functional form, which returns a new Array, and it's the new one that's shuffled. You can instead use:
stack_of_cards.shuffle!
...to shuffle the array in-place.
If you want to shuffle a hash you can use something like this:
class Hash
def shuffle
Hash[self.to_a.sample(self.length)]
end
def shuffle!
self.replace(self.shuffle)
end
end
I've posted this answer since I always find this question if I search for "ruby shuffle hash".
In addition to using the shuffle method, you can use the sort method:
array.sort {|a, b| rand <=> rand }
This may be of use if you are using an older version of Ruby where shuffle is not implemented. As with shuffle!, you can use sort! to work on the existing array.
If you wanted to get crazy and write your own in-place shuffle method, you could do something like this.
def shuffle_me(array)
(array.size-1).downto(1) do |i|
j = rand(i+1)
array[i], array[j] = array[j], array[i]
end
array
end
For arrays:
array.shuffle
[1, 3, 2].shuffle
#=> [3, 1, 2]
For hashes:
Hash[*hash.to_a.shuffle.flatten]
Hash[*{a: 1, b: 2, c: 3}.to_a.shuffle.flatten(1)]
#=> {:b=>2, :c=>3, :a=>1}
#=> {:c=>3, :a=>1, :b=>2}
#=> {:a=>1, :b=>2, :c=>3}
# Also works for hashes containing arrays
Hash[*{a: [1, 2], b: [2, 3], c: [3, 4]}.to_a.shuffle.flatten(1)]
#=> {:b=>2, :c=>3, :a=>1}
#=> {:c=>[3, 4], :a=>[1, 2], :b=>[2, 3]}
Old question, but maybe help for someone else. I used it to create a card game, that's what #davissp14 wrote, it's called "Fisher-Yates algorithm"
module FisherYates
def self.shuffle(numbers)
n = numbers.length
while n > 0
x = rand(n-=1)
numbers[x], numbers[n] = numbers[n], numbers[x]
end
return numbers
end
end
Now you can use it as:
numbers_array = [1,2,3,4,5,6,7,8,9]
asnwer = FisherYates.shuffle(numbers_array)
return answer.inspect
https://dev.to/linuxander/fisher-yates-shuffle-with-ruby-1p7h
If you want to shuffle a hash, but don't want to overload the Hash class, you can use the sort function and then convert it back to a hash with the to_h function (Ruby 2.1+):
a = {"a" => 1, "b" => 2, "c" => 3}
puts a.inspect
a = a.sort {|a, b| rand <=> rand }.to_h
puts a.inspect
For an array:
array.shuffle
For a hash:
hash.sort_by{ rand() }
An alternative hash shuffle is ..
hash.to_a.shuffle.to_h

Resources