How to compare arrays inside an array with each other in ruby? - ruby-on-rails

[ 1, 1, 3, 5 ] & [ 1, 2, 3 ] #=> [ 1, 3 ]
[ 'a', 'b', 'b', 'z' ] & [ 'a', 'b', 'c' ] #=> [ 'a', 'b' ]
I need the intersection of each array with all other arrays within an array.
So the array could look like ->
a = [[1, 2, 3], [3, 4, 5], [4, 5, 6]]
The result should look like ->
a = [[3],[3,4,5][4,5]]
Any suggestions?

Look into the combination method.
a = [[1, 2, 3], [3, 4, 5], [4, 5, 6],[1,"a","b"]]
p a.combination(2).map{|x,y| x & y } #=> [[3], [], [1], [4, 5], [], []]
And if you do not want the empty arrays in there:
p a.combination(2).map{|x,y| x & y }.reject(&:empty?) #=> [[3], [1], [4, 5]]
Edit: After seeing some examples what OP actually want here is how I would achieve the desired result:
original = [[1, 2, 3], [3, 4, 5], [4, 5, 6]]
def intersect_with_rest(array)
array.size.times.map do
first, *rest = array
array.rotate!
first & rest.flatten
end
end
p intersect_with_rest(original) #=> [[3], [3, 4, 5], [4, 5]]
p original #=> [[1, 2, 3], [3, 4, 5], [4, 5, 6]]
Or:
original = [[1, 2, 3], [3, 4, 5], [4, 5, 6]]
result = original.map.with_index do |x,i|
x & (original[0...i]+original[1+i..-1]).flatten
end
p result #=> [[3], [3, 4, 5], [4, 5]]

Yeah, finally I found a solution. Maybe there is a simpler way, but that works for me now..
c = [[1,2,3],[3,4,5],[4,5,6]]
results = [];c.length.times.each {|e| results.push c.rotate(e).combination(2).map {|x, y| x & y}}
results.map{|x, y| y + x}
=> [[3], [3, 4, 5], [4, 5]]
Thanks to #hirolau for the hint. Best regards

Related

How to count consecutive numbers in an array?

