Ruby: assignment to elements of an array - ruby

I'm a beginner and I want to create a matrix. For example:
0 1 1
1 1 1
1 1 2
irb(main):001:0> t = [[1]*3]*3
=> [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
irb(main):002:0> (0...3).each do |x| t[x][x]=x end
=> 0...3
irb(main):003:0> t
=> [[0, 1, 2], [0, 1, 2], [0, 1, 2]] # why all values changed?
What's wrong?

The way you construct the array does not cretae new arrays for each row but references the same array for all rows:
t.each do |row|
p row.object_id
end
# 70325094342320
# 70325094342320
# 70325094342320
It is the same as:
a = [1, 1, 1]
t = [a, a, a]
Try this to see the difference:
t = [[1] * 3, [1] * 3, [1] * 3]

Related

Duplicate elements of array in ruby

I find a lot of reference about removing duplicates in ruby but I cannot find how to create duplicate.
If I have an array like [1,2,3] how can I map it to an array with dubbed items? [1,1,2,2,3,3]
Is there a method?
Try this one
[1, 2, 3].flat_map { |i| [i, i] }
=> [1, 1, 2, 2, 3, 3]
Here's yet another way, creating the array directly with Array#new :
array = [1, 2, 3]
repetitions = 2
p Array.new(array.size * repetitions) { |i| array[i / repetitions] }
# [1, 1, 2, 2, 3, 3]
According to fruity, #ursus's answer, #ilya's first two answers and mine have comparable performance. transpose.flatten is slower than any of the others.
#Ursus answer is the most clean, there are possible solutions:
a = [1, 2, 3]
a.zip(a).flatten
#=> [1, 1, 2, 2, 3, 3]
Or
a.inject([]) {|a, e| a << e << e} # a.inject([]) {|a, e| n.times {a << e}; a}
=> [1, 1, 2, 2, 3, 3]
Or
[a, a].transpose.flatten # ([a] * n).transpose.flatten
=> [1, 1, 2, 2, 3, 3]
Try this:
[1, 2, 3] * 2
=> [1, 2, 3, 1, 2, 3]
You might want it sorted:
([1, 2, 3] * 2).sort
=> [1, 1, 2, 2, 3, 3]

Find combinations in Ruby that are less than a certain number

Say I have an array [1,2,3] and I want every combination of these numbers that don't exceed 4. So I would have [1,2,3].someMethod(4) and it would give me:
[1,1,1,1]
[1,1,2]
[1,3]
[2,2]
So far I have:
(1..4).flat_map{|size| [1,2,3].repeated_combination(size).to_a }
but this gives me every possible combinations, including the ones that exceed my given limit. Is there an good way to either only get combinations that add up to my limit?
arr = [1,2,3]
(arr+[0]).repeated_combination(4).select { |a| a.reduce(:+) == 4 }.map { |a| a - [0] }
#=> [[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
Change == to <= if desired.
This answer, like the others, assumes arr contains natural numbers, including 1.
results = (1..4).each.with_object([]) do |size, results|
[1,2,3].repeated_combination(size) do |combo|
results << combo if combo.reduce(:+) == 4
end
end
p results
--output:--
[[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
Parameterizing the algorithm:
def do_stuff(values, target_total)
(1..target_total).each.with_object([]) do |size, results|
values.repeated_combination(size) do |combo|
results << combo if combo.reduce(:+) == 4
end
end
end
p do_stuff([1, 2, 3], 4)
You can filter out the arrays you don't want by using the select method. Just select all the arrays that have a sum == 4 (the sum is calculated by the inject method).
all_arrs = (1..4).flat_map do |size|
[1,2,3].repeated_combination(size).to_a
end
valid_arrs = all_arrs.select do |arr|
arr.inject { |a, b| a + b } == 4
end
print valid_arrs
# Output:
# [[1, 3], [2, 2], [1, 1, 2], [1, 1, 1, 1]]
A recursive approach.
def some_method(a, n)
return [[]] if n == 0
a.select { |e| e <= n }.\
flat_map { |e| some_method(a,n-e).map { |es| ([e] + es).sort } }.\
sort.\
uniq
end
p some_method([1,2,3], 4)
# => [[1, 1, 1, 1], [1, 1, 2], [1, 3], [2, 2]]
EDIT: Here is another recursive version without filtering duplicates but with opposite order. I added comments to make it clearer.
def some_method(a, n)
return [[]] if n == 0 # bottom (solution) found
return [] if a.empty? || n < 0 # no solution
max = a.max
# search all solutions with biggest value
l = some_method(a, n-max).map { |e| [max] + e }
# search all solutions without biggest value
r = some_method(a-[max],n)
l + r
end
p some_method([1,2,3], 4)
# => [[3, 1], [2, 2], [2, 1, 1], [1, 1, 1, 1]]

Each loop not working as expected

I have an array called #results that is made up only of arrays. I want to iterate through #results and permanently delete any of the inner arrays that are smaller than a given size:
My code:
def check_results limit
#results.each_with_index do |result, index|
#results.delete_at(index) if result.size < limit
end
end
Unfortunately, this only deletes the first item where the array length is less than limit. For example if limit = 4 and #results = [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1], [1, 1]] then check_results returns [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1]]
I can't figure out why this is happening. Am I using the wrong loop?
You should do this, as delete_at modifies the array, and you will get unexpected behavior if you are deleting elements while iterating it
#results.reject { |i| i.size < limit }
Above code will exclude all array elements whose size is smaller than limit
It's not a good idea to modify the #results array in place as that will conflict with the outer iteration.
What you should do instead is use select to build a new array.
def check_results(limit)
#result.select { |result| result.size > limit }
end
As per the documentation, #delete_at returns the element at that index.
a = ["ant", "bat", "cat", "dog"]
a.delete_at(2) #=> "cat"
a #=> ["ant", "bat", "dog"]
a.delete_at(99) #=> nil
I added some debug statements to show you what is happening at each step, assuming limit is 4:
#results = [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1], [1, 1]]
#results.each_with_index do |r, i|
puts "RESULT: #{r.to_s}"
puts "INDEX: #{i}"
#results.delete_at(i) if r.size < 4
puts "ARRAY: #{#results.to_s}"
end
RESULT: [1, 1, 1, 1]
INDEX: 0
ARRAY: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1], [1, 1]]
RESULT: [1, 1, 1, 1]
INDEX: 1
ARRAY: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1], [1, 1]]
RESULT: [1, 1, 1]
INDEX: 2
ARRAY: [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1]]
# #results == [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1]]
As you can see, the element originally at index 2 has been removed. Because you are modifying #results while you are iterating through it, an index of 3 no longer exists, and an index of 2 has already been analyzed. This is why you should not modify an object while iterating through it.
Ideally, you want to use #delete_if. Similar to methods ending in !, #delete_if will modify the array (not return a copy of the result), based on conditions from a block (as an argument). The following would be how you would implement the method:
def check_results(limit)
#results.delete_if { |arr| arr.length < limit }
end
#results = [ ['foo', 'bar'], ['bizz', 'bazz'], ['kaboom'] ]
check_results(2)
# => #results == [ ['foo', 'bar'], ['bizz', 'bazz'] ]
If you do not want to modify #results, then I suggest a similar method, #reject. Again, #results will not be modified, and instead a copy of the results will be returned.
def check_results(limit)
#results.reject { |arr| arr.length < limit }
end
#results = [ ['foo', 'bar'], ['bizz', 'bazz'], ['kaboom'] ]
check_results(2)
# => [ ['foo', 'bar'], ['bizz', 'bazz'] ]
# => #results == [ ['foo', 'bar'], ['bizz', 'bazz'], ['kaboom'] ]

