find letter within string and get positions - ruby - arrays

The goal is to find if the letter 'a' is within a string and add the three positions away from the 'a' occurrences within the string.
So if the string was 'gabe' my list would look like this list = [2,3,4].
If the string is 'gabba' then list = [2,3,4,5,6,7]
my current code seems to be giving me errors
def nearby_az(string)
list = []
for i in 0..(string.length)
if string[i] == 'a'
list.push(i+1)
list.push(i+2)
list.push(i+3)
next
end
return list
end
I get the following error:
(repl):11: syntax error, unexpected end-of-input, expecting keyword_end
can you see where my logic falls off?

The error comes from the fact you haven't closed the range block with an end. But there are other points. I suggest you to try something like this:
def nearby_az(str)
list = Array.new
pos = -1
str.each_char do |c|
pos = pos + 1
if (c == 'a') then
list.push(pos+1)
list.push(pos+2)
list.push(pos+3)
end
end
list
end
or yet better
def nearby_az(str)
list = Array.new
nstr = str.each_char.to_a
nstr.each_index do |i|
if (nstr[i] == 'a') then
list.push(i+1)
list.push(i+2)
list.push(i+3)
end
end
list
end
(This ways you don't even have to create an artificial index, by using the natural index of the nstr array)
With this code, if you do
puts nearby_az("asdfgaqwer")
the result will be [ 1, 2, 3, 6, 7, 8], as desired.
Remember you don't need a return in Ruby. The value of the last expression calculated in a method is returned by default to the method caller.
Of course you may continue using your way, doing this:
def nearby_az(string)
list = []
for i in 0..(string.length)
if string[i] == 'a'
list.push(i+1)
list.push(i+2)
list.push(i+3)
end
end
list
end
And it will give you the same result, although I think the first code is a bit easier to read.

Here is a more Ruby-like way you could do that.
Code
def indices(str)
str.each_char.
with_index.
select { |c,_| c=='a' }.
flat_map { |_,i| (i..i+2).to_a }
end
Examples
indices "gabe"
#=> [1, 2, 3]
indices "gabba"
#=> [1, 2, 3, 4, 5, 6]
indices "abbadabbadoo"
#=> [0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10]
Explanation
Suppose
str = "gagga"
Then the steps are as follows:
enum0 = str.each_char
#=> #<Enumerator: "gagga":each_char>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: "gagga":each_char>:with_index>
Carefully examine the above return value. You can think of enum1 as a compound enumerator (though Ruby has no such concept--enum1 is simply an enumerator). We can see the elements of enum1 that will be passed to select by converting enum1 to an array:
enum1.to_a
#=> [["g", 0], ["a", 1], ["g", 2], ["g", 3], ["a", 4]]
Continuing,
a = enum1.select { |c,_| c=='a' }
#=> [["a", 1], ["a", 4]]
a.flat_map { |e,i| (i..i+2).to_a }
#=> [1, 2, 3, 4, 5, 6]
Enumerable#flat_map's block variables are e and i. When the first element of a (["a", 1]) is passed to the block, the block variables are assigned using parallel assignment:
e, i = ["a", 1]
#=> ["a", 1]
e #=> "a"
i #=> 1
and the block calculation is performed:
(i..i+2).to_a
#=> (1..3).to_a
#=> [1,2,3]
Note that flat_map is equivalent to:
b = a.map { |e,i| (i..i+2).to_a }
#=> [[1, 2, 3], [4, 5, 6]]
c = b.flatten(1)
#=> [1, 2, 3, 4, 5, 6]
One last thing: flat_map's first block variable, e, is not used in the block calculation. As is common in such situations, _ (a legitimate local variable) is used instead for that variable. That informs the reader that that block variable is not used, and may also reduce the chances of introducing errors within the block.

Related

better multiple array sort, based on first array

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"]

What the difference between Ruby + and concat for arrays?

I've been trying to collect arrays with digits into one array. If I try to use + it returns emty array as output. Using concat returns expected array of digits. How does it work and what the main difference between these Ruby methods?
0.step.with_object([]) do |index, output|
output + [index]
break output if index == 100
do # returns empty array
0.step.with_object([]) do |index, output|
output.concat [index]
break output if index == 100
end # returns an array contains digits from 0 to 100
Unlike Enumerable#reduce, Enumerable#each_with_object passes the same object through reducing process.
Array#+ creates a new instance, leaving the original object unrouched.
Array#concat mutates the original object.
With reduce the result will be the same:
0.step.reduce([]) do |acc, index|
break acc if index > 100
acc + [index]
end
Let's create two arrays:
a = [1, 2]
b = [3, 4]
Like all objects, these arrays have unique object ids:
a.object_id #=> 48242540181360
b.object_id #=> 48242540299680
Now let's add them together:
c = a + b #=> [1, 2, 3, 4]
This creates a new object (held by the variable c):
c.object_id #=> 48242540315060
and leaves (the objects held by) a and b (and their object ids) unchanged:
a #=> [1, 2]
b #=> [3, 4]
Now, let's write:
a += b #=> [1, 2, 3, 4]
which Ruby changes to:
a = a + b
when it compiles the code. We obtain:
a #=> [1, 2, 3, 4]
a.object_id #=> 48242541482040
The variable a now holds a new object that equals the previous value of a plus b.
Now let's concatenate b with (the new value of) a:
a.concat(b) #=> [1, 2, 3, 4, 3, 4]
This changes (mutates) a, but of course does not change a's object id:
a #=> [1, 2, 3, 4, 3, 4]
a.object_id #=> 48242541482040
Lastly, we could replace a's value with c, without affecting a's object id:
a.replace(c) #=> [1, 2, 3, 4]
a #=> [1, 2, 3, 4]
a.object_id #=> 48242541482040
See Array#+, Array#concat and Array#replace.

How to find indices of max n elements in array in stable order

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.

Passing an array into a block

I am a little lost on the block below.
def sort_string(string)
string.split(" ").sort{|a,b| a.length <=> b.length}.join(" ")
end
The array is sorted based on the length (least to greatest). My confusion comes from what the variable b in the block of code is.
If I split the string "example string here" into an array and then sort it, how is [example],[string],[here] passed into the block {|a,b| a.length <=> b.length}? I don't understand how the elements of the array are passed into the code and then compared.
When using sort, Ruby passes two objects into the block. They are to be compared, either using the built-in <=> method, or by some machination you devise that determines whether one is less-than (-1), equal-to (0), or greater-than (1) the other. So, a is one and b is the other.
Meditate on this:
[1, 2, 3, 4].shuffle # => [4, 1, 3, 2]
.sort { |i, j|
[i, j] # => [4, 1], [4, 3], [1, 3], [4, 2], [3, 2], [1, 2]
i <=> j # => 1, 1, -1, 1, 1, -1
}
# => [1, 2, 3, 4]
Remember what <=> does and compare the values returned for the i <=> j comparison each time through the loop.
But of course you knew this from reading the documentation for sort:
http://ruby-doc.org/core-2.3.0/Enumerable.html#method-i-sort
http://ruby-doc.org/core-2.3.0/Array.html#method-i-sort

Ruby: The most elegant way to detect turning points in Array

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.

Resources