Ruby three-way transitive comparison - arrays

I want to compare three arrays transitively to see if there are any shared elements:
arrs = [
["AAA", "", ""],
["", "", "CCC"],
["AAA", "BBB", "CCC"]
]
I want to return a matrix that compares the elements transitively. That is, if two arrays share any of the same elements, or if they match with a third record, return 1. Otherwise return 0.
With this example the result should be:
result = [
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]
]
result[0][0] is 1 because if we compare arrs[0] with arrs[0] (compare itself), they share "AAA".
result[0][1] is 1 because if we compare arrs[0] and arrs[1] there are no shared elements, but both arrs[0] & arrs[2] and arrs[1] & arrs[2] return an intersecting element, so we return 1
result[0][2] is 1 because if we compare arrs[0] with arrs[2], they share "AAA"
We repeat the process for all other array combinations in arrs.

It's really not that tricky, you just need to double-map here:
def transitive(arr)
arr.map do |a|
arr.map do |b|
(a & b).any? ? 1 : 0
end
end
end
A more Ruby approach is to use true or false, but 1 and 0 is fine if you can handle the ternary to convert it.
How it works:
arrs = [
["AAA", "", ""],
["", "", "CCC"],
["AAA", "BBB", "CCC"]
]
transitive(arrs)
# => [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
Not a very exciting example. Here's one that has more variety:
arrs = [
%w[ A B C ],
%w[ A D E ],
%w[ D E F ]
]
transitive(arrs)
# => [[1, 1, 0], [1, 1, 1], [0, 1, 1]]
Where that has some misses.

Assumption
We are given an array arr of size n, each element being an array of size m. We wish to construct another n x m array, a, such each element a[i][j] equals 1 (else 0) if all of the following arrays are non-empty:
a[i] & a[i] # non-empty if a is non-empty
a[i] & a[(i+1)%n]
a[(i+1)%n] & a[(i+2)%n]
a[(j-1)%n] & a[j]
This is what I interpret "transitive" to mean. Note that I've assumed the transitive relation "wraps around" from the last to the first element of arr.
Let's consider an example.
arr = [["A", "B", "C"],
["A", "D", "E"],
["D", "E", "F"]]
Suppose we wish to compute a[i][j] of the array a being constructed. This equals 1 (else 0) if the following arrays are all non-empty:
a[1] & a[1] #=> a[1] => ["A", "D", "E"]
a[1] & a[2%3] #=> a[1] & a[2] => ["D"]
a[(i+1)%n] & a[(i+2)%n] #=> a[2] & a[1] => []
Note that had (a[1] & a[2%3]).empty? #=> true, it would not be necessary to compute the third expression (or any following expressions if arr were larger).
For i #=> 0,
a[0,0] = (arr[0] & arr[0]).any?
#=> arr[0].any? #=> true, hence = 1
a[0,1] = (arr[0] & arr[1]).any?
#=> ["A"].any? #=> true, hence = 1
a[0,2] = (arr[0] & arr[1]).any? && (arr[1] & arr[2]).any?
#=> (a[0,1] == 1) && ["D"].any? => true && true => true, hence = 1
For i #=> 1,
a[1,1] = (arr[1] & arr[1]).any?
#=> arr[1].any? #=> true, hence = 1
a[1,2] = (arr[1] & arr[2]).any?
#=> ["D"].any? #=> true, hence = 1
a[1,0] = (arr[1] & arr[2]).any? && (arr[2] & arr[0]).any?
#=> (a[1,2] == 1) && [].any? => true && false => true, hence = 0
For i #=> 2,
a[2,2] = (arr[2] & arr[2]).any?
#=> arr[2].any? #=> true, hence = 1
a[2,0] = (arr[2] & arr[0]).any?
#=> [].any? #=> false, hence = 0
a[2,1] = (arr[2] & arr[0]).any? && (arr[0] & arr[1]).any?
#=> (a[2,0] == 1) && ["A"].any? => false && true => false, hence = 0
Code
require 'set'
def transitive(arr)
n = arr.size
st = n.times.with_object(Set.new) do |i,st|
(i..i+n-1).each do |j|
if j==i
st << [i,j]
else
jj = j % n
jprev = (j-1) % n
break unless st.include?([i,jprev]) & (arr[jprev] & arr[jj]).any?
st << [i,jj]
end
end
end
Array.new(n) do |i|
Array.new(arr.first.size) { |j| st.include?([i,j]) ? 1 : 0 }
end
end
Example
For arr defined earlier,
transitive(arr)
#=> [[1, 1, 1],
# [0, 1, 1],
# [0, 0, 1]]
Explanation
The steps are as follows:
n = arr.size
#=> 3
st = n.times.with_object(Set.new) do |i,st|
(i..i+n-1).each do |j|
if j==i
st << [i,j]
else
jj = j % n
jprev = (j-1) % n
break unless st.include?([i,jprev]) & (arr[jprev] & arr[jj]).any?
st << [i,jj]
end
end
end
#=> #<Set: {[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]}>
st is a set of the transitive elements of arr. This shows that elements of arr with indices [0, 2] (order matters) are transitive, but those with indices [2, 0] are not (because st does not contain [2, 0]). Notice that once [2, 0] it was determined to not be transitive it was not necessary to check [2, 1].
The last step uses the method Array::new:
Array.new(n) {|i| Array.new(arr.first.size) {|j| st.include?([i,j]) ? 1 : 0}}
#=> [[1, 1, 1],
# [0, 1, 1],
# [0, 0, 1]]

