I'm working to update the SVG::Graph gem, and have made many improvements to my version, but have found a bottleneck with multiple array sorting.
There is a "sort_multiple" function built in, which keeps an array of arrays (all of equal size) sorted by the first array in the group.
The issue I have is that this sort works well on truly random data, and really badly on sorted, or almost sorted data:
def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
if lo < hi
p = partition(arrys,lo,hi)
sort_multiple(arrys, lo, p-1)
sort_multiple(arrys, p+1, hi)
end
arrys
end
def partition( arrys, lo, hi )
p = arrys[0][lo]
l = lo
z = lo+1
while z <= hi
if arrys[0][z] < p
l += 1
arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
end
z += 1
end
arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
l
end
this routine appears to use a variant of the Lomuto partition scheme from wikipedia: https://en.wikipedia.org/wiki/Quicksort#Lomuto_partition_scheme
I have an array of 5000+ numbers, which is previously sorted, and this function adds about 1/2 second per chart.
I have modified the "sort_multiple" routine with the following:
def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
first = arrys.first
return arrys if first == first.sort
if lo < hi
...
which has "fixed" the problem with sorted data, but I was wondering if there is any way to utilise the better sort functions built into ruby to get this sort to work much quicker. e.g. do you think I could utilise a Tsort to speed this up? https://ruby-doc.org/stdlib-2.6.1/libdoc/tsort/rdoc/TSort.html
looking at my benchmarking, the completely random first group appears to be very fast.
Current benchmarking:
def sort_multiple( arrys, lo=0, hi=arrys[0].length-1 )
if lo < hi
p = partition(arrys,lo,hi)
sort_multiple(arrys, lo, p-1)
sort_multiple(arrys, p+1, hi)
end
arrys
end
def partition( arrys, lo, hi )
p = arrys[0][lo]
l = lo
z = lo+1
while z <= hi
if arrys[0][z] < p
l += 1
arrys.each { |arry| arry[z], arry[l] = arry[l], arry[z] }
end
z += 1
end
arrys.each { |arry| arry[lo], arry[l] = arry[l], arry[lo] }
l
end
first = (1..5400).map { rand }
second = (1..5400).map { rand }
unsorted_arrys = [first.dup, second.dup, Array.new(5400), Array.new(5400), Array.new(5400)]
sorted_arrys = [first.sort, second.dup, Array.new(5400), Array.new(5400), Array.new(5400)]
require 'benchmark'
Benchmark.bmbm do |x|
x.report("unsorted") { sort_multiple( unsorted_arrys.map(&:dup) ) }
x.report("sorted") { sort_multiple( sorted_arrys.map(&:dup) ) }
end
results:
Rehearsal --------------------------------------------
unsorted 0.070699 0.000008 0.070707 ( 0.070710)
sorted 0.731734 0.000000 0.731734 ( 0.731742)
----------------------------------- total: 0.802441sec
user system total real
unsorted 0.051636 0.000000 0.051636 ( 0.051636)
sorted 0.715730 0.000000 0.715730 ( 0.715733)
#EDIT#
Final accepted solution:
def sort( *arrys )
new_arrys = arrys.transpose.sort_by(&:first).transpose
new_arrys.each_index { |k| arrys[k].replace(new_arrys[k]) }
end
I have an array of 5000+ numbers, which is previously sorted, and this function adds about 1/2 second per chart.
Unfortunately, algorithms implemented in Ruby can become quite slow. It's often much faster to delegate the work to the built-in methods that are implemented in C, even if it comes with an overhead.
To sort a nested array, you could transpose it, then sort_by its first element, and transpose again afterwards:
arrays.transpose.sort_by(&:first).transpose
It works like this:
arrays #=> [[3, 1, 2], [:c, :a, :b]]
.transpose #=> [[3, :c], [1, :a], [2, :b]]
.sort_by(&:first) #=> [[1, :a], [2, :b], [3, :c]]
.transpose #=> [[1, 2, 3], [:a, :b, :c]]
And although it creates several temporary arrays along the way, the result seems to be an order of magnitude faster than the "unsorted" variant:
unsorted 0.035297 0.000106 0.035403 ( 0.035458)
sorted 0.474134 0.003065 0.477199 ( 0.480667)
transpose 0.001572 0.000082 0.001654 ( 0.001655)
In the long run, you could try to implement your algorithm as a C extension.
I confess I don't fully understand the question and don't have the time to study the code at the link, but it seems that you have one sorted array that you are repeatedly mutating only slightly, and with each change you may mutate several other arrays, each a little or a lot. After each set of mutations you re-sort the first array and then rearrage each of the other arrays consistent with the changes in indices of elements in the first array.
If, for example, the first array were
arr = [2,4,6,8,10]
and the change to arr were to replace the element at index 1 (4) with 9 and the element at index 3 (8) with 3, arr would become [2,9,6,3,10], which, after re-sorting, would be [2,3,6,9,10]. We could do that as follows:
new_arr, indices = [2,9,6,3,10].each_with_index.sort.transpose
#=> [[2, 3, 6, 9, 10], [0, 3, 2, 1, 4]]
Therefore,
new_arr
#=> [2, 3, 6, 9, 10]
indices
#=> [0, 3, 2, 1, 4]
the intermediate calculation being
[2,9,6,3,10].each_with_index.sort
#=> [[2, 0], [3, 3], [6, 2], [9, 1], [10, 4]]
Considering that
new_array == [2,9,6,3,10].values_at(*indices)
#=> true
we see that each of the other arrays, after having been mutated, can be sorted to conform with the sorting of indices in the first array with the following method, which is quite fast.
def sort_like_first(a, indices)
a.values_at(*indices)
end
For example,
a = [5,4,3,1,2]
a.replace(sort_like_first a, indices)
a #=> [5, 1, 3, 4, 2]
a = %w|dog cat cow pig owl|
a.replace(sort_like_first a, indices)
a #=> ["dog", "pig", "cow", "cat", "owl"]
In fact, it's not necessary to sort each of the other arrays until they are required in the calculations.
I would now like to consider a special case, namely, when only a single element in the first array is to be changed.
Suppose (as before)
arr = [2,4,6,8,10]
and the element at index 3 (8) is to be replaced with 5, resulting in [2,4,6,5,10]. A fast sort can be done with the following method, which employs a binary search.
def new_indices(arr, replace_idx, replace_val)
new_loc = arr.bsearch_index { |n| n >= replace_val } || arr.size
indices = (0..arr.size-1).to_a
index_removed = indices.delete_at(replace_idx)
new_loc -= 1 if new_loc > replace_idx
indices.insert(new_loc, index_removed)
end
arr.bsearch_index { |n| n >= replace_val } returns nil if n >= replace_val #=> false for all n. It is for that reason I have tacked on || arr.size.
See Array#bsearch_index, Array#delete_at and Array#insert.
Let's try it. If
arr = [2,4,6,8,10]
replace_idx = 3
replace_val = 5
then
indices = new_indices(arr, replace_idx, replace_val)
#=> [0, 1, 3, 2, 4]
Only now can we replace the element of arr at index replace_idx.
arr[replace_idx] = replace_val
arr
#=> [2, 4, 6, 5, 10]
We see that the re-sorted array is as follows.
arr.values_at(*indices)
#=> [2, 4, 5, 6, 10]
The other arrays are sorted as before, using sort_like_first:
a = [5,4,3,1,2]
a.replace(sort_like_first(a, indices))
#=> [5, 4, 1, 3, 2]
a = %w|dog cat cow pig owl|
a.replace(sort_like_first(a, indices))
#=> ["dog", "cat", "pig", "cow", "owl"]
Here's a second example.
arr = [2,4,6,8,10]
replace_idx = 3
replace_val = 12
indices = new_indices(arr, replace_idx, replace_val)
#=> [0, 1, 2, 4, 3]
arr[replace_idx] = replace_val
arr
#=> [2, 4, 6, 12, 10]
The first array sorted is therefore
arr.values_at(*indices)
#=> [2, 4, 6, 10, 12]
The other arrays are sorted as follows.
a = [5,4,3,1,2]
a.replace(sort_like_first a, indices)
a #=> [5, 4, 3, 2, 1]
a = %w|dog cat cow pig owl|
a.replace(sort_like_first a, indices)
a #=> ["dog", "cat", "cow", "owl", "pig"]
Related
I want to move the element at index 2 to the start of the array [1, 2, 3, 4], the resulting array should look like [3, 1, 2, 4].
My solution was to do the following
[3] + ([1, 2, 3, 4] - [3])
Is there a better way to do this?
A method that takes the first n elements from an array and rotates them by one, then adds back the remaining elements.
def rotate_first_n_right(arr, n)
arr[0...n].rotate(-1) + arr[n..-1]
end
rotate_first_n_right([1,2,3,4], 3)
# => [3, 1, 2, 4]
This does fail if we try to use it on an array that is too short, as the arr[n..-1] slice will yield nil which will cause an error when we try to add it to the first array.
We can fix this by expanding both slices into a list.
def rotate_first_n_right(arr, n)
[*arr[0...n].rotate(-1), *arr[n..-1]]
end
To see why this works, a very simple example:
[*[1, 2, 3], *nil]
# => [1, 2, 3]
A problem with you example is what happens if 3 occurs in the array more than once. E.g.
[1,2,3,3,3,4] - [3]
# => [1, 2, 4]
Not sure what you mean about "rotation" as this is not exactly a rotation but you could go with
def move_element_to_front(arr, idx)
# ruby >= 2.6 arr.dup.then {|a| a.unshift(a.delete_at(idx)) }
arr = arr.dup
arr.unshift(arr.delete_at(idx))
end
This will move the element at idx to the first position in the returned Array
def move_element_to_front(arr, idx)
[arr[idx]].concat(arr[0,idx], arr[idx+1..])
end
arr = [:dog, :cat, :pig, :hen]
move_element_to_front(arr, 2)
#=> [:pig, :dog, :cat, :hen]
move_element_to_front(arr, 0)
#=> [:dog, :cat, :pig, :hen]
move_element_to_front(arr, 3)
#=> [:hen, :dog, :cat, :pig]
The operative line of the method could alternatively be expressed
[arr[idx], *arr[0,idx], *arr[idx+1..]]
I am using hacker rank and I do not understand why my ruby code only works for one test case out of like 20. Here is the question:
John Watson knows of an operation called a right circular rotation on
an array of integers. One rotation operation moves the last array
element to the first position and shifts all remaining elements right
one. To test Sherlock's abilities, Watson provides Sherlock with an
array of integers. Sherlock is to perform the rotation operation a
number of times then determine the value of the element at a given
position.
For each array, perform a number of right circular rotations and
return the values of the elements at the given indices.
Function Description
Complete the circularArrayRotation function in the editor below.
circularArrayRotation has the following parameter(s):
int a[n]: the array to rotate
int k: the rotation count
int queries[1]: the indices to report
Returns
int[q]: the values in the rotated a as requested in m
Input Format
The first line contains 3 space-separated integers, n, k, and q, the number of elements in the integer array, the rotation count and the number of queries. The second line contains n space-separated integers,
where each integer i describes array element a[i] (where 0 <= i < n). Each of the q subsequent lines contains a single integer, queries[i], an index of an element
in a to return.
Constraints
Sample Input 0
3 2 3
1 2 3
0
1
2
Sample Output 0
2
3
1
Here is my code :
def circularArrayRotation(a, k, queries)
q = []
while k >= 1
m = a.pop()
a.unshift m
k = k - 1
end
for i in queries do
v = a[queries[i]]
q.push v
end
return q
end
It only works for the sample text case but I can't figure out why. Thanks for any help you can provide.
Haven't ran any benchmarks, but this seems like a job for the aptly named Array.rotate() method:
def index_at_rotation (array, num_rotations, queries)
array = array.rotate(-num_rotations)
queries.map {|q| array[q]}
end
a = [1, 2, 3]
k = 2
q = [0,1, 2]
index_at_rotation(a, k, q)
#=> [2, 3, 1]
Handles negative rotation values and nil results as well:
a = [1, 6, 9, 11]
k = -1
q = (1..4).to_a
index_at_rotation(a, k, q)
#=> [9, 11, 1, nil]
I don't see any errors in your code, but I would like to suggest a more efficient way of making the calculation.
First observe that after q rotations the element at index i will at index (i+q) % n.
For example, suppose
n = 3
a = [1,2,3]
q = 5
Then after q rotations the array will be as follows.
arr = Array.new(3)
arr[(0+5) % 3] = a[0] #=> arr[2] = 1
arr[(1+5) % 3] = a[1] #=> arr[0] = 2
arr[(2+5) % 3] = a[2] #=> arr[1] = 3
arr #=> [2,3,1]
We therefore can write
def doit(n,a,q,queries)
n.times.with_object(Array.new(n)) do |i,arr|
arr[(i+q) % n] = a[i]
end.values_at(*queries)
end
doit(3,[1,2,3],5,[0,1,2])
#=> [2,3,1]
doit(3,[1,2,3],5,[2,1])
#=> [1, 3]
doit(3,[1,2,3],2,[0,1,2])
#=> [2, 3, 1]
p doit(3,[1,2,3],0,[0,1,2])
#=> [1,2,3]
doit(20,(0..19).to_a,25,(0..19).to_a.reverse)
#=> [14, 13, 12, 11, 10, 9, 8, 7, 6, 5,
# 4, 3, 2, 1, 0, 19, 18, 17, 16, 15]
Alternatively, we may observe that after q rotations the element at index j was initially at index (j-q) % n.
For the earlier example, after q rotations the array will be
[a[(0-5) % 3], a[(1-5) % 3], a[(2-5) % 3]]
#=> [a[1], a[2], a[0]]
#=> [2,3,1]
We therefore could instead write
def doit(n,a,q,queries)
n.times.map { |j| a[(j-q) % n] }.values_at(*queries)
end
I'm fairly new to learning Ruby so please bear with me. I am working on a 7 kyu Ruby coding challenge and I've been tasked with finding how many people are left on the bus (first value represents people on, second value, people off) please look at comments in code for more detail.
below is a test example:
([[10, 0], [3, 5], [5, 8]]), # => should return 5"
This is my solution so far:
def number(bus_stops)
bus_stops.each{ | on, off | on[0] -= off[1] }
end
bus_stops
# loop through the array
# for the first array in the nested array subtract second value from first
# add the sum of last nested array to first value of second array and repeat
# subtract value of last element in nested array and repeat
How can I approach this? any resources you would recommend?
There would be many ways to achieve this. Here is one with inject
arr.map { |inner_array| inner_array.inject(&:-) }.inject(&:+)
Iterate over the arrays and calculate the count at each position of how many people would have been left on the bus (this can return negative integers). This will return
[10, -2, -3]
[10 on, none off][3 on, 5 off][5 on, 8 off]
Then inject a + operator between each element to calculate the sum of people left on the bus. This only works if you count from 0 people on and 0 people off.
Here are two other ways to compute the desired result.
arr = [[10, 0], [3, 5], [5, 8]]
Use Array#transpose
arr.transpose.map(&:sum).reduce(:-)
#=> 5
The steps are as follows.
a = arr.transpose
#=> [[10, 3, 5], [0, 5, 8]]
b = a.map(&:sum)
#=> [18, 13] ([total ons, total offs])
b.reduce(:-)
#=> 5
Use Matrix methods
require 'matrix'
(Matrix.row_vector([1] * arr.size) * Matrix[*arr] * Matrix.column_vector([1,-1]))[0,0]
#=> 5
The steps are as follows.
a = [1] * arr.size
#=> [1, 1, 1]
b = Matrix.row_vector(a)
#=> Matrix[[1, 1, 1]]
c = Matrix[*arr]
#=> Matrix[[10, 0], [3, 5], [5, 8]]
d = b * c
#=> Matrix[[18, 13]]
e = Matrix.column_vector([1,-1])
#=> Matrix[[1], [-1]]
f = d * e
#=> Matrix[[5]]
f[0,0]
#=> 5
See Matrix::[], Matrix::row_vector, Matrix::column_vector and Matrix#[]. Notice that the instance method [] is documented in Object.
sum takes a block, which is really simple in this case:
arr = [[10, 0], [3, 5], [5, 8]]
p arr.sum{|on, off| on - off} # => 5
So you were very close.
I have a number and an array:
n = 4
a = [0, 1, 2, 3, 3, 4]
I want to find the indices corresponding to the maximal n elements of a in the reverse order of the element size, and in stable order when the element sizes are equal. The expected output is:
[5, 3, 4, 2]
This code:
a.each_with_index.max(n).map(&:last)
# => [5, 4, 3, 2]
gives the right indices, but changes the order.
Code
def max_with_order(arr, n)
arr.each_with_index.max_by(n) { |x,i| [x,-i] }.map(&:last)
end
Examples
a = [0,1,2,3,3,4]
max_with_order(a, 1) #=> [5]
max_with_order(a, 2) #=> [5, 3]
max_with_order(a, 3) #=> [5, 3, 4]
max_with_order(a, 4) #=> [5, 3, 4, 2]
max_with_order(a, 5) #=> [5, 3, 4, 2, 1]
max_with_order(a, 6) #=> [5, 3, 4, 2, 1, 0]
Explanation
For n = 3 the steps are as follows.
b = a.each_with_index
#=> #<Enumerator: [0, 1, 2, 3, 3, 4]:each_with_index>
We can convert b to an array to see the (six) values it will generate and pass to the block.
b.to_a
#=> [[0, 0], [1, 1], [2, 2], [3, 3], [3, 4], [4, 5]]
Continuing,
c = b.max_by(n) { |x,i| [x,-i] }
#=> [[4, 5], [3, 3], [3, 4]]
c.map(&:last)
#=> [5, 3, 4]
Note that the elements of arr need not be numeric, merely comparable.
You can supply a block to max to make the determination more specific like so
a.each_with_index.max(n) do |a,b|
if a[0] == b[0] # the numbers are the same
b[1] <=> a[1] # compare the indexes in reverse
else
a[0] <=> b[0] # compare the numbers themselves
end
end.map(&:last)
#=> [5,3,4,2]
max block expects a comparable response e.g. -1,0,1 so in this case we are just saying if the number is the same then compare the indexes in reverse order e.g. 4 <=> 3 #=> -1 the -1 indicates this values is less so that will then be placed after 3
Also to expand on #CarySwoveland's answer (which I am a bit jealous I did not think of), since you only care about returning the indices we could implement as follows without a secondary map
a.each_index.max_by(n) { |x| [a[x],-x] }
#=> [5,3,4,2]
#compsy you wrote without changing order, so it would be:
a = [0,1,2,3,3,4]
n = a.max
i = 0
a.each do |x|
break if x == n
i += 1
end
I use variable i as index, when x (which is the value beeing analized) is equals n we use break to stop the each method conserving the last value of i wich corresponds to the position of the max value at the array. Be aware that value of i is different by one of the natural position in the array, and tht is because in arrays the first element is 0 not 1.
I break the each because there is no need to keep checking all the other values of the array after we found the position of the value.
Let's take the following Array:
[1, 4, 5, 3, 1, 4, 6, 5, 4]
It has the following turning points (when rise changes to fall, or vice versa):
5 (at index 2)
1 (at index 4)
6 (at index 6)
To make task more general:
There is an Array a = [a1, a2, ...]
There is function p(x,y) -> z, where z is Comparable
How to get all elements ai ∈ a (0 < i < a.length-1) for which p(ai-1, ai) != p(ai, ai+1)
I would like to write something like:
a.detect_edges{|prev, n| prev >= n} # => [[5,2], [1, 4], [6,6]]
What's the most elegant way to get those turning points with their respective indexes? Here's my code with which I'm not satisfied from the aesthetic point of view:
class Array
def detect_edges(&blk)
return nil if self.length < 2
prev = blk.call(self[0], self[1])
result = []
self[0..-2].each_with_index do |elem, i|
current = blk.call(elem, self[i+1])
if current != prev
result.push [elem, i]
end
prev = current
end
result
end
end
[1, 4, 5, 3, 1, 4, 6, 5, 4]
.each_cons(3).with_index(1)
.reject{|(e1, e2, e3), i| (e1 <=> e2) == (e2 <=> e3)}
.map{|(e1, e2, e3), i| [e2, i]}
# => [[5, 2], [1, 4], [6, 6]]
Look ma, no map!
a = [1, 4, 5, 3, 1, 4, 6, 5, 4]
a[1..-2].each.with_index(1).reject { |e,i| (a[i-1]<=>e) == e<=>a[i+1] }
#=> [[5, 2], [1, 4], [6, 6]]
So you basically want the elements and their indices, where the element is the local max in a 1 index range:
arr.each.with_index.select { |element, index| element == arr[index.pred..index.next].max }
# => [[5, 2], [6, 6]]
Note, you have to handle the case for the first element or if elements are equal.
EDIT: for your updated version, you just have to check if the result of <=> has changed. Note that you will again have to check the case when elements are equal:
arr.each.with_index.to_a.tap(&:pop).drop(1).select do |element, index|
(arr[index.pred] <=> element) != (element <=> arr[index.next])
end # => [[5, 2], [1, 4], [6, 6]]
I don't see reason to get more fancy than:
class Array
def detect_edges
self.collect.with_index do |e, i|
next if i == 0 || i >= size-1
yield(self[i-1],e) != yield(e,self[i+1]) ? [e, i] : nil
end.compact
end
end
Note that when patching Array one should probably use refinements.