Related
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.
I am trying to sum the elements of an array by grouping by the first element.
ex:
[[1, 8], [3, 16], [1, 0], [1, 1], [1, 1]]
should give
[ {1 => 10}, {3 => 16} ]
It is summing the values in the original array where the first element was 1 and 3. The data structures in the end result don't matter, ex: an array of arrays, an array of hash or just a hash is fine.
Some tries:
k = [[1, 8], [3, 16], [1, 0], [1, 1], [1, 1]]
h = {}
k.inject({}) { |(a,b)| h[a] += b}
#=> undefined method `+' for nil:NilClass
data = [[1, 8], [3, 16], [1, 0], [1, 1], [1, 1]]
data.each_with_object({}) { |(k, v), res| res[k] ||= 0; res[k] += v }
gives
{1=>10, 3=>16}
there is also inject version although it's not so laconic:
data.inject({}) { |res, (k, v)| res[k] ||= 0; res[k] += v; res }
inject vs each_with_object
You're pretty close, some changes are needed on your code:
k.inject({}) do |hash, (a, b)|
if hash[a].nil?
hash[a] = b
else
hash[a] += b
end
hash
end
First of all, you don't need the h variable. #inject accepts an argument, often called the accumulator, which you can change it for each array element and then get as the return. Since you're already passing an empty hash to inject, you don't need the variable.
Next, you have to handle the case where the key doesn't yet exist on the hash, hence the if hash[a].nil?. In that case, we assign the value of b to the hash where the key is a. When the key exists in the hash, we can safely sum the value.
Another thing to notice is that you are using the wrong arguments of the block. When calling #inject, you first receive the accumulator (in this case, the hash), then the iteration element.
Documentation for #inject
k.group_by(&:first).transform_values {|v| v.map(&:last).sum }
You actually used the words "group by" in your question, but you never grouped the array in your code. Here, I first group the inner arrays by their first elements, ending up with:
{ 1 => [[1, 8], [1, 0], [1, 1], [1, 1]], 3 => [[3, 16]] }
Next, I only want the last element of all of the inner arrays, since I already know that the first is always going to be the key, so I use Hash#transform_values to map the two-element arrays to their last element. Lastly, I Enumerable#sum those numbers.
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.
I'd like to split an array into sub arrays of a specified length.
I know that .each_slice will chunk an array into equal length subarrays with the remainder leftover like so:
a = [1,2,3,4,5,6,7,8,9,10]
a.each_slice(3).to_a => [[1,2,3],[4,5,6],[7,8,9],[10]]
However, say I want the output like this:
=> [[1],[2,3],[4,5,6],[7,8,9,10]]
Is there a method in ruby for slicing an array into different specified lengths depending on the arguments you give it?
Try this
a = [1,2,3,4,5,6,7,8,9,10]
slices = [1,2,3,4].map { |n| a.shift(n) }
This slices the array into pieces
NB, this mutates the original array.
I cannot see how to improve on #akuhn's answer, but here are a couple of other methods that could be used.
a = [1,2,3,4,5,6,7,8,9,10,11]
slice_sizes = [1,2,3,4]
#1 Stab out slices
def variable_slice(a, slice_sizes)
last = 0
slice_sizes.each_with_object([]) do |n,arr|
arr << a[last,n]
last += n
end
end
variable_slice(a, slice_sizes)
#=> [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
#2 Use recursion
def variable_slice(a, slice_sizes)
return [] if slice_sizes.empty?
i, *rest = slice_sizes
[a.first(i)].concat variable_slice(a[i..-1], rest)
end
variable_slice(a, slice_sizes)
#=> [[1], [2, 3], [4, 5, 6], [7, 8, 9, 10]]
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]