Swapping two numbers with while loop in Ruby

I'd like to get [[2, 1, 3], [1, 3, 2]] from [1, 2, 3] in Ruby.
For [1, 2, 3, 4], I'd like to get [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
Rule: Within two numbers, if left one is smaller then it swap the position.
I have the following codes so far but it returns [[2, 3, 1], [2, 3, 1]]
What am I doing wrong here? I appreciate any inputs.
In amidakuji.rb
class Amidakuji
def initialize(column, rung)
#column = column
#rung = rung
#myarr = []
#per_arr = []
#build_arr = []
end
def build_initial
#arr = (1..#column).to_a
end
def swap_element
i = 0
arr = build_initial
while i < #column - 1 do
#build_arr << swap(arr, i)
i += 1
end
#build_arr
end
def swap(arr, a)
if arr[a] < arr[a + 1]
arr[a], arr[a + 1] = arr[a + 1], arr[a]
end
arr
end
end
In amidakuji_spec.rb
it 'should create an array with swapped elements' do
expect(#kuji1.swap_element).to eq ([[2, 1, 3], [1, 3, 2]])
end
Results
Failures:
expected: [[2, 1, 3], [1, 3, 2]]
got: [[2, 3, 1], [2, 3, 1]]
You can do this quite compactly by using the methods Enumerable#each_cons and Enumerable#map.
Code
def doit(arr)
(0...arr.size).each_cons(2).map do |i,j|
a = arr.dup
a[i], a[j] = a[j], a[i]
a
end
end
Examples
doit([1,2,3]) #=> [[2, 1, 3], [1, 3, 2]]
doit([1,2,3,4]) #=> [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
doit([1,2,3,4,5]) #=> [[2, 1, 3, 4, 5], [1, 3, 2, 4, 5],
#=> [1, 2, 4, 3, 5], [1, 2, 3, 5, 4]]
Explanation
arr = [1,2,3,4]
b = (0...arr.size).each_cons(2)
#=> #<Enumerator: 0...4:each_cons(2)>
To view the contents of this enumerator:
b.to_a
#=> [[0, 1], [1, 2], [2, 3]]
Lastly
b.map do |i,j|
a = arr.dup
a[i], a[j] = a[j], a[i]
a
end
#=> [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
In the last step, consider the first element of b that is passed to map, which assigns the following values to the block variables:
i => 0
j => 1
We then make a copy of arr, swap the elements offsets 0 and 1, making
a => [2, 1, 3, 4]
and then enter a at the end of the block, causing map to replace [0, 1] with that array.
Given what you're trying to accomplish and the output you're getting, it looks like you're reusing the same array when you want distinct arrays instead. Specifically this line:
#build_arr << swap(arr, i)
is always passing the same 'arr' to swap.
So first time, it swaps the 1 and the 2 to give you [2, 1, 3]
Second time, it swaps the 1 and the 3 give you [2, 3, 1]
You push the same array onto #build_arr twice, which is why it repeats.

Ruby: Permutation on an Array

Say, I have an array:
a = [1,2]
and
n = 3
I want output like this:
[[1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 2, 2], [2, 1, 1], [2, 1, 2], [2, 2, 1], [2, 2, 2]]
This are all possible combinations of length n of elements from array a.
Most importantly I'm using ruby 1.8.7
a.repeated_combination(n).to_a
Please test in detail before use:
x = [1,0]
n = 3
def perm(a, n)
l = a.length
(l**n).times do |i|
entry = []
o = i
n.times do
v = o % l
entry << a[v]
o /= l
end
yield(i, entry)
end
end
perm(x, n) do |i, entry|
puts "#{i} #{entry.reverse.inspect}"
end
prints
0 [0, 0, 0]
1 [0, 0, 1]
2 [0, 1, 0]
3 [0, 1, 1]
4 [1, 0, 0]
5 [1, 0, 1]
6 [1, 1, 0]
7 [1, 1, 1]

Resources