Nested Array Initialization && Assignment Oddity [duplicate] - ruby

This question already has an answer here:
Ruby Array Initialization [duplicate]
(1 answer)
Closed 3 years ago.
What is going on in the Array initialization that's causing the disparity in int assignment?
arr = Array.new(3) { Array.new(3) { Array.new(3) } }
3.times do |x|
3.times do |y|
3.times do |z|
arr[x][y][z] = Random.rand(1..9)
end
end
end
puts arr.to_s
#=> [[[3, 3, 1], [4, 9, 6], [2, 4, 7]], [[1, 6, 8], [9, 8, 5], [1, 7, 5]], [[2, 5, 9], [2, 8, 8], [9, 1, 8]]]
#=> [[[2, 4, 4], [6, 8, 9], [6, 2, 7]], [[2, 7, 7], [2, 1, 1], [8, 7, 7]], [[5, 3, 5], [3, 8, 1], [7, 6, 6]]]
#=> [[[4, 9, 1], [1, 6, 8], [9, 2, 5]], [[3, 7, 1], [7, 5, 4], [9, 9, 9]], [[6, 8, 2], [8, 2, 8], [2, 9, 9]]]
arr = Array.new(3, Array.new(3, Array.new(3)))
3.times do |x|
3.times do |y|
3.times do |z|
arr[x][y][z] = Random.rand(1..9)
end
end
end
puts arr.to_s
#=> [[[8, 2, 4], [8, 2, 4], [8, 2, 4]], [[8, 2, 4], [8, 2, 4], [8, 2, 4]], [[8, 2, 4], [8, 2, 4], [8, 2, 4]]]
#=> [[[2, 1, 4], [2, 1, 4], [2, 1, 4]], [[2, 1, 4], [2, 1, 4], [2, 1, 4]], [[2, 1, 4], [2, 1, 4], [2, 1, 4]]]
#=> [[[2, 7, 6], [2, 7, 6], [2, 7, 6]], [[2, 7, 6], [2, 7, 6], [2, 7, 6]], [[2, 7, 6], [2, 7, 6], [2, 7, 6]]]

When you use new(size=0, obj=nil) to initialize the array:
From the doc:
In the first form, if no arguments are sent, the new array will be
empty. When a size and an optional obj are sent, an array is created
with size copies of obj. Take notice that all elements will reference
the same object obj.
If you want multiple copy, then you should use the block version which uses the result of that block each time an element of the array needs to be initialized.

Related

Integer to array of chunks

I need to turn an integer into an array of intervals. For example,
number_to_steps(number: 10, step: 3)
# => [[0, 2], [3, 5], [6, 8], [9, 9]]
number_to_steps(number: 7, step: 2)
# => [[0, 1], [2, 3], [4, 5], [6, 6]]
number_to_steps(number: 8, step: 2)
# => [[0, 1], [2, 3], [4, 5], [6, 7]]
I tried:
def number_to_ranges(number:, size:)
chunks = ((number - 1) / size.to_f).ceil
(0..chunks - 1).map do |index|
from = index * size
to = (index + 1) * size - 1
[ from, to > number ? number : to ]
end
end
But it doesn't work properly. For example,
number_to_ranges(number: 14, step: 4)
[[0, 3], [4, 7], [8, 11], [12, 14]]
should not go to 14.
Any idea?
An alternative (to your method) would be to use each_slice.
def number_to_steps(number:, step:)
(0...number).each_slice(step).map { |arr| [arr.first, arr.last] }
end
Tests:
number_to_steps(number: 10, step: 3)
#=> [[0, 2], [3, 5], [6, 8], [9, 9]]
number_to_steps(number: 7, step: 2)
#=> [[0, 1], [2, 3], [4, 5], [6, 6]]
number_to_steps(number: 8, step: 2)
#=> [[0, 1], [2, 3], [4, 5], [6, 7]]
def number_to_steps(number:, size:)
(0..number-1).step(size).map { |i| [i, [i+size, number].min-1] }
end
number_to_steps(number: 10, size: 3)
#=> [[0, 2], [3, 5], [6, 8], [9, 9]]
number_to_steps(number: 7, size: 2)
#=> [[0, 1], [2, 3], [4, 5], [6, 6]]
number_to_steps(number: 8, size: 2)
#=> [[0, 1], [2, 3], [4, 5], [6, 7]]
There are two places where you need to change your method number_to_ranges.
When calculating the number of chunks, change number - 1 to number.
When finding the last element for a chunk, do number - 1 instead of number.
This is what the final code should look like:
def number_to_ranges(number:, size:)
chunks = (number / size.to_f).ceil
(0...chunks).map do |index|
from = index * size
to = (index + 1) * size - 1
[from, [to, number - 1].min]
end
end
number_to_ranges(number: 10, size: 3)
=> [[0, 2], [3, 5], [6, 8], [9, 9]]
number_to_ranges(number: 7, size: 2)
=> [[0, 1], [2, 3], [4, 5], [6, 6]]
number_to_ranges(number: 8, size: 2)
=> [[0, 1], [2, 3], [4, 5], [6, 7]]
number_to_ranges(number: 14, size: 4)
=> [[0, 3], [4, 7], [8, 11], [12, 13]]

