It's a bit of a tricky situation I'm in, but I have an array like this:
const nums = [32, -3, 62, 8, 121, -231, 62, 13];
and need to replace them by their corresponding ascending index. The above example should yield:
[4, 1, 5, 2, 7, 0, 6, 3]
The solution I've come up with is this: TS Playground
const nums = [32, -3, 62, 8, 121, -231, 62, 13];
const numsCopy = nums.map(e => e);
// Basic sorting
for (let i = 0; i < numsCopy.length; i++) {
for (let j = 0; j < numsCopy.length; j++) {
if (numsCopy[i] < numsCopy[j]) {
let t = numsCopy[j];
numsCopy[j] = numsCopy[i];
numsCopy[i] = t;
}
}
}
for (let i = 0; i < numsCopy.length; i++) {
let sortedValue = numsCopy[i];
nums[nums.indexOf(sortedValue)] = i;
}
Problems arise however when I change nums to include a value nums.length > n >= 0. The call nums.indexOf(...) may return a faulty result, as it may have already sorted an index, even though it exists somewhere in the array.
If you replace nums with these values, -231 will have an index of 2 for some reason...
const nums = [32, -3, 62, 7, 121, -231, 62, 13, 0];
> [5, 1, 6, 3, 8, 2, 7, 4, 0]
Is there a better approach to this problem, or a fix to my solution?
You could sort the indices by the value and create a new array with index values a sorted positions.
to get the wanted result call the sorting function again and you get the indices sorted by the index order.
const
sort = array => [...array.keys()].sort((a, b) => array[a] - array[b]),
fn = array => sort(sort(array));
console.log(...fn([32, -3, 62, 8, 121, -231, 62, 13])); // 4 1 5 2 7 0 6 3
console.log(...fn([-1, 3, 1, 0, 2, 9, -2, 7])); // 1 5 3 2 4 7 0 6
Copy the array, sort its values, get indexOf, and null the value in the sorted copy:
const sortIndicesByValue = array => {
const sorted = [...array].sort((a, b) => a - b);
return array.map(e => {
const i = sorted.indexOf(e);
sorted[i] = null;
return i;
})
}
console.log(...sortIndicesByValue([32, -3, 62, 8, 121, -231, 62, 13]));
console.log(...sortIndicesByValue([-1, 3, 0, 0, 2, 9, -2, 7]));
My code only prints the largest one, but I need to have the 4 largest numbers displayed and to be summed up.
let sizes = [3, 6, 2, 56, 32, 5, 89, 32];
let largest = sizes[0];
for (let x = 0; x < sizes.length; x++) {
if (largest < sizes[x] ) {
largest = sizes[x];
}
}
console.log(largest);;
Please use this code.
let sizes = [3, 6, 2, 56, 32, 5, 89, 32];
sizes.sort(function(a, b) { return b - a });
console.log(sizes.slice(0, 4).join(' '));
console.log(sizes.slice(0, 4).reduce((total, num) => total + num));
One method you can use is to sort your elements first, and then simply print out the first 4 elements:
sizes(function(a, b) {
return a - b;
});
console.log(sizes[0])
console.log(sizes[1])
console.log(sizes[2])
console.log(sizes[3])
There are easy solutions (sorting the array O(n log(n))) and there are performant solutions (O(n)) like:
let sizes = [3, 6, 2, 56, 32, 5, 89, 32];
let largest = sizes.slice(0, 4).sort((lhs, rhs) => rhs - lhs);
for (let x = 4; x < sizes.length; x++) {
if (largest[3] < sizes[x]) {
largest[3] = sizes[x];
largest = largest.sort((lhs, rhs) => rhs - lhs);
}
}
console.log(largest);
you can do that...
const
sizes = [3, 6, 2, 56, 32, 5, 89, 32]
, sumLargest = sizes.reduce((lg,x)=>
{
if (lg.length < 4) lg.push(x)
else
{
let min = Math.min(...lg)
if (x>min) lg[lg.indexOf(min)] = x
}
return lg
},[]).reduce((a,b)=>a+b,0)
console.log('sumLargest = ', JSON.stringify(sumLargest ))
I need a sliding window over an Array in JavaScript.
For example, a sliding window of size 3 over [1,2,3,4,5,6,7,8,9] shall compute the sequence [[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[7,8,9]].
The following is my attempt, because I couldn't find a readymade solution:
function window(a, sz) {
return a.map((_, i, ary) => ary.slice(i, i + sz)).slice(0, -sz + 1);
}
It returns an array of windows that can be mapped over to get the individual windows.
What is a better solution?
Array#reduce
A reasonable alternative to avoid .map followed by .slice() is to use .reduce() to generate the windows:
function toWindows(inputArray, size) {
return inputArray
.reduce((acc, _, index, arr) => {
if (index+size > arr.length) {
//we've reached the maximum number of windows, so don't add any more
return acc;
}
//add a new window of [currentItem, maxWindowSizeItem)
return acc.concat(
//wrap in extra array, otherwise .concat flattens it
[arr.slice(index, index+size)]
);
}, [])
}
const input = [1, 2, 3, 4, 5, 6, 7, 8, 9];
//JSON.stringify to produce more compact result in the console
console.log(JSON.stringify(toWindows(input, 2)));
console.log(JSON.stringify(toWindows(input, 3)));
console.log(JSON.stringify(toWindows(input, 4)));
console.log(JSON.stringify(toWindows(input, 9)));
console.log(JSON.stringify(toWindows(input, 10)));
//somewhat more realistic usage:
//find the maximimum odd sum when adding together three numbers at a time
const output = toWindows([ 3, 9, 1, 2, 5, 4, 7, 6, 8 ], 3)
.map(window => window.reduce((a,b) => a+b)) //sum
.filter(x => x%2 === 1) //get odd
.reduce((highest, current) => Math.max(highest, current), -Infinity) //find highest
console.log(output)
This can then be shortened, if needed:
function toWindows(inputArray, size) {
return inputArray
.reduce(
(acc, _, index, arr) => (index+size > arr.length) ? acc : acc.concat([arr.slice(index, index+size)]),
[]
)
}
const input = [1, 2, 3, 4, 5, 6, 7, 8, 9];
//JSON.stringify to produce more compact result in the console
console.log(JSON.stringify(toWindows(input, 2)));
console.log(JSON.stringify(toWindows(input, 3)));
console.log(JSON.stringify(toWindows(input, 4)));
console.log(JSON.stringify(toWindows(input, 9)));
console.log(JSON.stringify(toWindows(input, 10)));
//somewhat more realistic usage:
//find the maximimum odd sum when adding together three numbers at a time
const output = toWindows([ 3, 9, 1, 2, 5, 4, 7, 6, 8 ], 3)
.map(window => window.reduce((a,b) => a+b)) //sum
.filter(x => x%2 === 1) //get odd
.reduce((highest, current) => Math.max(highest, current), -Infinity) //find highest
console.log(output);
Array.from
The approach can be simplified using Array.from to generate an array with the appropriate length first and then populate it with the generated windows:
function toWindows(inputArray, size) {
return Array.from(
{length: inputArray.length - (size - 1)}, //get the appropriate length
(_, index) => inputArray.slice(index, index+size) //create the windows
)
}
const input = [1, 2, 3, 4, 5, 6, 7, 8, 9];
//JSON.stringify to produce more compact result in the console
console.log(JSON.stringify(toWindows(input, 2)));
console.log(JSON.stringify(toWindows(input, 3)));
console.log(JSON.stringify(toWindows(input, 4)));
console.log(JSON.stringify(toWindows(input, 9)));
console.log(JSON.stringify(toWindows(input, 10)));
//somewhat more realistic usage:
//find the maximimum odd sum when adding together three numbers at a time
const output = toWindows([ 3, 9, 1, 2, 5, 4, 7, 6, 8 ], 3)
.map(window => window.reduce((a,b) => a+b)) //sum
.filter(x => x%2 === 1) //get odd
.reduce((highest, current) => Math.max(highest, current), -Infinity) //find highest
console.log(output)
Generator
Another alternative is to use a generator function, instead of pre-computing all windows. This can be useful for more lazy evaluation with a sliding window approach. You can still compute all the windows using Array.from, if needed:
function* windowGenerator(inputArray, size) {
for(let index = 0; index+size <= inputArray.length; index++) {
yield inputArray.slice(index, index+size);
}
}
function toWindows(inputArray, size) {
//compute the entire sequence of windows into an array
return Array.from(windowGenerator(inputArray, size))
}
const input = [1, 2, 3, 4, 5, 6, 7, 8, 9];
//JSON.stringify to produce more compact result in the console
console.log(JSON.stringify(toWindows(input, 2)));
console.log(JSON.stringify(toWindows(input, 3)));
console.log(JSON.stringify(toWindows(input, 4)));
console.log(JSON.stringify(toWindows(input, 9)));
console.log(JSON.stringify(toWindows(input, 10)));
//somewhat more realistic usage:
//find the sum closest to a target number when adding three numbers at a time
const veryLargeInput = [17, 95, 27, 30, 32, 38, 37, 67, 53, 46, 33, 36, 79, 14, 19, 25, 3, 54, 98, 11, 68, 96, 89, 71, 34, 31, 28, 13, 99, 10, 15, 84, 48, 29, 74, 78, 8, 90, 50, 49, 59, 18, 12, 40, 22, 80, 42, 21, 73, 43, 70, 100, 1, 44, 56, 5, 6, 75, 51, 64, 58, 85, 91, 83, 24, 20, 72, 26, 88, 66, 77, 60, 81, 35, 69, 93, 86, 4, 92, 9, 39, 76, 41, 37, 63, 45, 61, 97, 2, 16, 57, 65, 87, 94, 52, 82, 62, 55, 7, 23];
const targetNumber = 100;
console.log(`-- finding the closest number to ${targetNumber}`)
const iterator = windowGenerator(veryLargeInput, 3);
let closest = -1;
for (const win of iterator) {
const sum = win.reduce((a, b) => a+b);
const difference = Math.abs(targetNumber - sum);
const oldDifference = Math.abs(targetNumber - closest);
console.log(
`--- evaluating: ${JSON.stringify(win)}
sum: ${sum},
difference with ${targetNumber}: ${difference}`
);
if (difference < oldDifference) {
console.log(`---- ${sum} is currently the closest`);
closest = sum;
if (difference === 0) {
console.log("----- prematurely stopping - we've found the closest number")
break;
}
}
}
console.log(`-- closest sum is: ${closest}`)
Have you considered going recursive?
l is the size of each window
xs is your list
i is the number of iterations we need to make which is xs.length - l
out contains the result
A slice can be obtained with xs.slice(i, i + l). At each recursion i is incremented until i gets to a point where the next slice would contain less than l elements.
const windows = (l, xs, i = 0, out = []) =>
i > xs.length - l
? out
: windows(l, xs, i + 1, [...out, xs.slice(i, i + l)]);
console.log(windows(3, [1,2,3,4,5,6,7,8,9]));
There is also a non-recursive solution with flatMap.
With flatMap you can return an array at each iteration, it will be flattened in the end result:
const duplicate = xs => xs.flatMap(x => [x, x]);
duplicate([1, 2]);
//=> [1, 1, 2, 2]
So you can return your slices (wrapped in []) until i gets over the limit which is xs.length - l:
const windows = (l, xs) =>
xs.flatMap((_, i) =>
i <= xs.length - l
? [xs.slice(i, i + l)]
: []);
console.log(windows(3, [1,2,3,4,5,6,7,8,9]))
Note that in some libraries like ramda.js, this is called aperture:
Returns a new list, composed of n-tuples of consecutive elements. If n is greater than the length of the list, an empty list is returned.
aperture(3, [1,2,3,4,5,6,7,8,9]);
//=> [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6], [5, 6, 7], [6, 7, 8], [7, 8, 9]]
As you can see a few people had the same question before:
how I can solve aperture function in javascript?
How to create windowed slice of array in javascript?
Adding to the native JavaScript objects through their prototype is not a good idea. This can break things in unexpected ways and will cause a lot of frustration for you and anyone else using your code. It is better to just create your own function in this case.
To get the functionality you want, you could simply pass the array to your function and then access it from there. Make the method calls you want on the array from your function. Following the principle of KISS, there's no need for anything more fancy here.
Also, remember that Array.map is called for each element of the array. That's not really what you need here. If the goal is to get a sliding window of size n, and you want each of the windows to be added to a new array, you could use a function like this:
const myArray = [1, 2, 3, 4, 5, 6, 7, 8];
const slicingWindows = (arr, size) => {
if (size > arr.length) {
return arr;
}
let result = [];
let lastWindow = arr.length - size;
for (let i = 0; i <= lastWindow; i += 1) {
result.push(arr.slice(i, i + size));
}
return result;
};
So here, we will get an array of windows, which are also arrays. Calling console.log(slicingWindows(a,3)), gives this output:
[1, 2, 3]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
Using JS ES6, you can do the following:
class SlidingWindow{
constructor(windowSize) {
this.deque = []; // for storing the indexex of the 'k' elements in the input
this.windowSize = windowSize;
}
compute(input){
let output = [];
if(!input || input.length === 0) {
return [];
}
if(input.length < this.windowSize) {
return input;
}
for(let i=0; i < input.length; i++) {
//if the index in the first element of the this.deque is out of bound (i.e. idx <= i-this.windowSize) then remove it
if(this.deque.length > 0 && this.deque[0] === i-this.windowSize) {
this.deque.shift(); //remove the first element
}
this.deque.push(i)
if(i+1 >= this.windowSize) {
output.push(this.deque.map(idx => input[idx]));
}
}
return output;
}
}
//Here is how to use it:
let slidingWindow = new SlidingWindow(3);
console.log('computed sliding windows: '+JSON.stringify(slidingWindow.compute([1,2,3,4,5,6,7,8,9])));
To compute the maximum of each sliding window, you can customise the above code as follows:
class SlidingWindow{
constructor(windowSize) {
this.deque = []; // for storing the indexex of the 'k' elements in the input
this.windowSize = windowSize;
}
customCompute(input, processWindow, addOutput) {
let output = [];
if(!input || input.length === 0) {
return [];
}
if(input.length < this.windowSize) {
return input;
}
for(let i=0; i < input.length; i++) {
//if the index in the first element of the this.deque is out of bound (i.e. idx <= i-this.windowSize) then remove it
if(this.deque.length > 0 && this.deque[0] === i-this.windowSize) {
this.deque.shift(); //remove the first element
}
processWindow(this.deque, input[i], input)
this.deque.push(i)
if(i+1 >= this.windowSize) {
output.push(addOutput(this.deque, input));
}
}
this.deque = [];
return output;
}
}
let slidingWindow = new SlidingWindow(3);
console.log('computed sliding windows: '+JSON.stringify(slidingWindow.compute([1,2,3,4,5,6,7,8,9])));
function processWindow(deque, currentElement, input){
while(deque.length > 0 && currentElement > input[deque[deque.length -1]]) {
deque.pop(); //remove the last element
}
};
console.log('computed sliding windows maximum: '+JSON.stringify(slidingWindow.customCompute([1,3,-1,-3,5,3,6,7], processWindow, (deque, input) => input[deque[0]])));
Simple while-loop solution
function windowArray(array, windowSize) {
return array.map((value, index) => {
const windowedArray = [];
while (array[index] && windowedArray.length < windowSize) {
windowedArray.push(array[index]);
index++;
}
return windowedArray;
});
};
const array = [1, 1, 1, 2, 2, 2, 3, 3, 3]
const windowValue = 3;
const windowedArray = windowArray(array, windowValue)
const filteredWindowedArray = windowedArray.filter(group => group.length === windowValue);
console.log("INPUT ARRAY", JSON.stringify(array))
console.log("WINDOWED ARRAY", JSON.stringify(windowedArray));
console.log("FILTERED WINDOWED ARRAY", JSON.stringify(filteredWindowedArray));