Ruby multidimensional array assignment disparities - ruby

Can someone please explain the difference in output between these two cases:
array = [['a', 'a,'], ['a', 'a']]
# => [['a', 'a,'], ['a', 'a']]
array[0][0] = 'b'
# => 'b'
array
# => [['b', 'a'], ['a', 'a']]
and
array = [['a'] * 2] * 2
# => [['a', 'a,'], ['a', 'a']]
array[0][0] = 'b'
# => 'b'
array
# => [['b', 'a'], ['b', 'a']]
It seems when using the form [['a'] * 2] * 2 to create the multi-dimensional array, changes to the first 'row' are duplicated in every row?
Is there an alternative short-form way to define a multidimensional array that doesn't have this behaviour?

The issue with your second technique is that you are creating a single array ['a', 'a'] and putting it in both positions in the array array. You need to clone the items in array so they aren't literally the same item.
array = [['a'] * 2] * 2
# => [["a", "a"], ["a", "a"]]
array.map! { |item| item.clone }
# => [["a", "a"], ["a", "a"]]
array[0][0] = 'b'
# => "b"
array
# => [["b", "a"], ["a", "a"]]

When you write [['a'] * 2] * 2 you are creating a single Array Object ['a', 'a'] with two references to it in the outer Array. To accomplish what you want you'd have to write.
array = []
2.times do
array << ['a']*2
end
This way you're creating a new inner array at each iteration and they should be independent from one another.

Related

How do I move an element of an array one place up/down with Ruby

Let's say I have this array
array = ['a', 'b', 'c', 'd']
What is a good way to target an element (for example 'b') and switch it with the next element in line (in this case 'c') so the outcome becomes:
=> ['a', 'c', 'b', 'd']
array[1], array[2] = array[2], array[1]
array #=> ["a", "c", "b", "d"]
or
array[1, 2] = array.values_at(2, 1)
array #=> ["a", "c", "b", "d"]
There is no build in function to do this. You can swap the values like so:
array = %w[a b c d]
array[1..2] = array[1..2].reverse
array #=> ["a", "c", "b", "d"]
You could add some helper methods to the core array class.
class Array
def move_up(index)
self[index, 2] = self[index, 2].reverse
self
end
def move_down(index)
move_up(index - 1)
end
end
Note: Keep in mind that this solution mutates the original array. You could also opt for a version that creates a new array. For this version you can call #dup (result = dup) than work with result instead of self.
References:
Array#[]
Array#[]=
Array#reverse
Object#dup
Try this for swapping
array[0],array[1] = array[1],array[0]
or in general
array[i],array[i+1] = array[i+1],array[i]
Assuming that you want to target the elements by their indices, a combination of insert and delete_at would work:
array = %w[a b c d]
array.insert(2, array.delete_at(1))
array
#=> ["a", "c", "b", "d"]

processing array with duplicates

