What the difference between Ruby + and concat for arrays? - 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.

Related

Ruby array: getting all the elements that have the max value while chaining methods (namely, without having a handle on the sorted array)

Suppose that you need to get all the elements that have the max value in an array.
A possible method would be to sort the array then use Enumerable#take_while:
array = [ 1, 3, 2, 3, 2, 3 ].sort {|a,b| b - a}
array.take_while { |e| e == array[0] }
#=> [3, 3, 3]
Now, when you are beautifully chaining methods and don't want to stop the chain just for storing the sorted array (which you'll need for referencing its first element in the take_while block), how would you do it?
I posted the question and an answer below for reference, but I probably missed better ways, so feel free to post your own method
Another way:
arr = [ 1, 3, 2, 3, 2, 3 ]
arr.sort {|a,b| b - a}.tap { |a| a.select! { |e| e == a.first } }
#=> [3, 3, 3]
Note that arr is not mutated.
ruby < 2.5
My original response to the question: sort.slice_when.first
[ 1, 3, 2, 3, 2, 3 ].sort {|a,b| b - a}.slice_when {|a,b| b != a}.first
#=> [3, 3, 3]
note: As slice_when returns an Enumerator, this solution won't walk through all the sorted array when chaining it with first. There is a more performant solution below tough.
ruby >= 2.5
Combining #engineersmnky and #Cary methods: then and max+select
[ 1, 3, 2, 3, 2, 3 ].then { |arr| mx = arr.max; arr.select { |elm| elm == mx } }
#=> [3, 3, 3]
You can try this
pry(main)> [ 1, 2, 2, 3, 3, 3 ].sort.slice_when {|a,b| b > a}.to_a.last
=> [3, 3, 3]
A bit similar of the last solution but also different.
Source https://ruby-doc.org/core-3.0.2/Enumerable.html#method-i-slice_when

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.

How to sort one array based on another array using Ruby

There are two arrays:
A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
B = [3, 4, 1, 5, 2, 6]
I want to sort B in a way that for all the elements of B that exists in A, sort the elements in the order that is in array A.
The desired sorted resulted would be
B #=> [1, 2, 3, 4, 5, 6]
I have tried to do
B = B.sort_by { |x| A.index }
but it does not work.
This question differs from the possible duplicates because it deals with presence of elements in the corresponding array and no hashes are present here.
It perfectly works:
▶ A = [1,3,2,6,4,5,7,8,9,10]
▶ B = [3,4,1,5,2,6]
▶ B.sort_by &A.method(:index)
#⇒ [1, 3, 2, 6, 4, 5]
If there could be elements in B that are not present in A, use this:
▶ B.sort_by { |e| A.index(e) || Float::INFINITY }
I would start by checking what elements from B exist in A :
B & A
and then sort it:
(B & A).sort_by { |e| A.index(e) }
First consider the case where every element of B is in A, as with the question's example:
A = [1,2,3,4,5,6,7,8,9,10]
B = [3,6,1,5,1,2,1,6]
One could write the following, which requires only a single pass through A (to construct g1) and a single pass through B.
g = A.each_with_object({}) { |n,h| h[n] = 1 }
#=> {1=>1, 2=>1, 3=>1, 4=>1, 5=>1, 6=>1, 7=>1, 8=>1, 9=>1, 10=>1}
B.each_with_object(g) { |n,h| h[n] += 1 }.flat_map { |k,v| [k]*(v-1) }
#=> [1, 1, 1, 2, 3, 5, 6, 6]
If there is no guarantee that all elements of B are in A, and any that are not are to be placed at the end of the sorted array, one could change the calculation of g slightly.
g = (A + (B-A)).each_with_object({}) { |n,h| h[n] = 1 }
This requires one more pass through A and through B.
Suppose, for example,
A = [2,3,4,6,7,8,9]
and B is unchanged. Then,
g = (A + (B-A)).each_with_object({}) { |n,h| h[n] = 1 }
#=> {2=>1, 3=>1, 4=>1, 6=>1, 7=>1, 8=>1, 9=>1, 1=>1, 5=>1}
B.each_with_object(g) { |n,h| h[n] += 1 }.flat_map { |k,v| [k]*(v-1) }
#=> [2, 3, 6, 6, 1, 1, 1, 5]
This solution demonstrates the value of a controversial change to hash properties that were made in Ruby v1.9: hashes would thereafter be guaranteed to maintain key-insertion order.
1 I expect one could write g = A.product([1]).to_h, but the doc Array#to_h does not guarantee that the keys in the hash returned will have the same order as they do in A.
You just missed x in A.index, so the query should be:
B = B.sort_by { |x| A.index(x) }

find letter within string and get positions - ruby

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.

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

Resources