If I have an array:
array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]
I want to be able to identify consecutive matching numbers that have a length of greater than 3. And map the starting index of the consecutive numbers. An example output for the above array would be:
consecutive_numbers = [
{starting_index: 1, value: 2, length: 4},
{starting_index: 10, value: 3, length: 4},
{starting_index: 14, value: 2, length: 7}
]
The values can be the same, but the consecutive series' must be mutually exclusive. See that there are 2 hashes with a value of 2, but their starting indexes are different.
My attempt so far... looks like this:
array.each_cons(3).with_index.select{|(a,b,c), i|
[a,b,c].uniq.length == 1
}
but that will returns:
[[[2, 2, 2], 1], [[2, 2, 2], 2], [[1, 1, 1], 7], [[3, 3, 3], 10], [[3, 3, 3], 11], [[2, 2, 2], 14], [[2, 2, 2], 15], [[2, 2, 2], 16], [[2, 2, 2], 17], [[2, 2, 2], 18]]
But that returns overlapping results.
array.each_with_index.
chunk(&:first).
select { |_,a| a.size > 3 }.
map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
The steps are as follows.
e = array.each_with_index.chunk(&:first)
#=> #<Enumerator: #<Enumerator::Generator:0x00005b1944253c18>:each>
We can convert this enumerator to an array to view the elements it will generate and pass to its block.
e.to_a
#=> [[1, [[1, 0]]],
# [2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
# [5, [[5, 5], [5, 6]]],
# [1, [[1, 7], [1, 8], [1, 9]]],
# [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
# [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]]
Continuing,
c = e.select { |_,a| a.size > 3 }
#=> [[2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
# [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
# [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]]
c.map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
This is another way.
array.each_with_index.with_object([]) do |(n,i),arr|
if arr.any? && arr.last[:value] == n
arr.last[:length] += 1
else
arr << { starting_index: i, value: n, length: 1 }
end
end.select { |h| h[:length] > 3 }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
You can chunk_while each pair of elements are equal:
p array.chunk_while { |a, b| a == b }.to_a
# [[1], [2, 2, 2, 2], [5, 5], [1, 1, 1], [3, 3, 3, 3], [2, 2, 2, 2, 2, 2, 2]]
You select the arrays with 3 or more elements.
After that, with then, you can yield self, so you have access to the array of arrays, which you can use to get the starting_index:
[1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].chunk_while(&:==).then do |this|
this.each_with_object([]).with_index do |(e, memo), index|
memo << { starting_index: this.to_a[0...index].flatten.size, value: e.first, length: e.size }
end
end.select { |e| e[:length] > 3 }
# [{:starting_index=>1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
For the starting_index, you get the elements to the current index (non inclusive), flatten them, and get the total of elements.
The value, as each array in the array has the same elements, can be anything, the length, is the length of the current array in the "main" array.
This is another option..
Zipping the array (Enumerable#zip) with its indexes by Endles Ranges
Calling Enumerable#slice_when (another flavour of chunk_while)
Mapping (Enumerable#map) to the required Hash
Finally rejecting (Enumerable#reject) hashes if length is greater than 3
array
.zip(0..)
.slice_when { |a, b| a.first != b.first }
.map { |a| { starting_index: a.first.last, value: a.first.first, length: a.size } }
.reject { |h| h[:length] < 3 }
#=> [{:starting_index=>1, :value=>2, :length=>4}, {:starting_index=>7, :value=>1, :length=>3}, {:starting_index=>10, :value=>3, :length=>4}, {:starting_index=>14, :value=>2, :length=>7}]
Well, the most obvious (and probably the fastest) way is iterate over an array and count everything manually:
array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]
array_length_pred = array.length.pred
consecutive_numbers = []
starting_index = 0
value = array.first
length = 1
array.each_with_index do |v, i|
if v != value || i == array_length_pred
length += 1 if i == array_length_pred && value == v
if length >= 3
consecutive_numbers << {
starting_index: starting_index,
value: value,
length: length
}
end
starting_index = i
value = v
length = 1
next
end
length += 1
end
p consecutive_numbers
# [{:starting_index=>1, :value=>2, :length=>4},
# {:starting_index=>7, :value=>1, :length=>3},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
You could work with strings instead.
Here, I coerce the array into a string:
input_sequence = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].join
I use a regex to group consecutive characters:
groups = input_sequence.gsub(/(.)\1*/).to_a
#=> ["1", "2222", "55", "111", "3333", "2222222"]
Now I can search for the groups as substrings within the input string:
groups.map do |group|
{
starting_index: input_sequence.index(group),
value: group[0].to_i,
length: group.length
}
end.reject { |group| group[:length] <= 3 }
#=> [{:starting_index=>1, :value=>2, :length=>4},
{:starting_index=>7, :value=>1, :length=>3},
{:starting_index=>10, :value=>3, :length=>4},
{:starting_index=>14, :value=>2, :length=>7}]
There's room for improvement here -- I'm creating lots of intermediate objects for one -- but I thought I would offer a different approach.

Sort items in cycle

