Merge sub arrays containing that have intersection in Groovy / Java - arrays

Suppose we have one array:
def a = [[0], [1], [2], [2, 1], [1, 2], [3, 2], [2, 3], [3],
[4, 3], [3, 4], [4], [5], [6], [8], [10, 7], [7, 10],
[7, 8], [8, 7], [9], [10], [7, 10, 8], [8, 9], [9, 8],
[11, 0], [0, 11]]
then
def b = reduce(a)
should produce
[ [0, 11], [1, 2, 3, 4], [5], [6], [7,8,9,10] ]
My logic was to have a new array reducedObjects that would contain the result. First subarray from objects is copied to reducedObjects and then next subarray is checked if has intersection with subarrays from reducedObjects. If there is an intersection, then make a union from the subarray in reducedObjects and the original subarray.
def reduce(objects) {
def reducedObjects = new ArrayList()
objects.each { object ->
if (reducedObjects.size() == 0) {
reducedObjects << object
} else {
def isReduced = false
for (int i = 0; i < reducedObjects.size(); i++) {
def equals = false
object.each { it ->
if(reducedObjects[i].contains(it)) {
equals = true
isReduced = true
}
}
if (equals) {
reducedObjects[i] = (object + reducedObjects[i]).unique().sort()
reducedObjects.unique()
}
}
if(!isReduced) {
reducedObjects << object
}
}
}
return reducedObjects.unique()
}
The following function do not work fine, as the result is:
[[0, 11], [1, 2, 3, 4], [5], [6], [7, 8, 9, 10], [8, 9]]
The problem is that reducedObjects has an subarray [7, 8] and then the next subarray is [9]. The function will create a new subarray as there is no intersection, but in next iteration, there is a subarray [8, 9] and it is merged in both [7,8] and [9].
Is there any better solution to do this? Or somehow to improve this solution?

To expand on my comment above - I think the problem lies here:
for (int i = 0; i < reducedObjects.size(); i++) {
def equals = false
object.each { it ->
if(reducedObjects[i].contains(it)) {
equals = true
isReduced = true
}
}
if (equals) {
reducedObjects[i] = (object + reducedObjects[i]).unique().sort()
reducedObjects.unique()
}
}
Here, you are iterating over your input list (current item from that list being object) and comparing that object with all the previously reduced lists and merging them, but for every reduced list it is being merged.
Imagine your reducedObjects looks like [[7,8],[9,10]] and object is [8,9] - then this code will merge object [8,9] with both those existing items.
A more idiomatic groovy approach might be something like:
def a = [[0], [1], [2], [2, 1], [1, 2], [3, 2], [2, 3], [3],
[4, 3], [3, 4], [4], [5], [6], [8], [10, 7], [7, 10],
[7, 8], [8, 7], [9], [10], [7, 10, 8], [8, 9], [9, 8],
[11, 0], [0, 11]]
def reduced = []
a.each{ incoming ->
List allReducedLists = (reduced.findAll{ r -> incoming.intersect(r) } + incoming).flatten().unique()
reduced = reduced.findAll{ r -> !allReducedLists.intersect(r) }
reduced << allReducedLists
}
println reduced
(not tested beyond the above input, so maybe missed some cases)

Here is another option you have got that uses a recursive closure:
def a = [[0], [1], [2], [2, 1], [1, 2], [3, 2], [2, 3], [3],
[4, 3], [3, 4], [4], [5], [6], [8], [10, 7], [7, 10],
[7, 8], [8, 7], [9], [10], [7, 10, 8], [8, 9], [9, 8],
[11, 0], [0, 11]
]
def reducedList
reducedList = {l->
def r = []
def found = false
l.each{a1->
found = false
l.each{a2->
if(a1!=a2){
if(a1.intersect(a2).size()>0){
r.push(a1.plus(a2).sort().unique())
found = true
}
}
}
if(found==false)r.push(a1.sort().unique())
}
return (r.unique()==l)?r.unique():reducedList(r)
}
println reducedList(a) //[[0, 11], [1, 2, 3, 4], [5], [6], [7, 8, 9, 10]]

Related

How to get all elements from this array with ruby?