Combining arrays in Ruby

The following array contains two arrays each having 5 integer values:
[[1,2,3,4,5],[6,7,8,9,10]]
I want to combine these in such a way that it generates five different arrays by combining values of both arrays at index 0,1.. upto 4.
The output should be like this:
[[1,6],[2,7],[3,8],[4,9],[5,10]]
Is there any simplest way to do this?
What about transpose method?
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
#=> [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
a.transpose
#=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
this method also can help you in future, as example:
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
#=> [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]]
a.transpose
#=> [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15]]
a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
a.first.zip(a.last)
If you're sure your sub arrays have the same length, you can use Array#transpose :
[[1,2,3,4,5],[6,7,8,9,10]].transpose
#=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]
As a bonus, it works fine with more than 2 arrays :
[[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]].transpose
#=> [[1, 6, 11], [2, 7, 12], [3, 8, 13], [4, 9, 14], [5, 10, 15]]
If you're not sure your sub arrays have the same length :
[[1,2,3,4,5],[6,7,8,9], [10,11]].reduce(&:zip).map(&:flatten)
#=> [[1, 6, 10], [2, 7, 11], [3, 8, nil], [4, 9, nil], [5, nil, nil]]
Using transpose in this example would throw an IndexError.
Using parallel assignment:
a, b = [[1, 2, 3, 4, 5],[6, 7, 8, 9, 10]]
a.zip b #=> [[1, 6], [2, 7], [3, 8], [4, 9], [5, 10]]

How to get 'fair combination' from an array of n elements?

