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.
Related
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).
I would like to find all the permutations of plucking 3, 4 or 5 numbers from [2,3,4,5,6,7,8], repeats allowed, such that their sum is 16. So [8,5,3], [8,3,5] and [4,3,3,3,3] are valid permutations. Also circular permutations should be removed so [3,3,3,3,4] wouldn't also be added to the answer.
I can do this in Ruby without allowing repeats like this:
d = [2,3,4,5,6,7,8]
number_of_divisions = [3,4,5]
number_of_divisions.collect do |n|
d.permutation(n).to_a.reject do |p|
p[0..n].inject(0) { |sum,x| sum + x } != 16
end
end
How could I allow repeats so that [3,3,3,3,4] was included?
For all permutations, including duplicates, one might use Array#repeated_permutation:
d = [2,3,4,5,6,7,8]
number_of_divisions = [3,4,5]
number_of_divisions.flat_map do |n|
d.repeated_permutation(n).reject do |p| # no need `to_a`
p.inject(:+) != 16
end
end
or, even better with Array#repeated_combination:
number_of_divisions.flat_map do |n|
d.repeated_combination(n).reject do |p| # no need `to_a`
p.inject(:+) != 16
end
end
There are far fewer repeated combinations than repeated permutations, so let's find the repeated combinations that sum to the given value, then permute each of those. Moreover, by applying uniq at each of several steps of the calculation we can significantly reduce the number of repeated combinations and permutations considered.
Code
require 'set'
def rep_perms_for_all(arr, n_arr, tot)
n_arr.flat_map { |n| rep_perms_for_1(arr, n, tot) }
end
def rep_perms_for_1(arr, n, tot)
rep_combs_to_rep_perms(rep_combs_for_1(arr, n, tot)).uniq
end
def rep_combs_for_1(arr, n, tot)
arr.repeated_combination(n).uniq.select { |c| c.sum == tot }
end
def rep_combs_to_rep_perms(combs)
combs.flat_map { |c| comb_to_perms(c) }.uniq
end
def comb_to_perms(comb)
comb.permutation(comb.size).uniq.uniq do |p|
p.size.times.with_object(Set.new) { |i,s| s << p.rotate(i) }
end
end
Examples
rep_perms_for_all([2,3,4,5], [3], 12)
#=> [[2, 5, 5], [3, 4, 5], [3, 5, 4], [4, 4, 4]]
rep_perms_for_all([2,3,4,5,6,7,8], [3,4,5], 16).size
#=> 93
rep_perms_for_all([2,3,4,5,6,7,8], [3,4,5], 16)
#=> [[2, 6, 8], [2, 8, 6], [2, 7, 7], [3, 5, 8], [3, 8, 5], [3, 6, 7],
# [3, 7, 6], [4, 4, 8], [4, 5, 7], [4, 7, 5], [4, 6, 6], [5, 5, 6],
# [2, 2, 4, 8], [2, 2, 8, 4], [2, 4, 2, 8], [2, 2, 5, 7], [2, 2, 7, 5],
# [2, 5, 2, 7], [2, 2, 6, 6], [2, 6, 2, 6], [2, 3, 3, 8], [2, 3, 8, 3],
# ...
# [3, 3, 3, 7], [3, 3, 4, 6], [3, 3, 6, 4], [3, 4, 3, 6], [3, 3, 5, 5],
# [3, 5, 3, 5], [3, 4, 4, 5], [3, 4, 5, 4], [3, 5, 4, 4], [4, 4, 4, 4],
# ...
# [2, 2, 4, 5, 3], [2, 2, 5, 3, 4], [2, 2, 5, 4, 3], [2, 3, 2, 4, 5],
# [2, 3, 2, 5, 4], [2, 3, 4, 2, 5], [2, 3, 5, 2, 4], [2, 4, 2, 5, 3],
# ...
# [2, 5, 3, 3, 3], [2, 3, 3, 4, 4], [2, 3, 4, 3, 4], [2, 3, 4, 4, 3],
# [2, 4, 3, 3, 4], [2, 4, 3, 4, 3], [2, 4, 4, 3, 3], [3, 3, 3, 3, 4]]
Explanation
rep_combs_for_1 uses the method Enumerable#sum, which made its debut in Ruby v2.4. For earlier versions, use c.reduce(:0) == tot.
In comb_to_perms, the first uniq simply removes duplicates. The second uniq, with a block, removes all but one of the p.size elements (arrays) that can be obtained by rotating any of the other p-1 elements. For example,
p = [1,2,3]
p.size.times.with_object(Set.new) { |i,s| s << p.rotate(i) }
#=> #<Set: {[1, 2, 3], [2, 3, 1], [3, 1, 2]}>
I was stumped coming up with a functional way to reverse a multi-dimmensional (even dimensions) array in Ruby.
input: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
output: [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
This iterative solution works.
def reverse(arr)
size = arr.length
output = Array.new(size) { Array.new(size,0) }
arr.reverse.each_with_index do |a, i|
a.each_with_index do |a, j|
output[j][i] = a
end
end
output
end
Anyone have any insight into how to do using more of functional programming style and without referring to an explicit index?
If array is your input, then it is as simple as
result = array.transpose.map(&:reverse)
if I understand your desired output correctly. ;)
To elaborate a bit: Array#transpose basically "mirrors" the 2D array along the main diagonal:
transposed = array.transpose #=> [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
You seem to want that only with all the rows reversed, which is handled by the call to map:
result = transposed.map(&:reverse) #=> [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
The map(&:reverse) syntax is only shorthand for map { |a| a.reverse } and is enabled by this method.
Doing it by hand
After my initial answer it turned out in the comments that the OP is actually after a functional implementation of transpose. Here is what I came up with:
def transpose(a)
(0...a[0].length).map { |i|
(0...a.length).map { |j| a[j][i] }
}
end
Although this does refer to explicit indices, it is a pure function composed of other pure functions, so it at least meets my definition of functional. ;)
ar = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ar.reverse.transpose # => [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
arr = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
arr_rev = arr.reverse
#=> [[7, 8, 9], [4, 5, 6], [1, 2, 3]]
arr_rev.first.zip *arr_rev[1..-1]
#=> [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
I believe this satisfies the requirements of functional programming.
The steps:
arr_rev = arr.reverse
#=> [[7, 8, 9], [4, 5, 6], [1, 2, 3]]
arr_rev.first.zip(arr_rev[1..-1])
#=> [7, 8, 9].zip(*[[4, 5, 6], [1, 2, 3]])
#. [7, 8, 9].zip([4, 5, 6], [1, 2, 3])
#. [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
See Enumerable#zip.
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]]
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