How do I swap array elements using parallel assignment? - ruby

I am trying to swap two elements in an array like this
deck = []
(deck << (1..52).to_a << 'A' << 'B').flatten!
p deck
deck[deck.index("A")], deck[deck.index("B")] = deck[deck.index("B")], deck[deck.index("A")] #swap "A" and "B"
p deck
But it doesnt swap. If i do this, however:
deck[52], deck[53] = deck[53], deck[52]
it works. Any suggestions?

To make things simple, let deck be just ['A', 'B']. Here is step-by-step evaluation:
deck = ['A', 'B']
deck[deck.index("A")], deck[deck.index("B")] = deck[deck.index("B")], deck[deck.index("A")] # deck == ['A', 'B']
deck[deck.index("A")], deck[deck.index("B")] = deck[1], deck[0] # deck == ['A', 'B']
deck[deck.index("A")], deck[deck.index("B")] = 'B', 'A' # deck == ['A', 'B']
deck[0], deck[deck.index("B")] = 'B', 'A' # deck == ['A', 'B']
# Applying first assignment.
..., deck[deck.index("B")] = ..., 'A' # deck == ['B', 'B']
# NOTE: deck.index("B") is 0 now, not 1!
..., deck[0] = ..., 'A' # deck == ['B', 'B']
# Applying second assignment.
... # deck == ['A', 'B']
So what your code actually does is just assinging twise to the same element of array.
In order to fix this issue, just save deck.index() values to temporary arrays:deck = []
(deck << (1..52).to_a << 'A' << 'B').flatten!
p deck
index_a, index_b = deck.index("A"), deck.index("B")
deck[index_a], deck[index_b] = deck[index_b], deck[index_a]
p deck

Related

Ruby: Adding different array values into hash for the same key

I'm trying to add different values into an array for the same key into the hash. Instead of making a new instance in the array, my function sum ups the index values of the array elements
def dupe_indices(array)
hash = Hash.new([])
array.each.with_index { |ele, idx| hash[ele] = (idx) }
hash
end
I'm getting this
print dupe_indices(['a', 'b', 'c', 'a', 'c']) => {"a"=>3, "b"=>1,
"c"=>4}
Expected output
print dupe_indices(['a', 'b', 'c', 'a', 'c']) => { 'a' => [0, 3], 'b'
=> [1], 'c' => [2, 4] }
With two small modifications your code willl work.
change hash = Hash.new([]) to hash = Hash.new { |h,k| h[k] = [] }
you should really never use Hash.new([]), see this article for an explanation: https://mensfeld.pl/2016/09/ruby-hash-default-value-be-cautious-when-you-use-it/
Change hash[ele] = (idx) to hash[ele].push(idx)
you don't want to replace the value whenever you encounter a new index, you want to push it to the array.
array = ['a', 'b', 'c', 'a', 'c']
def dupe_indices(array)
hash = Hash.new { |h,k| h[k] = [] }
array.each.with_index { |ele, idx| hash[ele].push(idx) }
hash
end
dupe_indices(array)
# => {"a"=>[0, 3], "b"=>[1], "c"=>[2, 4]}

Compare 2 Hash with values consisting of array