I have an array
a = ['A', 'B', 'B', 'C', 'D', 'D']
and I have to go thru all the elements, do something depending on whether the is the last occurance or not, and remove the element after processing it.
The elements are already sorted if that matters.
I'm looking for something efficient. Any suggestions?
Her what I have until now. THIS WORKS AS EXPECTED but not sure it is very efficient.
a = ['A', 'B', 'B', 'C', 'D', 'D']
while !a.empty?
b = a.shift
unless a.count(b) > 0
p "unique #{b}"
else
p "duplicate #{b}"
end
end
and it produces
"unique A"
"duplicate B"
"unique B"
"unique C"
"duplicate D"
"unique D"
Thanks
Simple way:
array = ["A", "B", "B", "C", "D", "D"]
array.group_by{|e| e}.each do |key,value|
*duplicate, uniq = value
duplicate.map do |e|
puts "Duplicate #{e}"
end
puts "Unique #{uniq}"
end
As per Stefan's comment and suggestion, shorter way is:
array.chunk_while(&:==).each do |*duplicate, uniq|
duplicate.map do |e|
puts "Duplicate #{e}"
end
puts "Unique #{uniq}"
end
# Above both will give the same Output:
---------------------------------------
Unique A
Duplicate B
Unique B
Unique C
Duplicate D
Unique D
Based on your code and expected output, I think this is an efficient way to do what you're looking for:
a = ['A', 'B', 'B', 'C', 'D', 'D']
a.each_index do |i|
if i < a.length - 1 && a[i+1] == a[i]
puts "This is not the last occurrence of #{a[i]}"
else
puts "This is the last occurrence of #{a[i]}"
end
end
# Output:
# This is the last occurrence of A
# This is not the last occurrence of B
# This is the last occurrence of B
# This is the last occurrence of C
# This is not the last occurrence of D
# This is the last occurrence of D
But I want to reiterate the importance of the wording in my output versus yours. This is not about whether the value is unique or not in the input. It seems to be about whether the value is the last occurrence within the input or not.
Quite similar to the answer of #GaganGami but using chunk_while.
a.chunk_while { |a,b| a == b }
.each do |*list,last|
list.each { |e| puts "duplicate #{e}" }
puts "unique #{last}"
end
chunk_whilesplits the array into sub arrays when the element changes.
['A', 'B', 'B', 'C', 'D', 'D'].chunk_while { |a,b| a == b }.to_a
# => [["A"], ["B", "B"], ["C"], ["D", "D"]]
The OP stated that the elements of a are sorted, but that is not required by the method I propose. It also maintains array-order, which could be important for the "do something" code performed for each element to be removed. It does so with no performance penalty over the case where the array is already sorted.
For the array
['A', 'B', 'D', 'C', 'B', 'D']
I assume that some code is to be executed for 'A', 'C' the second 'B' and the second 'D', in that order, after which a new array
['B', 'D']
is returned.
Code
def do_something(e) end
def process_last_dup(a)
a.dup.
tap do |b|
b.each_with_index.
reverse_each.
uniq(&:first).
reverse_each { |_,i| do_something(a[i]) }.
each { |_,i| b.delete_at(i) }
end
end
Example
a = ['A', 'B', 'B', 'C', 'D', 'D']
process_last_dup(a)
#=> ["B", "D"]
Explanation
The steps are as follows.
b = a.dup
#=> ["A", "B", "B", "C", "D", "D"]
c = b.each_with_index
#=> #<Enumerator: ["A", "B", "B", "C", "D", "D"]:each_with_index>
d = c.reverse_each
#=> #<Enumerator: #<Enumerator: ["A",..., "D"]:each_with_index>:reverse_each>
Notice that d can be thought of as a "compound" enumerator. We can convert it to an array to see the elements it will generate and pass to uniq.
d.to_a
#=> [["D", 5], ["D", 4], ["C", 3], ["B", 2], ["B", 1], ["A", 0]]
Continuing,
e = d.uniq(&:first)
#=> [["D", 5], ["C", 3], ["B", 2], ["A", 0]]
e.reverse_each { |_,i| do_something(a[i]) }
reverse_each is used so that do_something is first executed for 'A', then for the second 'B', and so on.
e.each { |_,i| b.delete_at(i) }
b #=> ["B", "D"]
If a is to be modified in place replace a.dup. with a..
Readers may have noticed that the code I gave at the beginning used Object#tap so that tap's block variable b, which initially equals a.dup, will be returned after it has been modified within tap's block, rather than explicitly setting b = a.sup at the beginning and b at the end, as I've done in my step-by-step explanation. Both approaches yield the same result, of course.
The doc for Enumerable#uniq does not specify whether the first element is kept, but it does reference Array.uniq, which does keep the first. If there is any uneasiness about that one could always replace reverse_each with reverse so that Array.uniq would be used.

How do I do element-wise comparison of two arrays?

I have two arrays:
a = [1,2,3]
b = [1,4,3]
Is there an element-wise comparison method in Ruby such that I could do something like this:
a == b
returns:
[1,0,1] or something like [TRUE,FALSE,TRUE].
Here's one way that I can think of.
a = [1, 2, 3]
b = [1, 4, 3]
a.zip(b).map { |x, y| x == y } # [true, false, true]
You can also use .collect
a.zip(b).collect {|x,y| x==y }
=> [true, false, true]
a = [1,2,3]
b = [1,4,3]
a.zip(b).map { |pair| pair[0] <=> pair[1] }
=> [0, -1, 0]
The element-wise comparison is achieved with the zip Ruby Array object method.
a = [1,2,3]
b = [1,4,3]
a.zip(b)
=> [[1, 1], [2, 4], [3, 3]]
You can do something like this to get exactly what you want:
[1,2,3].zip([1,4,3]).map { |a,b| a == b }
=> [true, false, true]
This should do the trick:
array1.zip(array2).map { |a, b| a == b }
zip creates one array of pairs consisting of each element from both arrays at that position. Imagine gluing the two arrays side by side.
Try something like this :
#array1 = ['a', 'b', 'c', 'd', 'e']
#array2 = ['d', 'e', 'f', 'g', 'h']
#intersection = #array1 & #array2
#intersection should now be ['d', 'e'].
Intersection—Returns a new array containing elements common to the two arrays, with no duplicates.
You can even try some of the ruby tricks like the following :
array1 = ["x", "y", "z"]
array2 = ["w", "x", "y"]
array1 | array2 # Combine Arrays & Remove Duplicates(Union)
=> ["x", "y", "z", "w"]
array1 & array2 # Get Common Elements between Two Arrays(Intersection)
=> ["x", "y"]
array1 - array2 # Remove Any Elements from Array 1 that are
# contained in Array 2 (Difference)
=> ["z"]

Swapping array elements using parallel assignment

