Is it possible to iterate three arrays at the same time? - ruby

We can iterate two arrays at the same time using Array's zip method like:
#budget.zip(#actual).each do |budget, actual|
...
end
Is it possible to iterate three arrays? Can we use the transpose method to do the same?

>> [1,2,3].zip(["a","b","c"], [:a,:b,:c]) { |x, y, z| p [x, y, z] }
[1, "a", :a]
[2, "b", :b]
[3, "c", :c]
transpose also works but, unlike zip, it creates a new array right away:
>> [[1,2,3], ["a","b","c"], [:a,:b,:c]].transpose.each { |x, y, z| p [x, y, z] }
[1, "a", :a]
[2, "b", :b]
[3, "c", :c]
Notes:
You don't need each with zip, it takes a block.
Functional expressions are also possible. For example, using map: sums = xs.zip(ys, zs).map { |x, y, z| x + y + z }.
For an arbitrary number of arrays you can do xss[0].zip(*xss[1..-1]) or simply xss.transpose.

Related

Method to sort strings in descending order (in complex keys)

In order to descend-sort an array a of strings, reverse can be used.
a.sort.reverse
But when you want to use a string among multiple sort keys, that cannot be done. Suppose items is an array of items that have attributes attr1 (String), attr2 (String), attr3 (Integer). Sort can be done like:
items.sort_by{|item| [item.attr1, item.attr2, item.attr3]}
Switching from ascending to descending can be done independently for Integer by multiplying it with -1:
items.sort_by{|item| [item.attr1, item.attr2, -item.attr3]}
But such method is not straightforward for String. Can such method be defined? When you want to do descending sort with respect to attr2, it should be written like:
items.sort_by{|item| [item.attr1, item.attr2.some_method, item.attr3]}
I think you can always convert your strings into an array of integers (ord). Like this:
strings = [["Hello", "world"], ["Hello", "kitty"], ["Hello", "darling"]]
strings.sort_by do |s1, s2|
[
s1,
s2.chars.map(&:ord).map{ |n| -n }
]
end
PS:
As #CarySwoveland caught here is a corner case with empty string, which could be solved with this non elegant solution:
strings.sort_by do |s1, s2|
[
s1,
s2.chars.
map(&:ord).
tap{|chars| chars << -Float::INFINITY if chars.empty? }.
map{ |n| -n }
]
end
And #Jordan kindly mentioned that sort_by uses Schwartzian Transform so you don't need preprocessing at all.
The following supports all objects that respond to <=>.
def generalized_array_sort(arr, inc_or_dec)
arr.sort do |a,b|
comp = 0
a.zip(b).each_with_index do |(ae,be),i|
next if (ae<=>be).zero?
comp = (ae<=>be) * (inc_or_dec[i]==:inc ? 1 : -1)
break
end
comp
end
end
Example
arr = [[3, "dog"], [4, "cat"], [3, "cat"], [4, "dog"]]
inc_or_dec = [:inc, :dec]
generalized_array_sort(arr, inc_or_dec)
#=> [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
Another example
class A; end
class B<A; end
class C<B; end
[A,B,C].sort #=> [C, B, A]
arr = [[3, A], [4, B], [3, B], [4, A], [3, C], [4,C]]
inc_or_dec = [:inc, :dec]
generalized_array_sort(arr, inc_or_dec)
#=> [[3, A], [3, B], [3, C], [4, A], [4, B], [4, C]]
I'm not sure either of these passes your straightforwardness test, but I think both work correctly. Using #CarySwoveland's test data:
arr = [[3, "dog"], [4, "cat"], [3, "cat"], [4, "dog"]]
arr.sort_by {|a, b| [ a, *b.codepoints.map(&:-#) ] }
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
Alternatively, here's a solution that works regardless of the type (i.e. it needn't be a string):
arr.sort do |a, b|
c0 = a[0] <=> b[0]
next c0 unless c0.zero?
-(a[1] <=> b[1])
end
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
The latter could be generalized as a method like so:
def arr_cmp(a, b, *dirs)
return 0 if a.empty? && b.empty?
return a <=> b if dirs.empty?
a0, *a = a
b0, *b = b
dir, *dirs = dirs
c0 = a0 <=> b0
return arr_cmp(a, b, *dirs) if c0.zero?
dir * c0
end
This works just like <=> but as its final arguments takes a list of 1 or -1s indicating to the sort directions for each respective array element, e.g.:
a = [3, "dog"]
b = [3, "cat"]
arr_cmp(a, b, 1, 1) # => 1
arr_cmp(a, b, 1, -1) # => -1
Like <=> it's most useful in a sort block:
arr.sort {|a, b| arr_cmp(a, b, 1, -1) }
# => [[3, "dog"], [3, "cat"], [4, "dog"], [4, "cat"]]
I haven't tested it much, though, so there are probably edge cases for which it fails.
While I have no idea about generic academic implementation, in the real life I would go with:
class String
def hash_for_sort precision = 5
(#h_f_p ||= {})[precision] ||= self[0...precision].codepoints.map do |cp|
[cp, 99999].min.to_s.ljust 5, '0'
join.to_i
end
end
Now feel free to sort by -item.attr2.hash_for_sort.
The approach above has some glitches:
no valid sorting for the strings, that differ in > precision letters;
initial call to the function is O(self.length);
codepoints above 99999 would be considered equal (sorting is not accurate).
But taking into account the real circumstanses, I can not imagine when this won’t suffice.
P.S. If I were to solve this task precisely, I would search for an algorithm, converting strings to floats in a one-to-one manner.

How to search within a two-dimensional array

I'm trying to learn how to search within a two-dimensional array; for example:
array = [[1,1], [1,2], [1,3], [2,1], [2,4], [2,5]]
I want to know how to search within the array for the arrays that are of the form [1, y] and then show what the other y numbers are: [1, 2, 3].
If anyone can help me understand how to search only with numbers (as a lot of the examples I found include strings or hashes) and even where to look for the right resources even, that would be helpful.
Ruby allows you to look into an element by using parentheses in the block argument. select and map only assign a single block argument, but you can look into the element:
array.select{|(x, y)| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|(x, y)| x == 1}.map{|(x, y)| y}
# => [1, 2, 3]
You can omit the parentheses that correspond to the entire expression between |...|:
array.select{|x, y| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|x, y| x == 1}.map{|x, y| y}
# => [1, 2, 3]
As a coding style, it is a custom to mark unused variables as _:
array.select{|x, _| x == 1}
# => [[1, 1], [1, 2], [1, 3]]
array.select{|x, _| x == 1}.map{|_, y| y}
# => [1, 2, 3]
You can use Array#select and Array#map methods:
array = [[1,1], [1,2], [1,3], [2,1], [2,4], [2,5]]
#=> [[1, 1], [1, 2], [1, 3], [2, 1], [2, 4], [2, 5]]
array.select { |el| el[0] == 1 }
#=> [[1, 1], [1, 2], [1, 3]]
array.select { |el| el[0] == 1 }.map {|el| el[1] }
#=> [1, 2, 3]
For more methods on arrays explore docs.
If you first select and then map you can use the grep function to to it all in one function:
p array.grep ->x{x[0]==1}, &:last #=> [1,2,3]
Another way of doing the same thing is to use Array#map together with Array#compact. This has the benefit of only requiring one block and a trivial operation, which makes it a bit easier to comprehend.
array.map { |a, b| a if b == 1 }
#=> [1, 2, 3, nil, nil, nil]
array.map { |a, b| a if b == 1 }.compact
#=> [1, 2, 3]
You can use each_with_object:
array.each_with_object([]) { |(x, y), a| a << y if x == 1 }
#=> [1, 2, 3]

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] ]).

