Reputation: 312
Probably, to make a more general question is how to do list comprehension in Ruby.
Let's say given a number N (say N=5) I want to get an array like this:
[[0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
Using imperative way I can do:
arr = []
N = 5
(0..N-1).each do |i|
(i+1..N-1).each do |j|
arr << [i, j]
end
end
puts arr
Using functional way:
(0..N-1).collect{|el1| (el1+1..N-1).collect{|el2| [el1, el2]}}.flatten.each_slice(2).to_a
I don't like that the second example relies on the fact that the array is sorted in the right order. Is there a cleaner way to get the array in a functional way (without the extra flatten and slice)?
Upvotes: 0
Views: 122
Reputation: 110755
You could do this:
Array(0...N).combination(2).to_a
which looks nice, but has the disadvantage that it generates N!/(4*(N-2)!) combinations it doesn't use.
Edit: I originally had select { |x,y| x < y }
in place of to_a
in the above. @Stefan pointed out that select
is supurfluous. Maybe I had permutation(2)
on the brain.
Here's another way, a variant of @Arup's answer:
a = Array(0...N)
a.size.times.reduce([]) { |b,_| b + [a.shift].product(a) }
Upvotes: 1
Reputation: 8106
Most efficient way to do it is Stefan's, which was a comment to Cary's answer (modified to use n
variable in Arup's comment to this answer), which was a modification from Cary's answer, so +1 to all those guys:
2.1.0p0 :001 > n = 5
=> 5
2.1.0p0 :002 > a = (0...n).to_a.combination(2).to_a
=> [[0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
Some benchmarks:
2.1.0p0 :001 > require 'benchmark'
=> true
2.1.0p0 :002 > n = 5
=> 5
2.1.0p0 :003 > puts Benchmark.measure { 1000000.times { a = n.times.flat_map {|i| [i].product (i+1..n-1).to_a} } }
8.050000 0.010000 8.060000 ( 8.051535)
=> nil
2.1.0p0 :004 > puts Benchmark.measure { 1000000.times { a = n.times.flat_map {|x| (x+1..n-1).map{|y| [x,y]}} } }
6.250000 0.000000 6.250000 ( 6.249600)
=> nil
2.1.0p0 :005 > puts Benchmark.measure { 1000000.times { a = Array(0...n).combination(2).select { |x,y| x < y } } }
5.110000 0.010000 5.120000 ( 5.120506)
=> nil
2.1.0p0 :006 > puts Benchmark.measure { 1000000.times { a = n.times.to_a.combination(2).to_a } }
4.050000 0.000000 4.050000 ( 4.045895)
=> nil
2.1.0p0 :007 > puts Benchmark.measure { 1000000.times { a = Array(0...n).combination(2).to_a } }
3.920000 0.000000 3.920000 ( 3.928282)
=> nil
2.1.0p0 :008 > puts Benchmark.measure { 1000000.times { a = (0...n).to_a.combination(2).to_a } }
3.520000 0.000000 3.520000 ( 3.522056)
=> nil
Upvotes: 0
Reputation: 118299
I would do as below :
n = 5
a = n.times.flat_map do |i|
[i].product (i+1..n-1).to_a
end
a # => [[0, 1], [0, 2], [0, 3], [0, 4], [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
Upvotes: 2