Use a list/set/array of values as a hash key - ruby

I'm trying to implement a hypergraph. I'd like to use a (frozen) set as the key to a hash. I'd like to do the following or something like it.
set_a = Set.new(["a","b","c"])
set_b = Set.new(["a","b","d"])
set_c = Set.new(["a","b"])
set_d = Set.new(["a","b","e","f"])
set_a.freeze
set_b.freeze
set_c.freeze
set_d.freeze
data = {
set_a => [list of vertices],
set_b.freeze => [list of vertices],
set_c.freeze => [list of vertices],
set_d.freeze => [list of vertices]
}
However, it's not quite working. Doing:
data[some_set.freeze]
seems to cause errors.

And as long as you don't expose or share the data array there is not reason to freeze it.
You could just use instances of Array as keys. But you will have to ensure that the arrays are all sorted in the same way:
data = {
['a', 'b', 'c'] => [1, 2, 3],
['a', 'b', 'd'] => [1, 2, 4],
['a', 'b'] => [1, 2],
['a', 'b', 'e', 'f'] => [1, 2, 5, 6]
}
data[['a', 'b']]
#=> [1, 2]
Or as sawa mentioned in the comments: It might make sense to use Set to avoid ordering the arrays before using with the data hash. With Set your implementation might look like:
require 'set'
data = {
Set.new(['a', 'b', 'c']) => [1, 2, 3],
Set.new(['a', 'b', 'd']) => [1, 2, 4],
Set.new(['a', 'b']) => [1, 2],
Set.new(['a', 'b', 'e', 'f']) => [1, 2, 5, 6]
}
data[Set.new(['b', 'a'])] # Note that the order doesn't match
#=> [1, 2]

Related

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 two hashes with alternation in Ruby (2.x.x)

I have two hashes:
h1 = {'a' => 33, 'b' => 4, 'c' => 6}
h2 = {'d' => 10, 'e' => 1, 'f' => 12}
Now they should be merged into one, with alternation, so the final hash should be like:
{'a' => 33, 'd' => 10, 'b' => 4, 'e' = 1, 'c' => 6, 'f' => 12}
What's the best way to do that. Probably a single liner?
Thanks!
Here is my try
Hash[*[h1.to_a, h2.to_a].transpose.flatten]
# => {"a"=>33, "d"=>10, "b"=>4, "e"=>1, "c"=>6, "f"=>12}
# or
Hash[*h1.to_a.zip(h2.to_a).flatten]
# => {"a"=>33, "d"=>10, "b"=>4, "e"=>1, "c"=>6, "f"=>12}
Even with more STARS. :-)
Hash[*[*h1, *h2].transpose.flatten]
# => {"a"=>"b", "c"=>"d", "e"=>"f", 33=>4, 6=>10, 1=>12}
No stars!
h1 = {'a' => 33, 'b' => 4, 'c' => 6}
h2 = {'d' => 10, 'e' => 1, 'f' => 12}
h1.to_a.zip(h2.to_a).flatten(1).to_h
#=> {"a"=>33, "d"=>10, "b"=>4, "e"=>1, "c"=>6, "f"=>12}
For Ruby versions < 2.0:
Hash[h1.to_a.zip(h2.to_a).flatten(1)]
Note Array#transpose and Enumerable#zip are always interchangeable when manipulating arrays.
For Ruby versions < 1.9, the ordering of key/value pairs in hashes was not specified. For those versions, the closest you could come to answering your question would be to provide a desired ordering of keys:
keys_in_order = ['a', 'd', 'b', 'e', 'c', 'f']
retrieve the associated values:
values = h1.merge(h2).values_at(*keys_in_order)
#=> [33, 10, 4, 1, 6, 12]
and pair these with zip:
keys_in_order.zip(values)
#=> [["a", 33], ["d", 10], ["b", 4], ["e", 1], ["c", 6], ["f", 12]]

Joining multiple ordered arrays in Ruby

Lets say I have this:
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
And I want this:
d = [[1, 'a', 'ABC'], [2, 'b', 'DEF'], ...]
How can I accomplish this in Ruby?
I tried with .zip
r = []
r.zip(a, b, c)
puts r
But didn't work.
You need to do as below :-
a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c', 'd', 'e']
c = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
a.zip(b,c)
# => [[1, "a", "ABC"], [2, "b", "DEF"], [3, "c", "GHI"], [4, "d", "JKL"], [5, "e", "MNO"]]
One thing to remember here - Array#zip returns an array of size, equal to the size of the receiver array object.
# returns an array of size 2, as the same as receiver array size.
[1,2].zip([1,5,7]) # => [[1, 1], [2, 5]]
# below returns empty array, as the receiver array object is also empty.
[].zip([1,2,3,4,5]) # => []
For the same reason as I explained above r.zip(a, b, c) returns [].
[a,b,c].reduce(:zip).map(&:flatten)
d = [a,b,c].transpose
[[1, "a", "ABC"], [2, "b", "DEF"], [3, "c", "GHI"], [4, "d", "JKL"], [5, "e", "MNO"]]

Ruby each and collect change array of arrays

