Unintuitive behaviour of array.map - arrays

So this behaviour seems to be logical
> array = (1..4).to_a
=> [1, 2, 3, 4]
> array.map {|a| [array.delete(array.first), array.delete(array.last)]}
=> [[1, 4], [2, 3]]
> array
=> []
But then when we increase the range it doesn't work:
> array = (1..6).to_a
=> [1, 2, 3, 4, 5, 6]
> array.map {|a| [array.delete(array.first), array.delete(array.last)]}
=> [[1, 6], [2, 5]]
> array
=> [3, 4]
And it does the same weird behaviour with odd numbers:
> array = (1..9).to_a
=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
> array.map {|a| [array.delete(array.first), array.delete(array.last)]}
=> [[1, 9], [2, 8], [3, 7]]
> array
=> [4, 5, 6]
This was not what we expected, why does it behave this way?

This is a guess since I am not sure if this is the exact case but I can't put this as a comment.
So lets go step by step:
you defined an array:
> array = (1..6).to_a
=> [1, 2, 3, 4, 5, 6]
Now defined an iterator:
array.map {|a| [array.delete(array.first), array.delete(array.last)]}
so for first iteration it will take array[0] as the value for for variable a and do the operation and hence deleting first and last element of the array.
After first iteration array becomes [2,3,4,5].
For second iteration it will take array[1] as the value for for variable a and do the same as in first iteration and delete first and last element. Now array is [3,4].
Now for 3rd iteration, well it would not have tried for 3rd iteration if your original array was only size of 2 i.e like [2,3] but since you modified your original array during iteration so it will attempt 3rd iteration since original array had length 6.
For 3rd iteration it needs array's third element i.e array[2] but your current array does not have that index so it returns the remaining array without any other operation.
It is really confusing to modify array while being iterated.

Related

Get first element of each array in a parent array

I have an array of arrays in TypesScript and I want to splice it to get the first element of each sub-array. This isn't that hard of a task but I want a concise way of doing it.
Here is a Python example of what I want in TypeScript:
l1 = [[1,2],[3,4],[5,6]]
l2 = [i[0] for i in l1]
print(l2) # [1, 3, 5]
You would use map to transform each sub-array, passing it a function that takes a sub-array and returns its first element:
const input = [[1, 2], [3, 4], [5, 6]]
const output = input.map(subarray => subarray[0])
console.log(output) // => [1, 3, 5]

Can someone explain in detail how mapping this array works step by step

The original array: [ [3, 5], [9, 6], [3,1] ]
1) Make the first number in each mini array the smaller number and the second number in each mini array the bigger number
- [ [3, 5], [9, 6], [3,1] ] -> [[ 3,5], [6,9], [1,3]]
2) Sort the mini arrays by first number.
- [ [3,5], [6,9], [1,3] ] -> [ [1,3], [3,5], [6,9] ]
So [ [3, 5], [9, 6], [3,1] ] -> [ [1,3], [3,5], [6,9] ] by the end of the sorting transformation.
Can someone explain in a step by step, detailed, clear, concise way how to use array.map() to make this happen?
Use map to make the nested array numbers placed in increasing order, then apply sort based on first number in the nested array.
const arr = [
[3, 5],
[9, 6],
[3, 1]
];
const sortedArr = arr
.map(([num1, num2]) => num2 < num1 ? [num2, num1] : [num1, num2])
.sort(([arr1num1], [arr2num1]) => arr1num1 - arr2num1);
console.log(sortedArr);
-- Edit--
Result of map
[[3, 5], [6, 9], [1, 3]];
In the sort function, I'am doing array destructuring (see - https://javascript.info/destructuring-assignment). Basically, I'am only extracting the first element from array and storing in separate variable. In our case, arr1num1 and arr2num1.
Example-
const arr = [3, 5];
// Extracting the first element from array and saving in variable - arr1num1.
// I do not need second array element 5, so i did not extracted the second array element
const [arr1num1] = arr; // arr1num1 will have now value 3
console.log(arr1num1);
// If second array element is also required, then the above statement will become const [num1, num2] = arr; where num1 -> 3 and num2 ->5
Alternative, to above would be to directly access array elements using there index.
const arr = [
[3, 5],
[9, 6],
[3, 1]
];
const sortedArr = arr
.map(([num1, num2]) => num2 < num1 ? [num2, num1] : [num1, num2])
.sort((arr1, arr2) => arr1[0] - arr2[0]);
console.log(sortedArr);
To know more about sort, see - Sorting
How sorting works,
In first iteration, swapping of [3, 5] & [6, 9] will happen based on sort condition.
arr1num1 -> 3
arr2num1 -> 6
The statement arr1num1 - arr2num1 will check if 3 is greater than 6. If yes, it will swap the arrays [3, 5] and [6, 9]. Since, 3 is not greater than 9, so no swapping will take place.
Second Iteration, swapping of [6, 9] and [1, 3] will take place based on condition.
arr1num1 -> 6
arr1num2 -> 1
Since, 6 > than 1, swapping will take place.
Before Swapping array will be- `[[3, 5], [6, 9], [1, 3]]`;
After Swapping array will be - `[[3, 5], [1, 3], [6, 9]]`;
This process will continue, until your array gets sorted.