Related

How do I select from an array if the next element is not one plus the current element?

I want to select all elements from my array such that the element immediately after it is not one plus the current element.
I tried this:
data_col = ["46", "47", "48", "49", "50", "51", "52"]
data_col.select.with_index{|string, index| data_col[index+1] && string.to_i != data_col[index+1].to_i + 1 }
# => ["46", "47", "48", "49", "50", "51"]
Every element is selected even though I would expect none of them (except the last one) gets selected. How can I modify my statement to get what I want?
(numbers + [nil]).each_cons(2).reject { |x, y| x.next == y }.map(&:first)
Add a nil at the end. We will use that to always include the last element:
[1, 2, 3] + [nil] # => [1, 2, 3, nil]
Pair each two consecutive elements:
[1, 2, 3].each_cons(2).to_a # => [[1, 2], [2, 3]]
Remove the pairs that don't meet your criteria:
[1, 2, 5, 6, nil].each_cons(2).reject { |x, y| x.next == y }
# => [[2, 5], [6, nil]]
Get the numbers out of the pairs:
[[2, 5], [6, nil]].map(&:first) # => [2, 6]
This is one way to use Enumerable#each_cons.
arr = [1, 3, 4, 6, 7, 9]
arr.each_cons(2).
each_with_object([]) { |(x,y),a| a << x unless x.next==y } << arr[-1]
#=> [1, 4, 7, 9]
Another option is to step through an enumerator using Enumerator#next and Enumerator#peek.
enum = arr.to_enum
a = []
loop do
x = enum.next
a << x unless x.succ == enum.peek
end
a << arr[-1]
#=> [1, 4, 7, 9]
When the enumerator is at its end, Enumerator#peek generates a StopIteration exception which is handled by Kernel#loop by breaking out of the loop.
Try
data_col.select.with_index { |number_string, index| data_col[index + 1].to_i != 1 + number_string.to_i }
Or, if don't mind that you have the result elements as integer,
n_col = data_col.map(&:to_i)
n_col.select.with_index{ |n, i| n_col[i + 1] != n + 1 }
data_col = ["46", "47", "48", "49", "50", "51", "52"]
def method(arg)
results = []
i = 0
while i < arg.length
current_num = arg[i].to_i
next_num = arg[i + 1].to_i
next_num - current_num != 1 ? results << current_num : nil
i += 1
end
return results
end
p method(data_col) #[52]
This will also return just [52] like you were looking for, it uses a simple while loop, and is pretty easy to understand what's going on.

Implementing Array#flatten