Using combination method on Ruby,
[1, 2, 3, 4, 5, 6].combination(2).to_a
#=> [[1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 3],
# [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6],
# [4, 5], [4, 6], [5, 6]]
we can get a 2-dimensional array having 15 (6C2) elements.
I would like to create a fair_combination method that returns an array like this:
arr = [[1, 2], [3, 5], [4, 6],
[3, 4], [5, 1], [6, 2],
[5, 6], [1, 3], [2, 4],
[2, 3], [4, 5], [6, 1],
[1, 4], [2, 5], [3, 6]]
So that every three sub-arrays (half of 6) contain all the given elements:
arr.each_slice(3).map { |a| a.flatten.sort }
#=> [[1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6],
# [1, 2, 3, 4, 5, 6]]
This makes it kind of "fair", by using as different elements as possible as arrays go on.
To make it more general, what it needs to satisfy is as follows:
(1) As you follow the arrays from start and count how many times each number appears, at any point it should be as flat as possible;
(1..7).to_a.fair_combination(3)
#=> [[1, 2, 3], [4, 5, 6], [7, 1, 4], [2, 5, 3], [6, 7, 2], ...]
The first 7 numbers make [1,2,...,7] and so do the following 7 numbers.
(2) Once number A comes in the same array with B, A does not want to be in the same array with B if possible.
(1..10).to_a.fair_combination(4)
#=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 1, 5], [2, 6, 9, 3], [4, 7, 10, 8], ...]
Is there any good algorithm that creates a "fair combination" like this ?
It's not guaranteed to give the best solution, but it gives a good enough one.
At each step, it chooses a minimal subpool which is the set of items of minimal height, for which there is still a combination to choose from (height is the number of times the items have been used before).
For instance, let the enumerator be
my_enum = FairPermuter.new('abcdef'.chars, 4).each
The first iteration may return
my_enum.next # => ['a', 'b', 'c', 'd']
At this point those letters have height 1, but there is not enough letters of height 0 to make a combination, so take just all of them for the next:
my_enum.next # => ['a', 'b', 'c', 'e'] for instance
Now the heights are 2 for a, b and c, 1 for d and e, and 0 for f, and still the optimal pool is the full initial set.
So this is not really optimized for combinations of large size. On the other side, if the size of the combination is at most half of the size of the initial set, then the algorithm is pretty decent.
class FairPermuter
def initialize(pool, size)
#pool = pool
#size = size
#all = Array(pool).combination(size)
#used = []
#counts = Hash.new(0)
#max_count = 0
end
def find_valid_combination
[*0..#max_count].each do |height|
candidates = #pool.select { |item| #counts[item] <= height }
next if candidates.size < #size
cand_comb = [*candidates.combination(#size)] - #used
comb = cand_comb.sample
return comb if comb
end
nil
end
def each
return enum_for(:each) unless block_given?
while combination = find_valid_combination
#used << combination
combination.each { |k| #counts[k] += 1 }
#max_count = #counts.values.max
yield combination
return if #used.size >= [*1..#pool.size].inject(1, :*)
end
end
end
Results for fair combinations of 4 over 6
[[1, 2, 4, 6], [3, 4, 5, 6], [1, 2, 3, 5],
[2, 4, 5, 6], [2, 3, 5, 6], [1, 3, 5, 6],
[1, 2, 3, 4], [1, 3, 4, 6], [1, 2, 4, 5],
[1, 2, 3, 6], [2, 3, 4, 6], [1, 2, 5, 6],
[1, 3, 4, 5], [1, 4, 5, 6], [2, 3, 4, 5]]
Results of fair combination of 2 over 6
[[4, 6], [1, 3], [2, 5],
[3, 5], [1, 4], [2, 6],
[4, 5], [3, 6], [1, 2],
[2, 3], [5, 6], [1, 6],
[3, 4], [1, 5], [2, 4]]
Results of fair combinations of 2 over 5
[[4, 5], [2, 3], [3, 5],
[1, 2], [1, 4], [1, 5],
[2, 4], [3, 4], [1, 3],
[2, 5]]
Time to get combinations of 5 over 12:
1.19 real 1.15 user 0.03 sys
Naïve implementation would be:
class Integer
# naïve factorial implementation; no checks
def !
(1..self).inject(:*)
end
end
class Range
# constant Proc instance for tests; not needed
C_N_R = -> (n, r) { n.! / ( r.! * (n - r).! ) }
def fair_combination(n)
to_a.permutation
.map { |a| a.each_slice(n).to_a }
.each_with_object([]) do |e, memo|
e.map!(&:sort)
memo << e if memo.all? { |me| (me & e).empty? }
end
end
end
▶ (1..6).fair_combination(2)
#⇒ [
# [[1, 2], [3, 4], [5, 6]],
# [[1, 3], [2, 5], [4, 6]],
# [[1, 4], [2, 6], [3, 5]],
# [[1, 5], [2, 4], [3, 6]],
# [[1, 6], [2, 3], [4, 5]]]
▶ (1..6).fair_combination(3)
#⇒ [
# [[1, 2, 3], [4, 5, 6]],
# [[1, 2, 4], [3, 5, 6]],
# [[1, 2, 5], [3, 4, 6]],
# [[1, 2, 6], [3, 4, 5]],
# [[1, 3, 4], [2, 5, 6]],
# [[1, 3, 5], [2, 4, 6]],
# [[1, 3, 6], [2, 4, 5]],
# [[1, 4, 5], [2, 3, 6]],
# [[1, 4, 6], [2, 3, 5]],
# [[1, 5, 6], [2, 3, 4]]]
▶ Range::C_N_R[6, 3]
#⇒ 20
Frankly, I do not understand how this function should behave for 10 and 4, but anyway this implementation is too memory consuming to work properly on big ranges (on my machine it gets stuck on ranges of size > 8.)
To adjust this to more robust solution one needs to get rid of permutation there in favor of “smart concatenate permuted arrays.”
Hope this is good for starters.

Ruby array access 2 consecutive(chained) elements at a time

Now, This is the array,
[1,2,3,4,5,6,7,8,9]
I want,
[1,2],[2,3],[3,4] upto [8,9]
When I do, each_slice(2) I get,
[[1,2],[3,4]..[8,9]]
Im currently doing this,
arr.each_with_index do |i,j|
p [i,arr[j+1]].compact #During your arr.size is a odd number, remove nil.
end
Is there a better way??
Ruby reads your mind. You want cons ecutive elements?
[1, 2, 3, 4, 5, 6, 7, 8, 9].each_cons(2).to_a
# => [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9]]
.each_cons does exactly what you want.
[1] pry(main)> a = [1,2,3,4,5,6,7,8,9]
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
[2] pry(main)> a.each_cons(2).to_a
=> [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9]]
You almost got it right :)
arr = [1,2,3,4,5,6,7,8,9]
arr.each_cons(2) do |chunk|
p chunk
end
# >> [1, 2]
# >> [2, 3]
# >> [3, 4]
# >> [4, 5]
# >> [5, 6]
# >> [6, 7]
# >> [7, 8]
# >> [8, 9]
And if you wanted to implement your own each_cons:
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
cons = 2
0.upto(arr.size - cons) do |i|
p arr[i, cons]
end
Output:
[1, 2]
[2, 3]
[3, 4]
[4, 5]
[5, 6]
[6, 7]
[7, 8]
[8, 9]

Extract 2D sub-array (without using Matrix)

In Ruby, given an array-of-arrays representing a 2D grid of numbers, how would you extract a specific sub-2D array?
a = [[0, 3, 1, 5, 5],
[4, 6, 8, 3, 5],
[7, 1, 4, 0, 8],
[0, 8, 8, 7, 4],
[7, 2, 4, 5, 4]]
require 'pp'
pp sub_array(a,1..4,2..4)
#=> [[8, 3, 5],
#=> [4, 0, 8],
#=> [8, 7, 4],
#=> [4, 5, 4]]
This is 'easy' to do using the Matrix library:
m = Matrix[*a]
p m.minor(1..4,2..4).to_a
#=> [[8, 3, 5], [4, 0, 8], [8, 7, 4], [4, 5, 4]]
However, I feel certain that there's an elegant way to do this without using the Matrix, perhaps involving zip or transpose :)
I'm including the words "two-dimensional" here for search hits.
def sub_array(xs, rows, columns)
xs[rows].map { |row| row[columns] }
end
sub_array(a, 1..4, 2..4)
#=> [[8, 3, 5], [4, 0, 8], [8, 7, 4], [4, 5, 4]]

Resources