Want to get all the unique ids from this type of array:
[[], [1], [2], [1, 3, 2], [4, 5, 6, 7, 8, 1], [2, 4], [3], [2]]
Given a your array
a.flatten.uniq
[[], [1], [2], [1, 3, 2], [4, 5, 6, 7, 8, 1], [2, 4], [3], [2]].
reduce([]) { |acc, e| acc | e }
# or (credit goes to #Stefan)
# reduce([], :|)
#⇒ [1, 2, 3, 4, 5, 6, 7, 8]
For completeness sake a Micro-Benchmark that shows yet another solution: use inplace versions of uniq! which will be slightly faster.
If it is okay to change the original array you can also use
array.flatten!
array.uniq!
but be aware that this modifies the array and this might be unwanted, especially if it is a paramter to a method.
Here is a micro-benchmark:
require "benchmark/ips"
# ARRAY = [[], [1], [2], [1, 3, 2], [4, 5, 6, 7, 8, 1], [2, 4], [3], [2]].freeze
ARRAY = [[], [1], [2], [1, 3, 2], [4, 5, 6, 7, 8, 1], [2, 4], [3], [2], [], [1], [2], [1, 3, 2], [4, 5, 6, 7, 8, 1], [2, 4], [3], [2], [], [1], [2], [1, 3, 2], [4, 5, 6, 7, 8, 1], [2, 4], [3], [2], [], [1], [2], [1, 3, 2], [4, 5, 6, 7, 8, 1], [2, 4], [3], [2],[], [1], [2], [1, 3, 2], [4, 5, 6, 7, 8, 1], [2, 4], [3], [2]].freeze
Benchmark.ips do |x|
x.compare!
x.report("flatten.uniq") { ARRAY.flatten.uniq }
x.report("flatten.uniq!") { ARRAY.flatten.uniq! }
x.report("reduce") { ARRAY.reduce([]) { |acc, e| acc | e } }
end
And here are the results:
Comparison:
flatten.uniq!: 107888.3 i/s
flatten.uniq: 105813.6 i/s - same-ish: difference falls within error
reduce: 49892.6 i/s - 2.16x slower
Note that the reduce variant might actually be faster if there are fewer elements. But it will only work with one level of nesting whereas the other versions will work for all levels. So [[[1]]] will not work with the reduce variant.

Summing elements from arrays with matching elements

I have an array that looks like this:
original = [[1, 2, 3], [2, 2, 2], [1, 2, 3], [2, 2, 2], [2, 2, 3], [1, 2, 2], [5, 4, 2]]
I'd like to get a new array whose elements that match the second and third position would sum up its first position to get this:
expected_output = [[4, 2, 3], [5, 2, 2], [5, 4, 2]]
I got to grouping the elements from the array as follows:
new_array = original.group_by {|n| n[1] && n[2] }
# => {3=>[[1, 2, 3], [1, 2, 3], [2, 2, 3]], 2=>[[2, 2, 2], [2, 2, 2], [1, 2, 2], [5, 4, 2]]}
It is still far from my desired output.
Here's one way to return a new array of arrays where the first element of each array is the sum of the original array's first element where its second and third elements match:
arr = [[1, 2, 3], [2, 2, 2], [1, 2, 3], [2, 2, 2], [2, 2, 3], [1, 2, 2], [5, 4, 2]]
array_groups = arr.group_by { |sub_arr| sub_arr[1, 2] }
result = array_groups.map do |k, v|
k.unshift(v.map(&:first).inject(:+))
end
result
# => [[4, 2, 3], [5, 2, 2], [5, 4, 2]]
Hope this helps!
This will produce a similar result using an array grouping rather than combining the two latter numbers.
original = [[1, 2, 3], [2, 2, 2], [1, 2, 3], [2, 2, 2], [2, 2, 3], [1, 2, 2], [5, 4, 2]]
new = original.group_by {|n| [n[1], n[2]] }
added = new.map{|x| [new[x.first].map(&:first).inject(0, :+),x.first].flatten}
puts added.to_s
original.each_with_object(Hash.new(0)) { |(f,*rest),h| h[rest] += f }.
map { |(s,t),f| [f,s,t] }
# => [[4, 2, 3], [5, 2, 2], [5, 4, 2]]
Note that
original.each_with_object(Hash.new(0)) { |(f,*rest),h| h[rest] += f }
#=> {[2, 3]=>4, [2, 2]=>5, [4, 2]=>5}
Hash.new(0) is sometimes called a counting hash. To understand how that works, see Hash::new, especially the explanation of the effect of providing a default value as an argument of new. In brief, if a hash is defined h = Hash.new(0), then if h does not have a key k, h[k] returns the default value, here 0 (and the hash is not changed).