Ruby save modified array in a variable without it changing the original array

I'd like to save in two variables the values of an array excluding the first and last elements.
For example:
prices = [9, 3, 5, 2, 1]
The elements I need are:
prices_excl_first = [3, 5, 2, 1]
prices_excl_last = [9, 3, 5, 2]
I figured out how to remove an element from an array a few ways, including slicing off the value by passing its index to the slice method like so:
first_price = prices.slice(0)
last_price = prices.slice(-1)
We could then save the modified arrays into variables:
array_except_first_price = prices.delete(first_price) #=> [3, 5, 2, 1]
array_except_last_index = prices.delete(last_price) #=> [3, 5, 2]
There are two problems with this:
array_except_last_index doesn't contain the first element now
I still need access to the full, original array prices later
So essentially, how can I just temporarily modify the elements in the array when necessary in the problem?
Slicing and dropping elements from array permanently affect the array.
Ruby has first and last to copy just the first and last elements.
Ask for the first and last prices.size-1 elements.
prices = [9, 3, 5, 2, 1]
except_first = prices.last(prices.size-1)
except_last = prices.first(prices.size-1)
#Schwern's answer is probably the best you can get. Here's the second best:
prices = [9, 3, 5, 2, 1]
prices[1..-1] # => [3, 5, 2, 1]
prices[0..-2] # => [9, 3, 5, 2]
Or drop/take (which more closely map to the wording of your question).
prices.drop(1) # => [3, 5, 2, 1]
prices.take(prices.size-1) # => [9, 3, 5, 2]
You could use each_cons:
a, b = prices.each_cons(prices.size - 1).to_a
a #=> [9, 3, 5, 2]
b #=> [3, 5, 2, 1]
Splat it.
*a, d = prices
c, *b = prices
a #=> [9, 3, 5, 2]
b #=> [3, 5, 2, 1]
You can use dup to duplicate the array before performing destructive operations.
prices = [9, 3, 5, 2, 1]
except_first = prices.dup
except_first.delete_at 0
except_last = prices.dup
except_last.delete_at -1
This does end up duplicating the array a couple of times. If you're dealing with large arrays, this may be a problem.

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.

ruby array subrange when that range is a variable

Is it possible to apply a sub range to an array in ruby like this:
> array = [4, 3, 2, 1]
> array[0...2]
=> [4, 3]
if the [0...2] is stored in a variable? I can't seem to get a syntax to give me what I want. What replaces the <?> in the following, if anything?
> array = [4, 3, 2, 1]
> range = [0...2]
> array<?>
=> [4, 3]
Yes, sure! Do this way:
array = [4, 3, 2, 1]
exclusive_range = [0...2] # Will get 0th and 1st element of the array
inclusive_range = [0..2] # Will get 0th, 1st and 2nd element of the array
array[exclusive_range.first]
# => [4, 3]
array[inclusive_range.first]
# => [4, 3, 2]
If you want to avoid .first call, you can put your range in a variable (Not in an array):
range = 0...2
array[range]
# => [4, 3]
Note that (0..2).size #=> 3. If you want to return [4,3] you want:
range = 0..1
You could use it like this:
array[range] #=> [4, 3]
or like this:
array.values_at *range #=> [4, 3]

Resources