Related
So let's say I have a list/array which contains only integers (positive and negative). I want to make a method that gets the sum of only the numbers that are the same and consecutive. The result should be an array as shown in examples below.
The 'same' condition meaning: 1 == 1
Examples:
[1,4,4,4,0,4,3,3,1] # => [1,12,0,4,6,1]
So as you can see sum of consecutives 1 is 1
sum of 3 consecutives 4 is 12
sum of 0... and sum of 2
consecutives 3 is 6 ...
[1,1,7,7,3] # => [2,14,3]
[-5,-5,7,7,12,0] # => [-10,14,12,0]
I tried this, but it doesn't have the consecutive condition, how would I add that condition?
def sum_consecutives(string)
string.map{|num| string.count(num) > 1 ? num * string.count(num) : num}
end
Thanks in advance :-)
You can split the original Array into chunks of consecutive numbers: Enumerable#chunk_while
a = [1,4,4,4,0,4,3,3,1]
a.chunk_while {|a,b| a == b }
#=> [[1], [4, 4, 4], [0], [4], [3, 3], [1]]
Then just map and sum the sub arrays: Enumerable#sum
a.chunk_while {|a,b| a == b }.map(&:sum)
#=> [1, 12, 0, 4, 6, 1]
Suppose I have an array a, and a boolean array b, I want to extract a fixed number of elements from the valid elements in each row of a. The valid elements are the ones indicated by b.
Here is an example:
a = np.arange(24).reshape(4,6)
b = np.array([[0,0,1,1,0,0],[0,1,0,1,0,1],[0,1,1,1,1,0],[0,0,0,0,1,1]]).astype(bool)
x = []
for i in range(a.shape[0]):
c = a[i,b[i]]
d = np.random.choice(c, 2)
x.append(d)
Here I used a for loop, which will be slow in case these arrays are big and high-dimensional. Is there a more efficient way to do this? Thanks.
Generate a random uniform [0, 1] matrix of shape a.
Multiply this matrix by the mask b to set invalid elements to zero.
Select the k maximum indices from each row (simulating an unbiased random k-sample from only the valid elements in this row).
(Optional) use these indices to get the elements.
a = np.arange(24).reshape(4,6)
b = np.array([[0,0,1,1,0,0],[0,1,0,1,0,1],[0,1,1,1,1,0],[0,0,0,0,1,1]])
k = 2
r = np.random.uniform(size=a.shape)
indices = np.argpartition(-r * b, k)[:,:k]
To get the elements from the indices:
>>> indices
array([[3, 2],
[5, 1],
[3, 2],
[4, 5]])
>>> a[np.arange(a.shape[0])[:,None], indices]
array([[ 3, 2],
[11, 7],
[15, 14],
[22, 23]])
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"]
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 need to split a range into X number of groups and I am having difficulties finding a way without using arrays since these ranges can be very large.
My current solution is to create an array out of the range and then call each_slice on it with some math to split the data into X number of groups more or less the same size depending on how many groups there are.
irb(main):026:0> a = (0..10)
=> 0..10
irb(main):027:0> a.each_slice( (a.size/3.0).round ).to_a
=> [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10]]
irb(main):028:0> a.each_slice( (a.size/5.0).round ).to_a
=> [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10]]
The problem with this is that when a range is excessively large the application will hang because of the computation it takes to split the array.
All I really need is an array in this format (taking the a.size/3.0 3 group example into account):
[0..3, 4..7, 8..10]
So I may iterate the array to pass them to the set_range method in the Net::HTTP library.
The ranges I am dealing with are as large or larger than 0..46000000 since I am dealing with file sizes in bytes.
Any help would be appreciated.
Like this?
def split_ranges(amount, max)
(0...amount).collect{|i| (i * max / amount)...((i+1) * max / amount)}
end
p split_ranges(3, 46000000)
Output:
[0...15333333, 15333333...30666666, 30666666...46000000]
Edit: (OP request)
def split_ranges(amount, max)
(0...amount).collect{|i| (i * (max + 1) / amount)..((i + 1) * (max + 1) / amount - 1)}
end
p split_ranges(3, 46000000)
Output:
[0..15333332, 15333333..30666666, 30666667..46000000]
class Range
def each_subrange(n)
return to_enum(:each_subrange, n) unless block_given?
range_size = size
range_begin = self.begin
n.times do |i|
yield range_begin + range_size * i / n .. range_begin + range_size * (i + 1) / n - 1
end
end
end
a = 0..46000000
# without a block
puts a.each_subrange(3).to_a
# with a block
a.each_subrange(3) do |r|
puts r
end
This will ensure that no range differs in size by more than one from the size of any other range:
def split_it(r, n)
return [r] if n == 1
last = r.first - 1 + (r.last-r.first+1)/n
[r.first..last].concat(split_it(last+1..r.last, n-1))
end
r = 0..46000000
split_it(r, 3)
#=> [0..15333332, 15333333..30666666, 30666667..46000000]
split_it(r, 3).map(&:size)
#=> [15333333, 15333334, 15333334]
split_it(r, 4)
#=> [0..11499999, 11500000..22999999, 23000000..34499999,
# 34500000..46000000]
split_it(r, 4).map(&:size)
# => [11500000, 11500000, 11500000, 11500001]
split_it(r, 5)
#=> [0..9199999, 9200000..18399999, 18400000..27599999,
# 27600000..36799999, 36800000..46000000]
split_it(r, 5).map(&:size)
#=> [9200000, 9200000, 9200000, 9200000, 9200001]