I need to implement Array#flatten. This implementation removes all nested arrays:
a = [1, 2, [3, [4, 5]]]
def my_flatten(arr)
arr.reduce([]) do |result, item|
item.is_a?(Array) ? result + my_flatten(item) : result << item
end
end
my_flatten(a) #=> [1, 2, 3, 4, 5]
Prompt how to implement such behavior
a.flatten(1) #=> [1, 2, 3, [4, 5]]
Introduce a parameter to specify the max depth (defaulting to nil) and a parameter to keep track of the current depth (0 on the initial call and then incremented by 1 on each recursive call):
def my_flatten(arr, max_depth = nil, current_depth = 0)
arr.reduce([]) do |result, item|
item.is_a?(Array) && (max_depth.nil? || current_depth < max_depth) ?
result + my_flatten(item, max_depth, current_depth + 1) : result << item
end
end
You could replace the ?: with an if/else if you felt this was more readable:
def my_flatten(arr, max_depth = nil, current_depth = 0)
arr.reduce([]) do |result, item|
if item.is_a?(Array) && (max_depth.nil? || current_depth < max_depth)
result + my_flatten(item, max_depth, current_depth + 1)
else
result << item
end
end
end
This now returns the expected results:
my_flatten(a) #=> [1, 2, 3, 4, 5]
my_flatten(a, 1) #=> [1, 2, 3, [4, 5]]
I created something similar a long time back. Here is the gist link.
Code from gist:
class Array
def my_flatten(level = nil)
rb_flatten(self, [], level)
end
private
# apply recursion based on the level
# when no level provided, then produce a complete flatten array
# when level is given, then produce a flatten array flattened till that certain level
def rb_flatten(array, result, level)
array.each do |value|
if ((value.is_a? Array) && (level.nil? || (level && level > 0)))
rb_flatten(value, result, (level.nil? ? level : ((level || 0 ) - 1)))
else
result << value
end
end
return result
end
end
Hope that helps.
you can also use Proc like this.
class Array
def my_flatten(level = nil)
p = ->(arr, exp, lvl) do
arr.each { |val| Array === val && (!level || lvl < level) ? p.(val, exp, lvl+1) : exp << val }
exp
end
p.(self, [], 0)
end
end
a = [1, 2, [3, [4, 5]]]
p a.my_flatten
# => [1, 2, 3, 4, 5]
p a.my_flatten(0)
# => [1, 2, [3, [4, 5]]]
p a.my_flatten(1)
# => [1, 2, 3, [4, 5]]
p a.my_flatten(2)
# => [1, 2, 3, 4, 5]

Ruby: Idiomatic way to compare every index of array to all other indexes of the same array?