Replace array elements with map

I have two arrays:
#a = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
#b = [a, b, c]
I need to replace n-th column in a with b like:
swap_column(0)
#=> [a, 2, 3]
[b, 5, 6]
[c, 8, 9]
(This is for using Cramer's rule for solving equations system, if anybody wonders.)
The code I've come up with:
def swap_column(n)
#a.map.with_index { |row, j| row[n] = #b[j] }
end
How do I get rid of assignment here so that map returns the modified matrix while leaving #a intact?
What you wanted is dup. Also, you had the return value of the map.with_index block wrong.
def swap_column(i)
#a.map.with_index{|row, j| row = row.dup; row[i] = #b[j]; row}
end
or
def swap_column(i)
#a.map.with_index{|row, j| row.dup.tap{|row| row[i] = #b[j]}}
end
The answer by sawa is good and the main point is you need to dup your inner arrays for this to work properly. The only reason for this additional post is to point out that often when you are using with_index so that you can directly 1:1 index into another array you can simplify the code by using zip.
def swap_column(n)
#a.zip(#b).map {|r,e| r.dup.tap{|r| r[n] = e}}
end
What zip does is combine your two arrays into a new array where each element is an array made of the two corresponding elements of the initial arrays. In this case it would be an array of an array and an element you want to later use for replacement. We then map over those results and automatically destructure each element into the two pieces. We then dup the array piece and tap it to replace the nth element.
You can use transpose to do the following:
class M
attr :a, :b
def initialize
#a = [[1,2,3],
[4,5,6],
[7,8,9]
]
#b = [:a, :b, :c]
end
def swap_column(n)
t = #a.transpose
t[0] = #b
t.transpose
end
end
m = M.new
=> #<M:0x007ffdc2952e38 #a=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], #b=[:a, :b, :c]>
m.swap_column(0)
=> [[:a, 2, 3], [:b, 5, 6], [:c, 8, 9]]
m # m is unchanged
=> #<M:0x007ffdc2952e38 #a=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], #b=[:a, :b, :c]>

