I have an array:
a = [1,2,3,4,5,6]
I want an array of arrays taking the elements up to a certain amount like:
b = group(a,4) = [[1,2,3,4],[2,3,4,5],[3,4,5,6],[4,5,6],[5,6],[6]]
I tried:
a.each_cons(4) {|x1,x2,x3,x4| b[a.index(x1) = [x1,x2,x3,x4]}
but the resulting array doesn't have the last three elements:
[[1, 2, 3, 4],[2, 3, 4, 5],[3, 4, 5, 6]]
a = [1,2,3,4,5,6]
a.map.with_index { |e, i| a.slice(i, 4) }
#⇒ [1, 2, 3, 4]
#⇒ [2, 3, 4, 5]
#⇒ [3, 4, 5, 6]
#⇒ [4, 5, 6]
#⇒ [5, 6]
#⇒ [6]
a = [1,2,3,4,5,6]
asize = a.size
#=> 6
a.each_index.map { |i| a[i, [4, asize-i].min] }
#=> [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6], [5, 6], [6]]
The steps are as follows.
asize = a.size
#=> 6
enum0 = a.each_index
#=> #<Enumerator: [1, 2, 3, 4, 5, 6]:each_index>
enum1 = enum0.map
#=> #<Enumerator: #<Enumerator: [1, 2, 3, 4, 5, 6]:each_index>:map>
Have a close look at the return value for enum.map. You will see that it can be thought of as a compound enumerator.
We can see the elements that will be generated by enum1 by converting it to an array.
enum1.to_a
#=> [0, 1, 2, 3, 4, 5]
We can now pass each element generated by enum1 to the block for the mapping.
i = enum1.next
#=> 0
a[i, [4, asize-i].min]
#=> a[0, [4, 6].min]
#=> a[0, 4]
#=> [1,2,3,4]
so the first element of a, 1, is mapped to [1,2,3,4].
The remaining steps are as follows.
i = enum1.next #=> 1
a[i, [4, asize-i].min] #=> [2, 3, 4, 5]
i = enum1.next #=> 2
a[i, [4, asize-i].min] #=> [3, 4, 5, 6]
i = enum1.next #=> 3
a[i, [4, asize-i].min] #=> [4, 5, 6]
i = enum1.next #=> 4
a[i, [4, asize-i].min] #=> [5, 6]
i = enum1.next #=> 5
a[i, [4, asize-i].min] #=> [6]
i = enum1.next #=> StopIteration: iteration reached an end
Related
I have created a very ugly script to collect same numbers from an array. I don't think this is a very Ruby way :) Anyone could provide a more clean solution?
ar = [5, 5, 2, 2, 2, 6, 6]
collections = []
collect_same = []
while ar.length > 0
first = ar.values_at(0).join.to_i
second = ar.values_at(1).join.to_i
if ar.length == 1
collect_same << ar[0]
collections << collect_same
break
else
sum = ar.values_at(0, 1).inject {|a,b| a + b}
if second == first
p collect_same << ar[0]
ar.shift
else
collect_same << ar[0]
collections << collect_same
collect_same = []
ar.shift
end
end
end
p collections
The output:
=> [[5, 5], [2, 2, 2], [6, 6]]
Note, that in primary array same numbers always goes one after another.
So I wouldn't have primary array like this - ar = [1, 2, 1, 2]
Using chunk_while:
[5, 5, 2, 2, 2, 6, 6].chunk_while(&:==).to_a
#=> [[5, 5], [2, 2, 2], [6, 6]]
Ruby prior to 2.3:
[5, 5, 2, 2, 2, 6, 6].each_with_object([]) do |e, acc|
acc.last && acc.last.last == e ? acc.last << e : acc << [e]
end
#=> [[5, 5], [2, 2, 2], [6, 6]]
In case if you want to do it without order:
ar.group_by(&:itself).values
=> [[5, 5], [2, 2, 2], [6, 6]]
[5, 5, 2, 2, 2, 6, 6].slice_when(&:!=).to_a
#=> [[5, 5], [2, 2, 2], [6, 6]]
One could perhaps say that Enumerable#chunk_while and Enumerable#slice_when are ying and yang.
Prior to Ruby v2.3, one might write
[5, 5, 2, 2, 2, 6, 6].chunk(&:itself).map(&:last)
and prior to v2.2,
[5, 5, 2, 2, 2, 6, 6].chunk { |n| n }.map(&:last)
just another oneliner
arr = [5, 5, 2, 2, 2, 6, 6]
arr.uniq.map {|e| [e]*arr.count(e) }
# => [[5, 5], [2, 2, 2], [6, 6]]
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.
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.
What is the most concise and explicit way to write a method for this?
Given an array a of numbers and a number n, find the n consecutive elements of a whose sum is the largest.
Return the largest sum and the index of the first element in the group.
For example, with a = [1, 1, 1, 1, 1, 1, 1, 2] and n = 2, the
result would be a sum 3 and position 6.
arr = [1,3,2,4,3,5,2,1,3,4,2,5,1]
size = 3
Inefficient but pretty
arr.each_cons(size).with_index.map { |a,i| [a.inject(:+), i] }.max_by(&:first)
#=> [12, 3]
Efficient but whupped with an ugly stick1
tot = arr[0,size].inject(:+)
(1..arr.size-size).each_with_object([tot, 0]) do |i, best|
tot += arr[i+size-1] - arr[i-1]
best.replace([tot, i]) if tot > best.first
end
#=> [12, 3]
Steps performed by the pretty one
enum0 = arr.each_cons(size)
#=> #<Enumerator: [1, 3, 2, 4, 3, 5, 2, 1, 3, 4, 2, 5, 1]:each_cons(3)>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [1, 3, 2, 4, 3, 5, 2, 1, 3, 4, 2, 5, 1]:
# each_cons(3)>:with_index>
Carefully examine the above return value for enum1. You will see it is effectively a "compound" enumerator. We can see the values that enum1 will generate and pass to map by converting it to an array:
enum1.to_a
#=> [[[1, 3, 2], 0], [[3, 2, 4], 1], [[2, 4, 3], 2], [[4, 3, 5], 3],
# [[3, 5, 2], 4], [[5, 2, 1], 5], [[2, 1, 3], 6], [[1, 3, 4], 7],
# [[3, 4, 2], 8], [[4, 2, 5], 9], [[2, 5, 1], 10]]
Continuing:
b = enum1.map { |a,i| [a.inject(:+), i] }
#=> [[6, 0], [9, 1], [9, 2], [12, 3], [10, 4], [8, 5],
# [6, 6], [8, 7], [9, 8], [11, 9], [8, 10]]
Note the since the first element of enum1 that map passes to the block is [[1, 3, 2], 0], the two block variables are assigned as follows (using parallel or multiple assignment):
a, i = [[1, 3, 2], 0]
#=> [[1, 3, 2], 0]
a #=> [1, 3, 2]
i #=> 0
and the block calculation is performed:
[a.inject(:+), i]
#=> [6, 0]
Lastly,
b.max_by(&:first)
#=> [12, 3]
Enumerable#max_by determines the largest value among
b.map(&:first)
#=> [6, 9, 9, 12, 10, 8, 6, 8, 9, 11, 8]
Steps performed by the less pretty one
a = arr[0,size]
#=> [1, 3, 2]
tot = a.inject(:+)
#=> 6
enum = (1..arr.size-size).each_with_object([tot, 0])
#=> (1..13-3).each_with_object([6, 0])
#=> #<Enumerator: 1..10:each_with_object([6, 0])>
enum.to_a
#=> [[1, [6, 0]], [2, [6, 0]], [3, [6, 0]], [4, [6, 0]], [5, [6, 0]],
# [6, [6, 0]], [7, [6, 0]], [8, [6, 0]], [9, [6, 0]], [10, [6, 0]]]
enum.each do |i, best|
tot += arr[i+size-1] - arr[i-1]
best.replace([tot, i]) if tot > best.first
end
#=> [12, 3]
The first element of enum, [1, [6, 0]], is passed to the block, assigned to the block variables and the block calculation is performed:
i, best = [1, [6, 0]]
#=> [1, [6, 0]]
i #=> 1
best
#=> [6, 0]
tot += arr[i+size-1] - arr[i-1]
# tot = 6 + arr[1+3-1] - arr[1-1]
# = 6 + 4 - 1
# = 9
best.replace([tot, i]) if tot > best.first
#=> best.replace([9, 1]) if 9 > 6
#=> [9, 1]
best
#=> [9, 1]
The remaining calculations are similar.
1 Credit to Bo Diddley (at 2:51)
I have an array:
[1, 4, 4, 4, 2, 9, 0, 4, 3, 3, 3, 3, 4]
and want to replace the repeating values with a string "repeat". The repeated 4 at indices 1, 2, 3 and 3 at indices 8, 9, 10, 11 should be replaced. I should get:
[1, "repeat", 2, 9, 0, 4, "repeat", 4]
How is this accomplished?
Here are two ways you could do that.
#1 Use Enumerable#chunk:
arr = [1,4,4,4,2,9,0,4,3,3,3,3,4]
arr.chunk(&:itself).map { |f,a| a.size==1 ? f : "repeat" }
#=> [1, "repeat", 2, 9, 0, 4, "repeat", 4]
The steps:
enum = arr.chunk(&:itself)
#=> #<Enumerator: #<Enumerator::Generator:0x007febc99fb160>:each>
We can view the elements of this enumerator by converting it to an array:
enum.to_a
#=> [[1, [1]], [4, [4, 4, 4]], [2, [2]], [9, [9]], [0, [0]],
# [4, [4]], [3, [3, 3, 3, 3]], [4, [4]]]
Object#itself was added in Ruby v2.2. For earlier version you would use
enum = arr.chunk { |e| e }
It is now a simple matter to map the elements of enum as required:
enum.map { |f,a| a.size==1 ? f : "repeat" }
#=> [1, "repeat", 2, 9, 0, 4, "repeat", 4]
#2 Use Enumerable#slice_when
arr.slice_when { |e,f| e !=f }.map { |a| a.size==1 ? a.first : "repeat" }
The steps:
enum = arr.slice_when { |e,f| e !=f }
#=> #<Enumerator: #<Enumerator::Generator:0x007febc99b8cc0>:each>
a = enum.to_a
#=> [[1], [4, 4, 4], [2], [9], [0], [4], [3, 3, 3, 3], [4]]
a.map { |a| a.size==1 ? a.first : "repeat" }
#=> [1, "repeat", 2, 9, 0, 4, "repeat", 4]
slice_when was introduced in Ruby v.2.2.