Say I have an array arr = [7,0,4,-7] and I'd like to get the pair of indexes [i, i2] where arr[i] + arr[i2] == 0. In the example, the answer would be [0, 3]. What would be the idiomatic and efficient way to do so?
Here's the best I've gotten so far. I'm sure it's not the best way to do it. Previously I was using two while loops but I feel like this isn't any better.
> nums = [7,0,4,-7]
> nums.each_index do |n1|
(nums.length).times do |n2|
return [n1, n2] if nums[n1] + nums[n2] == 0
end
end
> [0, 3]
The following code will find you all pairs of elements where the sum is zero.
arr = [7,0,4,-7, -4, 5]
zero_sum = arr.combination(2).select { |pair| pair.first + pair.last == 0 }
zero_sum #=> [[7, -7], [4, -4]]
You can then find the indexes of these elements this way:
zero_sum.map { |pair| [arr.index(pair.first), arr.index(pair.last)] } #=> [[0, 3], [2, 4]]
If you need just one pair use method find instead of select:
arr.combination(2)
.find { |first, last| first + last == 0 } #=> [7, -7]
.map { |num| arr.index(num) } # =>[0, 3]
The following method requires only a single pass through the array. It returns all pairs of indices of elements of the array that sum to zero.
Code
def zero_summing_pairs(arr)
processed = {}
arr.each_with_index.with_object([]) do |(n,i),pairs|
processed[-n].each { |j| pairs << [j,i] } if processed.key?(-n)
(processed[n] ||= []) << i
end
end
Examples
zero_summing_pairs [7,0,4,-7]
#=> [[0, 3]]
zero_summing_pairs [7,4,0,7,4,0,-7,-4,-7]
#=> [[2, 5], [0, 6], [3, 6], [1, 7], [4, 7], [0, 8], [3, 8]]
The associated values are as follows.
arr = [7,0,4,-7]
zero_summing_pairs(arr).map { |i,j| [arr[i], arr[j]] }
#=> [[7, -7]]
arr = [7,4,0,7,4,0,-7,-4,-7]
zero_summing_pairs(arr).map { |i,j| [arr[i], arr[j]] }
#=> [[0, 0], [7, -7], [7, -7], [4, -4], [4, -4], [7, -7], [7, -7]]
Explanation
pairs is the array of pairs of indices of values of arr that sum to zero. pairs is the object that is returned by the method.
processed is a hash with keys equal to the values of arr that have been processed by the block. The value of each key k is an array of the indices i of arr that have been processed by the block and for which arr[i] #=> -n. I chose a hash structure for fast key lookup.
The line
(processed[n] ||= []) << i
requires explanation. Firstly, this is shorthand for
processed[n] = (processed[n] || []) << i
If processed has a key n (whose value is not nil), the value of that key on the right side of the above expression is a non-empty array containing indices i for which arr[i] #=> -n, so the above expression reduces to
processed[n] = processed[n] << i
and the index i is added to the array. If processed does not have a key n, processed[n] equals nil, so the expression becomes
processed[n] = (processed[n] || []) << i
= (nil || []) << i
= [] << i
= [i]
In other words, here the value of key n is made an empty array and then i is appended to that array.
Let's now step through the code for
arr = [7,0,4,-7]
processed = {}
enum0 = arr.each_with_index
#=> #<Enumerator: [7, 0, 4, -7]:each_with_index>
We can see the values that will be generated by this enumerator by converting it to an array.
enum0.to_a
#=> [[7, 0], [0, 1], [4, 2], [-7, 3]]
Continuing,
enum1 = enum0.with_object([])
#=> #<Enumerator: #<Enumerator: [7, 0, 4, -7]:each_with_index>:with_object([])>
enum1.to_a
#=> [[[7, 0], []], [[0, 1], []], [[4, 2], []], [[-7, 3], []]]
If you examine the return value for the definition of enum1, you will see that it can be thought of as a "compound" enumerator. The empty arrays (corresponding to the block variable pairs) will be filled in as the calculations are performed.
The first value of enum1 is generated and passed to the block, and the three block variables are assigned values using parallel assignment (aka multiple assignment) and disambiguation (aka decompositon).
(n,i), pairs = enum1.next
#=> [[7, 0], []]
n #=> 7
i #=> 0
pairs #=> []
As
processed.key?(-n)
#=> processed.key?(-7)
#=> false
the first line of the block is not executed. The second line of the block is
(processed[n] ||= []) << i
#=> processed[n]
#=> [i]
#=> [0]
so now
processed
#=> {7=>[0], 0=>[1]}
pairs
#=> []
The remaining three elements generated by enum1 are processed similarly.
(n,i), pairs = enum1.next
#=> [[0, 1], []]
processed.key?(-n)
#=> processed.key?(0)
#=> false
(processed[n] ||= []) << i
#=> (processed[0] ||= []) << 1
#=> [] << 1
#=> [1]
processed
#=> {7=>[0], 0=>[1]}
pairs
#=> []
(n,i), pairs = enum1.next
#=> [[4, 2], []]
processed.key?(-n)
#=> processed.key?(-4)
#=> false
(processed[n] ||= []) << i
#=> (processed[4] ||= []) << 2
#=> [] << 2
#=> [2]
processed
#=> {7=>[0], 0=>[1], 4=>[2]}
pairs
#=> []
(n,i), pairs = enum1.next
#=> [[-7, 3], []]
processed.key?(-n)
# processed.key?(7)
#=> true
processed[-n].each { |j| pairs << [j,i] }
# processed[7].each { |j| pairs << [j,3] }
#=> [0]
(processed[n] ||= []) << i
#=> (processed[-7] ||= []) << 3
#=> [] << 3
#=> [3]
processed
#=> {7=>[0], 0=>[1], 4=>[2], -7=>[3]}
pairs
#=> [[0, 3]]
Notice that the last value generated by enum1 is the first to have a match in processed, so is treated differently than the previous values in the block calculation. Lastly,
(n,i), pairs = enum1.next
#=> StopIteration: iteration reached an end (an exception)
causing pairs to be returned from the block and therefore from the method.
Here is one way to do this, it uses Array#combination, approach similar to other answer by #kallax, but works on combinations of indices instead of combination of elements:
arr = [7,0,4,-7]
(0...arr.size).to_a.combination(2).select {|i| arr[i.first] + arr[i.last] == 0}
#=> [[0, 3]]