I expected that Array.each and Array.collect would never change an object, like in this example:
a = [1, 2, 3]
a.each { |x| x = 5 }
a #output => [1, 2, 3]
But this doesn't seem to be the case when you are working with an array of arrays or an array of hashes:
a = [[1, 2, 3], [10, 20], ["a"]]
a.each { |x| x[0]=5 }
a #output => [[5, 2, 3], [5, 20], [5]]
Is this behaviour expected or am I doing something wrong?
Doesn't this make ruby behaviour a little unexpected? For example, in C++ if a function argument is declared const, one can be confident the function won't mess with it (ok, it can be mutable, but you got the point).
a = [[1, 2, 3], [10, 20], ["a"]]
a.each { |x| x[0]=5 }
In the above example, x is an array ( which you are passing to the block in each iteration ), from which you are accessing an element from its 0th index, and updating it. As array is mutable object, it also updating. Here a is an array of array.
In 1st iteration x is [1, 2, 3]. Now you are calling, Array#[]= method to update the 0th element of [1, 2, 3].
In 2nd iteration x is [10, 20]. same as above.
..and so on.. Thus after #each has completed its iterations, you got modified a.
a = [1, 2, 3]
a.each { |x| x = 5 }
In the above example, you are passing the array element to the each block, which are Fixnum object, and not mutable also. Here a ia an array of elements, and you are just accessing those elements.
update ( to clear OP's comment )
a = [[1, 2, 3], [10, 20], ["a"]]
a.each do |x|
# here x is holding the object from the source array `a`.
x # => [1, 2, 3]
x.object_id # => 72635790
# here you assgined a new array object, which has the same content as the
# inner array element [1, 2, 3]. But strictly these are 2 different object. Check
# out the object_id of those two.
x = [1, 2, 3]
x # => [1, 2, 3]
x.object_id # => 72635250
break # used break to stop iteration after 1st one.
end
Using each or map does not change the array itself. But is might look like it changes elements in the array. In fact when a array is holding references to other object, that references are keep unchanged, but the referenced object itself can change. I agree it is surprising when you learn it.
What you noticed:
a = ['a', 'b', 'c']
a.each { |x| x[0] = 'x' }
puts a # => ['x', 'x', 'x']
Here the first array element still references the same string, but the string has change.
Why it is important to understand this references?
array = ['a', 'b', 'c']
a = array
b = array
puts b # => ['a', 'b', 'c']
a[0] = 'x'
puts b # => ['x', 'b', 'c']
Does freeze protect us from changes?
a = ['a', 'b', 'c'].freeze
a << ['d'] # throws 'can't modify frozen Array (RuntimeError)'
Seems so. But again only for the array itself. It does not deep freeze the array.
a[0][0] = 'x'
puts a.inspect ['x', 'b', 'c']
I suggest the read about topics like referenced objects, pointers, call by value vs. call by reference.

How can I check if a Ruby array includes one of several values?

I have two Ruby arrays, and I need to see if they have any values in common. I could just loop through each of the values in one array and do include?() on the other, but I'm sure there's a better way. What is it? (The arrays both hold strings.)
Thanks.
Set intersect them:
a1 & a2
Here's an example:
> a1 = [ 'foo', 'bar' ]
> a2 = [ 'bar', 'baz' ]
> a1 & a2
=> ["bar"]
> !(a1 & a2).empty? # Returns true if there are any elements in common
=> true
Any value in common ? you can use the intersection operator : &
[ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ]
If you are looking for a full intersection however (with duplicates) the problem is more complex there is already a stack overflow here : How to return a Ruby array intersection with duplicate elements? (problem with bigrams in Dice Coefficient)
Or a quick snippet which defines "real_intersection" and validates the following test
class ArrayIntersectionTests < Test::Unit::TestCase
def test_real_array_intersection
assert_equal [2], [2, 2, 2, 3, 7, 13, 49] & [2, 2, 2, 5, 11, 107]
assert_equal [2, 2, 2], [2, 2, 2, 3, 7, 13, 49].real_intersection([2, 2, 2, 5, 11, 107])
assert_equal ['a', 'c'], ['a', 'b', 'a', 'c'] & ['a', 'c', 'a', 'd']
assert_equal ['a', 'a', 'c'], ['a', 'b', 'a', 'c'].real_intersection(['a', 'c', 'a', 'd'])
end
end
Using intersection looks nice, but it is inefficient. I would use "any?" on the first array (so that iteration stops when one of the elements is found in the second array). Also, using a Set on the second array will make membership checks fast. i.e.:
a = [:a, :b, :c, :d]
b = Set.new([:c, :d, :e, :f])
c = [:a, :b, :g, :h]
# Do a and b have at least a common value?
a.any? {|item| b.include? item}
# true
# Do c and b have at least a common value?
c.any? {|item| b.include? item}
#false
Array#intersect? (Ruby 3.1+)
Starting from Ruby 3.1, there is a new Array#intersect? method,
which checks whether two arrays have at least one element in common.
Here is an example:
a = [1, 2, 3]
b = [3, 4, 5]
c = [7, 8, 9]
# 3 is the common element
a.intersect?(b)
# => true
# No common elements
a.intersect?(c)
# => false
Also, Array#intersect? can be much faster than alternatives since it avoids creating an intermediate array, returns true as soon as it finds a common element, it is implemented in C.
Sources:
Ruby 3.1 adds Array#intersect?.
Pull Request.
Discussion.
Source code.
Try this
a1 = [ 'foo', 'bar' ]
a2 = [ 'bar', 'baz' ]
a1-a2 != a1
true

Resources