I have 2 hashes, let's say A, B
A: { 'key1' => [a, b], 'key2' => 'c' }
B: { 'key1' => [b, a], 'key2' => 'c' }
What is the best possible way to compare these 2 hashes. The ordering of the array contents does not matter. So in my case, hash A and B are equal
It's not as easy as it seems at first glance.
It is necessary to take into account several nuances:
the number of elements in the hashes may not match;
items with the same key in two hashes can be of different types.
A relatively universal solution can be as follows:
def hashes_comp(hash1, hash2)
return false if hash1.size != hash2.size
hash1.each do |key, value|
if value.class == Array
return false if hash2[key].class != Array || value.sort != hash2[key].sort
else
return false if value != hash2[key]
end
end
true
end
hash_a = {'key1' => ['a', 'b'], 'key2' => 'c'}
hash_b = {'key1' => ['b', 'a'], 'key2' => 'c'}
hash_c = {'key1' => ['a', 'c'], 'key2' => 'c'}
hash_d = {'key1' => ['a', 'b'], 'key2' => 'd'}
hash_e = {'key1' => ['a', 'b'], 'key2' => ['a', 'b']}
hash_f = {'key1' => ['a', 'b'], 'key2' => 'c', 'key3' => 'd'}
hashes_comp(hash_a, hash_b) #=> true
hashes_comp(hash_a, hash_c) #=> false
hashes_comp(hash_a, hash_d) #=> false
hashes_comp(hash_a, hash_e) #=> false
hashes_comp(hash_a, hash_f) #=> false
One can sort the arrays but that can be an expensive operation if the arrays are large. If n equals the size of the array, the time complexity of heapsort, for example, is O(n log(n)). It's faster to replace arrays with counting hashes, the construction of which enjoys a time complexity of O(n).
h1 = { 'k1' => [1, 2, 1, 3, 2, 1], 'k2' => 'c' }
h2 = { 'k1' => [3, 2, 1, 2, 1, 1], 'k2' => 'c' }
def same?(h1, h2)
return false unless h1.size == h2.size
h1.all? do |k,v|
if h2.key?(k)
vo = h2[k]
if v.is_a?(Array)
if vo.is_a?(Array)
convert(v) == convert(vo)
end
else
v == vo
end
end
end
end
def convert(arr)
arr.each_with_object(Hash.new(0)) { |e,g| g[e] += 1 }
end
same?(h1, h2)
#=> true
Here
convert([1, 2, 1, 3, 2, 1])
#=> {1=>3, 2=>2, 3=>1}
convert([3, 2, 1, 2, 1, 1])
#=> {3=>1, 2=>2, 1=>3}
and
{1=>3, 2=>2, 3=>1} == {3=>1, 2=>2, 1=>3}
#=> true
See Hash::new, specifically the case where the method takes an argument that equals the default value.
The guard clause return false unless h1.size == h2.size is to ensure that h2 does not have keys that are not present in h1. Note that the following returns the falsy value nil:
if false
#...
end
#=> nil
In a couple of places I've written that rather than the more verbose but equivalent expresion
if false
#...
else
nil
end
I would definitely agree with Ivan it's not as easy as it initially seems but I figured I would try doing it with recursion. This has the added benefit of being able to compare beyond just hashes.
hash_a = {'key1' => ['a', 'b'], 'key2' => 'c'}
hash_b = {'key1' => ['b', 'a'], 'key2' => 'c'}
hash_c = {'key1' => ['a', 'c'], 'key2' => 'c'}
hash_d = {'key1' => ['a', 'b'], 'key2' => 'd'}
hash_e = {'key1' => ['a', 'b'], 'key2' => ['a', 'b']}
hash_f = {'key1' => ['a', 'b'], 'key2' => 'c', 'key3' => 'd'}
def recursive_compare(one, two)
unless one.class == two.class
return false
end
match = false
# If it's not an Array or Hash...
unless one.class == Hash || one.class == Array
return one == two
end
# If they're both Hashes...
if one.class == Hash
one.each_key do |k|
match = two.key? k
break unless match
end
two.each_key do |k|
match = one.key? k
break unless match
end
if match
one.each do |k, v|
match = recursive_compare(v, two[k])
break unless match
end
end
end
# If they're both Arrays...
if one.class == Array
one.each do |v|
match = two.include? v
break unless match
end
two.each do |v|
match = one.include? v
break unless match
end
end
match
end
puts recursive_compare(hash_a, hash_b) #=> true
puts recursive_compare(hash_a, hash_c) #=> false
puts recursive_compare(hash_a, hash_d) #=> false
puts recursive_compare(hash_a, hash_e) #=> false
puts recursive_compare(hash_a, hash_f) #=> false
I came up with this solution:
def are_equals?(a, b)
(a.keys.sort == b.keys.sort) &&
a.merge(b) { |k, o_val, n_val| [o_val, n_val].all? { |e| e.kind_of? Array} ? o_val.sort == n_val.sort : o_val == n_val }.values.all?
end
How it works.
The first part tests for key equality, using Hash#keys, which returns the array of keys, sorted of course:
a.keys.sort == b.keys.sort
For the second part I used Hash#merge to compare values related to the same key, and can be expanded in this way:
res = a.merge(b) do |k, o_val, n_val|
if [o_val, n_val].all? { |e| e.kind_of? Array}
o_val.sort == n_val.sort
else
o_val == n_val
end
end
#=> {"key1"=>true, "key2"=>true}
It returns a Hash where values are true or false, then checks if all values are true using Enumerable#all?:
res.values.all?
#=> [true, true].all? => true

Ruby, reorder an array based on another array? [duplicate]

This question already has answers here:
How do I quickly reorder a Ruby Array given an order?
(3 answers)
Closed 8 years ago.
I'd like to order ary according to the indices specified in order.
# Ruby
ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]
# Result I want
ary = ['c', 'd', 'a', 'b']
ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]
ary.values_at(*order)
#=> ["c", "d", "a", "b"]
You could do something like this:
ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]
sorted_array = []
order.each do |i|
sorted_array.push(ary[i])
end
Though looking at it, Cary's answer is nicer.
ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]
new_array = []
order.each do |index| #This returns the array you want.
new_array << ary[index]
end

Merge Ruby Arrays into Multidimensional Array

What is the best way to merge the following two arrays into a multidimensional array?
x = ['A', 'B', 'C']
y = ['D', 'E', 'F']
Desired result:
z = [['A', 'D'], ['A', 'E'], ['A', 'F'], ['B', 'D'], ['B', 'E'], ['B', 'F'], ['C', 'D'], ['C', 'E'], ['C', 'F']]
You can use Array#product:
x = ['A', 'B', 'C']
y = ['D', 'E', 'F']
result = x.product(y)
puts result.inspect
Here's one way, although not necessarily the simplest possible way:
x = ['A', 'B', 'C']
y = ['D', 'E', 'F']
result = []
x.each do |x|
y.each do |y|
result << [x, y]
end
end
puts result.inspect
Update: here's a more concise way:
x = ['A', 'B', 'C']
y = ['D', 'E', 'F']
puts x.map { |x|
y.map { |y| [x, y] }
}.inspect
Another way of doing it is like this:
x = ['A', 'B', 'C']
y = ['D', 'E', 'F']
print x.concat(y).each_slice(2).to_a # => [["A", "B"], ["C", "D"], ["E", "F"]]

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"]

Resources