I'm having trouble explaining what I am looking for so I will provide an example, let's say I have this array:
[
[1, 2],
[1, 3],
[1, 4],
[2, 3],
[2, 4],
[3, 4]
]
rather than sorting it by the first column, I would like it to cycle through the first column, so instead of 1, 1, 1, 2, 2, 3 it would do: 1, 2, 3, 1, 2, 1
resulting in:
[
[1, 2],
[2, 3],
[3, 4],
[1, 3],
[2, 4],
[1, 4]
]
Even better would be if it could cycle through both columns to prevent two numbers in a row as much as possible, the ideal solution would sort the original array as:
[
[1, 2],
[3, 4],
[1, 3],
[2, 4],
[1, 4],
[2, 3]
]
Leading to the maximum spacing between repeating numbers for each inner array (both columns being taken into account).
I hope I have provided sufficient information, and I will greatly appreciate any advise, I am fairly clueless so far, searching has yeilded me nothing.
I will only address the first part of your question as I don't understand what you mean by "Even better would be if it could cycle through both columns to prevent two numbers in a row as much as possible...". The clause "as much as possible" is especially troublesome, as it refers to an unspecified criterion.
Let arr be your array. The elements are sorted in your example, but if they were not, the first step would be:
arr.sort!
See Array#sort! and Array#<=> for an explanation of how Ruby sorts arrays whose elements are arrays.
There are many ways to obtain the desired ordering. Here is one that uses Enumerable#chunk:
arr.chunk(&:first).flat_map {|_,a| a.map.with_index {|i,b| [b,i]}}.sort.map(&:last)
#=> [[1, 2], [2, 3], [3, 4], [1, 3], [2, 4], [1, 4]]
The steps are as follows:
e = arr.chunk(&:first)
#=> #<Enumerator: #<Enumerator::Generator:0x007fa01a8141d0>:each>
We can see the elements of this enumerator, which are passed to the block by Enumerator#each (which calls Array#each), by converting it to an array:
e.to_a
#=> [[1, [[1, 2], [1, 3], [1, 4]]], [2, [[2, 3], [2, 4]]], [3, [[3, 4]]]]
Continuing:
f = e.flat_map { |_,a| a.map.with_index { |i,b| [b,i] } }
#=> [[0, [1, 2]], [1, [1, 3]], [2, [1, 4]], [0, [2, 3]], [1, [2, 4]], [0, [3, 4]]]
g = f.sort
#=> [[0, 1, 2], [0, 2, 3], [0, 3, 4], [1, 1, 3], [1, 2, 4], [2, 1, 4]]
g.map(&:last)
#=> [[1, 2], [2, 3], [3, 4], [1, 3], [2, 4], [1, 4]]
Let's look more closely at the calculation of f:
h = e.flat_map
#=> #<Enumerator: #<Enumerator: #<Enumerator::Generator:0x007fa01a8141d0>:each>:flat_map>
h.to_a
#=> [[1, [[1, 2], [1, 3], [1, 4]]], [2, [[2, 3], [2, 4]]], [3, [[3, 4]]]]
You can think of h as a "compound" enumerator.
The first value of h, [1, [[1, 2], [1, 3], [1, 4]]], is passed to the block and captured by the block variables using parallel (or multiple) assignment:
i, a = h.next
#=> [1, [[1, 2], [1, 3], [1, 4]]]
i #=> 1
a #=> [[1, 2], [1, 3], [1, 4]]
As i is not used in the block calculation, it is customary to replace that block variable with the local variable _.
We can now perform the block calculation:
a.map.with_index { |i,b| [b,i] }
#=> [[0, [1, 2]], [1, [1, 3]], [2, [1, 4]]]
The remaining calculations are performed similarly.
you could try this
def func ary
ret = []
# group by first ones, and each sort by second ones
a = ary.group_by{|i| i[0]}.map{|_,i| i.sort_by{|j| j[1]}}
# add to ret
(0...a.map{|i| i.size}.max).map{
a.map{|i| ret << i.shift}
}
ret.compact
end
a = [[1, 2],[1, 3],[1, 4],[2, 3],[2, 4],[3, 4]]
p func(a)
#=> [[1, 2], [2, 3], [3, 4], [1, 3], [2, 4], [1, 4]]
Assuming the initial array is sorted by the first element:
arr =
[
[1, 2],
[1, 3],
[1, 4],
[2, 3],
[2, 4],
[3, 4],
]
res = []
arr_dup = arr.dup
remaining_values = arr_dup.map { |el| el[0] }
current_value = remaining_values.first
loop do
arr_dup.each_with_index do |el, index|
if el[0] >= current_value
res << el
current_value = remaining_values.select { |v| v > el[0] }.first || remaining_values.first
remaining_values.delete_at(remaining_values.index(current_value))
arr_dup.delete_at(index)
break
end
end
break if remaining_values.empty?
end
p arr #=> [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
p res #=> [[1, 2], [2, 3], [3, 4], [1, 3], [2, 4], [1, 4]]
Few tests:
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4], [5, 1], [20, 2]] =>
[[1, 2], [2, 3], [3, 4], [5, 1], [20, 2], [1, 3], [2, 4], [1, 4]]
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4], [5, 1], [5, 2], [20, 2]] =>
[[1, 2], [2, 3], [3, 4], [5, 1], [20, 2], [1, 3], [2, 4], [5, 2], [1, 4]]

Cycle through an array in Ruby

I have an array of the form [1,2,3,4,5].
Is it possible to loop through this array and during each iteration get an array where the starting point is the current element and the end point is the element before that?
Like
[1,2,3,4,5]
[2,3,4,5,1]
[3,4,5,1,2]
[4,5,1,2,3]
[5,1,2,3,4]
I am trying with .cycle method of array but it is not giving the expected result.
Check out rotate.
a = [ "a", "b", "c", "d" ]
a.rotate #=> ["b", "c", "d", "a"]
irb(main):005:0> array = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):006:0> array.size.times.map{|i| array.rotate(i)}
=> [[1, 2, 3, 4, 5], [2, 3, 4, 5, 1], [3, 4, 5, 1, 2], [4, 5, 1, 2, 3], [5, 1, 2 , 3, 4]]