Ruby hypothetical - variable nested loops

This is just a thought exercise and I'd be interested in any opinions. Although if it works I can think of a few ways I'd use it.
Traditionally, if you wanted to perform a function on the results of a nested loop formed from arrays or ranges etc, you would write something like this:
def foo(x, y)
# Processing with x, y
end
iterable_one.each do |x|
iterable_two.each do |y|
my_func(x, y)
end
end
However, what if I had to add another level of nesting. Yes, I could just add an additonal level of looping. At this point, let's make foo take a variable number of arguments.
def foo(*inputs)
# Processing with variable inputs
end
iterable_one.each do |x|
iterable_two.each do |y|
iterable_three.each do |z|
my_func(x, y, x)
end
end
end
Now, assume I need to add another level of nesting. At this point, it's getting pretty gnarly.
My question, therefore is this: Is it possible to write something like the below?
[iterable_one, iterable_two, iterable_three].nested_each(my_func)
or perhaps
[iterable_one, iterable_two, iterable_three].nested_each { |args| my_func(args) }
Perhaps passing the arguments as actual arguments isn't feasible, could you maybe pass an array to my_func, containing parameters from combinations of the enumerables?
I'd be curious to know if this is possible, it's probably not that likely a scenario but after it occurred to me I wanted to know.
Array.product yields combinations of enums as if they were in nested loops. It takes multiple arguments. Demo:
a = [1,2,3]
b = %w(a b c)
c = [true, false]
all_enums = [a,b,c]
all_enums.shift.product(*all_enums) do |combi|
p combi
end
#[1, "a", true]
#[1, "a", false]
#[1, "b", true]
#...
You can use product:
[1,4].product([5,6],[3,5]) #=> [[1, 5, 3], [1, 5, 5], [1, 6, 3], [1, 6, 5], [4, 5, 3], [4, 5, 5], [4, 6, 3], [4, 6, 5]]

Resources