Intrigued by this question, I have played a bit with parallel assignment with arrays and method calls. So here's an paradigmatic example, trying to swap two members in an array, by their value:
deck = ['A', 'B', 'C']
#=> ["A", "B", "C"]
deck[deck.index("A")], deck[deck.index("B")] = deck[deck.index("B")], deck[deck.index("A")]
#=> ["B", "A"]
deck
#=> ["A", "B", "C"]
The array hasn't changed. But if we change the order of arguments, it works:
deck[deck.index("B")], deck[deck.index("A")] = deck[deck.index("A")], deck[deck.index("B")]
#=> ["A", "B"]
deck
#=> ["B", "A", "C"]
I guess it has to do with the order of calling the index methods within the assignment, but not see it clearly. Can someone please explain the order of things underneath, and why the first example doesn't swap the member, and second does?
It is expected. It follows from how ruby evaluates expressions.
deck[deck.index("A")], deck[deck.index("B")] = deck[deck.index("B")], deck[deck.index("A")]
Implies
deck[deck.index("A")], deck[deck.index("B")] = 'B', 'A'
Note: strings 'A' and 'B' here are for illustration only. Ruby doesn't create new string objects here. Which essentially is:
deck[deck.index("A")] = 'B' -> deck[0] = 'B' (deck = ['B', 'B', 'C'])
deck[deck.index("B")] = 'A' -> deck[0] = 'A' (deck = ['A', 'B', 'C'])
Array#index returns when it finds the first match.
Now,
deck[deck.index("B")], deck[deck.index("A")] = deck[deck.index("A")], deck[deck.index("B")]
-> deck[deck.index("B")], deck[deck.index("A")] = 'A', 'B'
-> deck[deck.index("B")] = 'A' -> deck[1] = 'A' (deck = ['A', 'A', 'C'])
-> deck[deck.index("A")] = 'B' -> deck[0] = 'B' (deck = ['B', 'A', 'C'])
Just as an example, compare the machinations used to search the array, find the correct indexes then swap the values, with what you could do using a Hash:
h = { "cat" => "feline", "dog" => "canine", "cow" => "bovine" }
h['dog'], h['cat'] = h.values_at('cat', 'dog')
h #=> {"cat"=>"canine", "dog"=>"feline", "cow"=>"bovine"}
Now, if Ruby had an assignable values_at= Hash method it could be even cleaner:
h.values_at('dog', 'cat') = h.values_at('cat', 'dog')
but, alas, we don't. Hash slicing is a very powerful tool in Perl and something I miss about Ruby.
And, yes, I know I can add my own assignable values_at=.
M Rajesh is correct, but he actually had to think in order to work it out. I'm too lazy for that!
Here's a printf-debugging way of showing what happened.
deck = ['A', 'B', 'C']
#=> ["A", "B", "C"]
deck[deck.index("A").tap {|index|
STDERR.puts "Result of indexing for #{"A".inspect} is #{index.inspect}"
}],
deck[deck.index("B").tap {|index|
STDERR.puts "Result of indexing for #{"B".inspect} is #{index.inspect}"
}] =
deck[deck.index("B")], deck[deck.index("A")]
# Result of indexing for "A" is 0
# Result of indexing for "B" is 0
#=> ["B", "A"]
deck
#=> ["A", "B", "C"]

Converting an array of keys and an array of values into a hash in Ruby

I have two arrays like this:
keys = ['a', 'b', 'c']
values = [1, 2, 3]
Is there a simple way in Ruby to convert those arrays into the following hash?
{ 'a' => 1, 'b' => 2, 'c' => 3 }
Here is my way of doing it, but I feel like there should be a built-in method to easily do this.
def arrays2hash(keys, values)
hash = {}
0.upto(keys.length - 1) do |i|
hash[keys[i]] = values[i]
end
hash
end
The following works in 1.8.7:
keys = ["a", "b", "c"]
values = [1, 2, 3]
zipped = keys.zip(values)
=> [["a", 1], ["b", 2], ["c", 3]]
Hash[zipped]
=> {"a"=>1, "b"=>2, "c"=>3}
This appears not to work in older versions of Ruby (1.8.6). The following should be backwards compatible:
Hash[*keys.zip(values).flatten]
Another way is to use each_with_index:
hash = {}
keys.each_with_index { |key, index| hash[key] = values[index] }
hash # => {"a"=>1, "b"=>2, "c"=>3}
The same can be done using Array#transpose method. If you are using Ruby version >= 2.1, you can take the advantage of the method Array#to_h, otherwise use your old friend, Hash::[]
keys = ['a', 'b', 'c']
values = [1, 2, 3]
[keys, values].transpose.to_h
# => {"a"=>1, "b"=>2, "c"=>3}
Hash[[keys, values].transpose]
# => {"a"=>1, "b"=>2, "c"=>3}
Try this, this way the latter one d will overwrite the former one c
irb(main):001:0> hash = Hash[[[1,2,3,3], ['a','b','c','d']].transpose]
=> {1=>"a", 2=>"b", 3=>"d"}
irb(main):002:0>

Resources