Ruby "bucketize" an array

Suppose I have the following array:
a = (1..10).to_a
Is there a single in-built ruby (or rails) function that is capable or splitting the array into exactly N roughly equal parts while maintaining the order?
I'm looking for something like this:
a.bucketize(3)
=> [[1,2,3,4],[5,6,7],[8,9,10]]
a.bucketize(5)
=> [[1,2],[3,4],[5,6],[7,8],[9,10]]
Hint: each_slice doesn't do this.
Also, I know I could write this function myself and open up the Array class or Enumerable module.
Thanks.
I'd do it like this:
ary = (1..10).to_a
ary.each_slice((ary.length.to_f/3).ceil).to_a
=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
ary.each_slice((ary.length.to_f/5).ceil).to_a
=> [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
It's not perfect, but it does come close:
ary = (1..9).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
ary.each_slice((ary.length.to_f/2).ceil).to_a
=> [[1, 2, 3, 4, 5], [6, 7, 8, 9]]
ary.each_slice((ary.length.to_f/3).ceil).to_a
=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ary.each_slice((ary.length.to_f/4).ceil).to_a
=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
This kind of task is best tackled using a functional approach. Here's a tail-recursive functional implementation (except for the unavoidable << to accumulate efficiently on arrays):
class Array
def bucketize(n, index = 0, acc = [])
return acc if n <= 0 || size <= index
n0 = ((size - index).to_f / n).ceil
bucketize(n - 1, index + n0, acc << self[index, n0])
end
end
(1..9).to_a.bucketize(3)
#=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
(1..10).to_a.bucketize(3)
#=> [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
(1..11).to_a.bucketize(3)
#=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11]]
Here's what I ended up doing:
class Array
def bucketize(n)
return [] if (buckets = n.to_i) <= 0
j = length / buckets.to_f
result = each_with_index.chunk { |_, i| (i / j).floor }.map { |_, v| v.map(&:first) }
result << [] until result.length == buckets
result
end
end
Examples:
a = (1..10).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a.bucketize(1)
=> [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]]
a.bucketize(2)
=> [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
a.bucketize(3)
=> [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
a.bucketize(4)
=> [[1, 2, 3], [4, 5], [6, 7, 8], [9, 10]]
...
a.bucketize(9)
=> [[1, 2], [3], [4], [5], [6], [7], [8], [9], [10]]
...
a.bucketize(11)
=> [[1], [2], [3], [4], [5], [6], [7], [8], [9], [10], []]

How do I sort two arrays the same way?

I have two arrays:
a = [6, 4, 3]
b = [1, 3, 4]
I call a.sort:
a.sort = [3, 4, 6]
How do I sort array b so the values have the same position to values in array a before the sort?
It would be now:
b = [4, 3, 1]
So that values in b have the same position to values in array a.
You could combine both the arrays into one using the zip method. Once you combine a and b, you would get,
[[6, 1], [4, 3], [3, 4]]
Now sort the arrays, which would sort them based on the first element of each sub-array resulting in,
[[3, 4], [4, 3], [6, 1]]
Now we want to do the reverse of zip to get the first and second elements of each sub-array into a new array. Using transpose, we can get it back in the original form as,
[[3, 4, 6], [4, 3, 1]]
Thankfully using parallel assignment all of this is possible in one line. Here's the full code,
x, y = a.zip(b).sort.transpose
Now x should contain [3, 4, 6], and y should contain [4, 3, 1].
a = [6, 4, 3]
b = [1, 3, 4]
ra, rb = a.zip(b).sort_by(&:first).transpose
# ra => [3, 4, 6]
# rb => [4, 3, 1]
I don't know what you're trying to achieve, and I'm sure others could come up with a more elegant solution, but I would use a Hash instead. Assign the values of a a as the key, and the values of b as the values. You could iterate over a to accomplish this, or just in advance when you're creating this data. The result should be:
$ hash
=> {6 => 1, 4 => 3, 3 => 4}
a0 b0 a1 b1 a2 b2
$ hash.sort
=> [[3, 4], [4, 3], [6, 1]]
Like I said, not super smooth, but I've got turkey hangover...
[a, b].transpose.sort { |x, y| x[0] <=> y[0] }.transpose[1]
=> [4, 3, 1]
or
a, b = [a, b].transpose.sort { |x, y| x[0] <=> y[0] }.transpose

Resources