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]}
Related
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
Having two arrays of different sizes, I'd like to get the longer array as keys and the shorter one as values. However, I don't want any keys to remain empty, so that is why I need to keep iterating on the shorter array until all keys have a value.
EDIT: I want to keep array longer intact, but without empty values, that means keep iterating on shorter until all keys have a value.
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
Hash[longer.zip(shorter)]
#=> {1=>"a", 2=>"b", 3=>"c", 4=>nil, 5=>nil, 6=>nil, 7=>nil}
Expected Result
#=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
Here's an elegant one. You can "loop" the short array
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
longer.zip(shorter.cycle).to_h # => {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
A crude way until you find something more elegant:
Slice the longer array as per length of shorter one, and iterate over it to re-map the values.
mapped = longer.each_slice(shorter.length).to_a.map do |slice|
Hash[slice.zip(shorter)]
end
=> [{1=>"a", 2=>"b", 3=>"c"}, {4=>"a", 5=>"b", 6=>"c"}, {7=>"a"}]
Merge all hashes withing the mapped array into a single hash
final = mapped.reduce Hash.new, :merge
=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
Here's a fun answer.
longer = [1, 2, 3, 4, 5, 6, 7]
shorter = ['a', 'b', 'c']
h = Hash.new do |h,k|
idx = longer.index(k)
idx ? shorter[idx % shorter.size] : nil
end
#=> {}
h[1] #=> a
h[2] #=> b
h[3] #=> c
h[4] #=> a
h[5] #=> b
h[6] #=> c
h[7] #=> a
h[8] #=> nil
h #=> {}
h.values_at(3,5) #=> ["c", "b"]
If this is not good enough (e.g., if you wish to use Hash methods such as keys, key?, merge, to_a and so on), you could create the associated hash quite easily:
longer.each { |n| h[n] = h[n] }
h #=> {1=>"a", 2=>"b", 3=>"c", 4=>"a", 5=>"b", 6=>"c", 7=>"a"}
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.
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"]
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>