Finding index and size of consecutive repeated elements in an array

An array consists of 1, 2, and 0s. I am trying to identify the maximum repetition and its starting index within the array.
Example:
2 2 1 0 2 2 2 0 1 1
The method should accept an integer arguement, which can be one of the numbers 1 or 2
If we demonstrate these inputs on above array, the outputs would be:
find_duplicates(2)
=> 3,4
find_duplicates(1)
=> 2,8
where the first number indicates the size of the duplication, and second is the starting index of it.
I tried looping through the array and compare with arr[i+1] or arr[-1], but this is not the correct approach. Any help will be greatly appreciated.
Edit:
I had not pasted what I had tried at the time I asked the question, this is not something I would do if I could feel some confidence on the way I followed:
def find_status(arr,participant)
status = Array.new
#arr is a two dimensional array
for i in 0...arr.length do
current_line=arr[i]
cons=0
for j in 0...current_line.length do
#I worked on lots of if/else/case statements here, this is just one of them
if current_line[j] == participant
cons+=1 #count consecutive
if current_line[j]!=participant
cons=0
end
end
status[i] = cons
end
end
return status
end
def max_run(arr, target)
_,b = arr.each_with_index.
chunk { |n,_| n==target }.
select { |tf,_| tf==true }.
max_by { |_,a| a.size }
b ? [b.size, b.first.last] : nil
end
arr = [1,1,2,2,2,3,1,1,1,1,2,2,2,2,3,3]
max_run(arr,1) #=> [4, 6]
max_run(arr,2) #=> [4, 10]
max_run(arr,3) #=> [2, 14]
max_run(arr,4) #=> nil
For target = 2, the steps are as follows:
enum0 = arr.each_with_index
#=> #<Enumerator: [1, 1, 2, 2, 2, 3, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3]
# :each_with_index>
We can see the elements that will be generated by this enumerator by converting it to an array:
enum0.to_a
#=> [[1, 0], [1, 1], [2, 2], [2, 3], [2, 4], [3, 5], [1, 6], [1, 7], [1, 8],
# [1, 9], [2, 10], [2, 11], [2, 12], [2, 13], [3, 14], [3, 15]]
Continuing,
enum1 = enum0.chunk { |n,_| n==target }
#=> #<Enumerator: #<Enumerator::Generator:0x007f9beb9b0850>:each>
Carefully examine the return value here. You can think of enum1 as a "compound enumerator". It will generate the following values:
enum1.to_a
#=> [[false, [[1, 0], [1, 1]]], [true, [[2, 2], [2, 3], [2, 4]]],
# [false, [[3, 5], [1, 6], [1, 7], [1, 8], [1, 9]]],
# [true, [[2, 10], [2, 11], [2, 12], [2, 13]]], [false, [[3, 14], [3, 15]]]]
Continuing,
c = enum1.select { |tf,_| tf==true }
#=> [[true, [[2, 2], [2, 3], [2, 4]]],
# [true, [[2, 10], [2, 11], [2, 12], [2, 13]]]]
_,b = c.max_by { |_,a| a.size }
#=> [true, [[2, 10], [2, 11], [2, 12], [2, 13]]]
b #=> [[2, 10], [2, 11], [2, 12], [2, 13]]
b ? [b.size, b.first.last] : nil
#=> [[2, 10], [2, 11], [2, 12], [2, 13]] ? [4, [2,10].last]
#=> [4, 10]
a = [2, 2, 1, 0, 2, 2, 2, 0, 1, 1]
longest_sequence =
a.each_index.select{|i| a[i] == 2}.chunk_while{|i, j| i.next == j}.max_by(&:length)
# => [4, 5, 6]
[longest_sequence.length, longest_sequence.first] # => [3, 4]
The solution below is likely most efficient since it is O(N). It walks through an array, collecting the chunks:
arr.each.with_index.reduce({idx:-1, i: -1, len: 0}) do |memo, (e, i)|
memo[:i] = i if memo[:i] == -1 && e == 2 # at the beginning of chunk
memo[:len], memo[:idx] = [i - memo[:i], memo[:i]] \
if memo[:i] >= 0 && i - memo[:i] > memo[:len] # save values if needed
memo[:i] = -1 unless e == 2 # reset index counter
memo
end.reject { |k, _| k == :i } # reject temporary index value
#⇒ {
# :idx => 4,
# :len => 3
# }
To use it as method, accepting a parameter; just wrap the code above with def find_duplicates number and substitute 2 with number in the code above. Yes, it returns hash instead of an array.