Check if all strings in one array are in another array

This is for a tic tac toe game. I have an array board with nine string elements, and a nested array WIN_COMBINATIONS with position combinations from board:
board = ["X", "X", "X", " ", " ", " ", " ", " ", " "]
WIN_COMBINATIONS = [
[0, 1, 2],
[0, 3, 6],
[0, 4, 8],
[3, 4, 5],
[6, 7, 8],
[6, 4, 2],
[1, 4, 7],
[2, 5, 8]
]
How do I choose the array combinations from board that are all "X" or all "O" using the combinations found in WIN_COMBINATIONS?
For example
a different board than the one above in which X wins in the right diagonal.
board = ["X", "O", "X", "O", "X", "O", "X", "X", "O"]
# X | O | X
# ---+---+---
# O | X | O
# ---+---+---
# X | X | O
won?(board) #=> [2,4,6]
A slight variation of ndn's answer:
board = %w(X O X
O X O
X X O)
WIN_COMBINATIONS.select { |c| board.values_at(*c).join =~ /XXX|OOO/ }
#=> [[6, 4, 2]]
Explanation:
select returns all elements for which the block returns true.
values_at returns the values at the specified indices:
board.values_at(*[0, 1, 2])
#=> ["X", "O", "X"]
* converts the array to an argument list, so the above becomes values_at(0, 1, 2)
join returns a string with the concatenated elements:
["X", "O", "X"].join
#=> "XOX"
=~ checks if the string matches the regular expression /XXX|OOO/, i.e. either XXX or OOO
You can replace select with find if you just want to retrieve the first winning combination.
Edit Code incorporates Stefan's suggestion.
Do this once:
WIN_COMBINATIONS.each(&:sort!)
Then,
h = board.each_index.group_by{|i| board[i]}
# => {"X"=>[0, 1, 2], " "=>[3, 4, 5, 6, 7, 8]}
WIN_COMBINATIONS.find{|a| (h["X"] & a) == a or (h["O"] & a) == a}
# => [0, 1, 2]
WIN_COMBINATIONS.find do |combination|
values_at_positions = board.values_at(*combination).uniq
values_at_positions.size == 1 and ['X', 'O'].include?(*values_at_positions)
end # => [0, 1, 2]
Just out of curiosity (a slightly updated #Stefan’s answer):
WIN_COMBINATIONS.index do |wc|
board.values_at(*wc).join =~ /(?<l>\w)\k<l>\k<l>/
# or, as suggested by #Stefan: board.values_at(*wc).join =~ /(\w)\1\1/
end
#⇒ 5
Here we match the combinations to three same symbols, which is likely the most semantically correct interpretation of tic-tac-toe game.
You can try this.
def won?(board)
xpos = []
opos = []
who_won = nil;
board.each_with_index{|x,i| xpos << i if x == "X" }
board.each_with_index{|x,i| xpos << i if x == "O" }
WIN_COMBINATIONS.each do |com|
temp = com & xpos
who_won = "X" if temp == com
temp = com & opos
who_won = "O" if temp == com
break if !who_won.nil?
end
return who_won
end
won?(board) #=> X if x wins, O if O wins. nil if no body wins.
Its untested but should work.

ruby array of hash merge array values based on key

I have some questions about an array of hash in ruby.
Example :
a = [{236=>1}, {236=>1}, {237=>1}]
I want get result like this :
a = [{236=>2}, {237=>1}]
How do I merge an array of values based on their key in ruby?
I find this way readable:
a = [{236=>1}, {236=>1}, {237=>1}]
merge_with_addition = -> x,y {x.merge(y){|_,old,new|old+new}}
p a.reduce(&merge_with_addition) #=> {236=>2, 237=>1}
There are several ways to do that. Here's one that uses the form of the method Hash#update (aka merge!) that uses a block to determine the values of keys present in both hashes being merged:
a.each_with_object({}) { |g,h|
h.update(g["order_detail_id"]=>g) { |_,oh,ng|
{ "order_detail_id" =>g["order_detail_id"],
"quantity"=>oh["quantity"]+ng["quantity"] } } }.values
#=> [{"order_detail_id"=>236, "quantity"=>2},
# {"order_detail_id"=>237, "quantity"=>1}]
Another approach uses Enumerable#group_by:
a.group_by { |h| h["order_detail_id"] }.map { |id, a|
{ "order_detail_id"=>id, "quantity"=>a.reduce(0) { |t,g|
t + g["quantity"] } } }
#=> [{"order_detail_id"=>236, "quantity"=>2},
# {"order_detail_id"=>237, "quantity"=>1}]
Edit: monoy has asked how one can merge:
a = [{236=>1}, {236=>1}, {237=>1}]
When you have hashes with a single key-value pair, it's often easiest to first convert them to arrays, to have easy access to the key and value:
b = a.map(&:to_a)
#=> [[[236, 1]], [[236, 1]], [[237, 1]]]
Unfortunately, this has three levels of arrays, where we want just two. We could write:
b = a.map(&:to_a).flatten(1)
#=> [[236, 1], [236, 1], [237, 1]]
but an easier way is:
b = a.flat_map(&:to_a)
#=> [[236, 1], [236, 1], [237, 1]]
Again, we can merge the arrays several different ways. This is one which uses a hash with a default value of zero:
b.each_with_object(Hash.new(0)) { |(k,v),h| h[k] += v }
#=> {236=>2, 237=>1}
We have:
enum = b.each_with_object(Hash.new(0))
#=> #<Enumerator: [[236, 1], [236, 1], [237, 1]]:each_with_object({})>
We can convert this enumerator to an array to see what values each will pass into the block:
enum.to_a
#=> [[[236, 1], {}], [[236, 1], {}], [[237, 1], {}]]
We can use Enumerator#next to obtain each value of the enumerator, and assign the block variables to that value. The first passed to the block is:
(k,v),h = enum.next
#=> [[236, 1], {}]
k #=> 236
v #=> 1
h #=> {}
so in the block we execute:
h[k] += v
which evaluates to:
h[236] += 1
which means:
h[236] = h[236] + 1
The hash h is presently empty, so it doesn't have a key 236. Therefore, h[236] on the right side of the expression returns the hash's default value of zero:
h[236] = 0 + 1
so now:
h #=> {236=>1}
Now pass the next element into the block:
(k,v),h = enum.next
#=> [[236, 1], {236=>1}]
k #=> 236
v #=> 1
h #=> {236=>1}
so now the default value is not used when we execute the expression in the block:
h[k] += v
#=> h[k] = h[k] + v
#=> h[236] = h[236] + 1
#=> h[236] = 1 + 1
so now:
h #=> {236=>2}
The third and last element of the enumerator is now passed to the block:
(k,v),h = enum.next
#=> [[237, 1], {236=>2}]
k #=> 237
v #=> 1
h #=> {236=>2}
h[k] += v
#=> h[k] = h[k] + v
#=> h[237] = h[237] + 1
#=> h[236] = 0 + 1 # the default value of 0 applies
h #=> {236=>2, 237=>1}
and we are finished.
array = [{234 => 1}, {235 => 1}, {234 => 1}]
hashes = array.map(&:to_a).flatten
.each_slice(2).with_object(Hash.new(0)) { |(k, v), h|
h[k] += v
}.each_with_object([]) { |(k, v), a|
a << { k => v }
}

Resources