Joining two ranges into 2d array Ruby

How do I join two ranges into a 2d array as such in ruby? Using zip doesn't provide the result I need.
(0..2) and (0..2)
# should become => [[0,0],[0,1],[0,2], [1,0],[1,1],[1,2], [2,0],[2,1],[2,2]]
Ruby has a built in method for this: repeated_permutation.
(0..2).to_a.repeated_permutation(2).to_a
I'm puzzled. Here it is a day after the question was posted and nobody has suggested the obvious: Array#product:
[*0..2].product [*1..3]
#=> [[0, 1], [0, 2], [0, 3], [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3]]
range_a = (0..2)
range_b = (5..8)
def custom_join(a, b)
a.inject([]){|carry, a_val| carry += b.collect{|b_val| [a_val, b_val]}}
end
p custom_join(range_a, range_b)
Output:
[[0, 5], [0, 6], [0, 7], [0, 8], [1, 5], [1, 6], [1, 7], [1, 8], [2, 5], [2, 6], [2, 7], [2, 8]]
straight forward solution:
range_a = (0..2)
range_b = (5..8)
def custom_join(a, b)
[].tap{|result| a.map{|i| b.map{|j| result << [i, j]; } } }
end
p custom_join(range_a, range_b)
Output:
[[0, 5], [0, 6], [0, 7], [0, 8], [1, 5], [1, 6], [1, 7], [1, 8], [2, 5], [2, 6], [2, 7], [2, 8]]
Simply, this will do it:
a = (0...2).to_a
b = (0..2).to_a
result = []
a.each { |ae| b.each { |be| result << [ae, be] } }
p result
# => [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2]]

Group every n-th element of array

I have an array:
array = [1, 2, 3, 4, 5, 6, 7, 8, 9]
How do I group every n-elements of array? For example, for 3:
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
I wrote this code, but it's ugly:
array.each_with_index.group_by { |e, i| i % 3}.map {|h| h[1].map { |e| e[0] }}
Here's an easy way:
array.each_slice(3).to_a.transpose
#=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Nick Veys' answer is most straightforward, but here is another way.
array.group_by.with_index{|_, i| i % 3}.values
#=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Try this way out:
irb(main):002:0> a = Array(1..9)
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
irb(main):004:0> a.each_slice(3).to_a.transpose # good
=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Not what you want? Let me know
This could also be done by creating a hash, then extracting the values. For Ruby 1.9+, this would preserve the order of the elements of the array.
Code
def bunch(arr,ngroups)
arr.each_with_index.with_object(Hash.new { |h,k| h[k]=[] }) { |(e,i),h|
h[i%ngroups] << e }
.values
end
Example
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
bunch(arr,1) #=> [[1, 2, 3, 4, 5, 6, 7, 8, 9]]
bunch(arr,2) #=> [[1, 3, 5, 7, 9], [2, 4, 6, 8]]
bunch(arr,3) #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
bunch(arr,4) #=> [[1, 5, 9], [2, 6], [3, 7], [4, 8]]
bunch(arr,5) #=> [[1, 6], [2, 7], [3, 8], [4, 9], [5]]
bunch(arr,6) #=> [[1, 7], [2, 8], [3, 9], [4], [5], [6]]
bunch(arr,7) #=> [[1, 8], [2, 9], [3], [4], [5], [6], [7]]
bunch(arr,8) #=> [[1, 9], [2], [3], [4], [5], [6], [7], [8]]
bunch(arr,9) #=> [[1], [2], [3], [4], [5], [6], [7], [8], [9]]
Alternatives
If the argument were instead the size of each group, grp_size, we could compute:
ngroups = (arr.size+grp_size-1)/grp_size
The operative line of the method could instead be written:
arr.each_with_index.with_object({}) { |(e,i),h| (h[i%ngroups] ||= []) << e }
.values

Resources