Sliding window over Array in JavaScript - javascript

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));

Related

Sorting an array by their ascending value within the array in JS

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]));

How to find three largest numbers in an array?

Hello I want to find the three largest numbers in an array by ORDER.
I am confused how I should implement the last logic where I have to shift the indexes of the result array based on if the the current number in the result array is greater or less than the loop of the array items.
function findThreeLargestNumbers(array) {
let result = [null, null, null];
for (let i = 0; i < array.length; i++) {
if (!result[2] || result[i] > result[2]) {
for (let j = 0; i <= 2; i++) {
if (j === 2) {
result[j] = array[i]
} else {
result[j] = array[i + 1]
}
}
}
}
return result
}
console.log(findThreeLargestNumbers([141, 1, 17, -7, -17, -27, 18, 541, 8, 7, 7]));
You can simply sort it in ascending order and use slice to get the last 3 elements as:
1) When you want result in last 3 largest in ascending order [18, 141, 541]
function findThreeLargestNumbers(array) {
return [...array].sort((a, b) => a - b).slice(-3);
}
// [18, 141, 541]
console.log(findThreeLargestNumbers([141, 1, 17, -7, -17, -27, 18, 541, 8, 7, 7]));
2) When you want result in last 3 largest in descending order [541, 141, 18]
function findThreeLargestNumbers(array) {
return [...array].sort((a, b) => b - a).slice(0, 3);
}
console.log(findThreeLargestNumbers([141, 1, 17, -7, -17, -27, 18, 541, 8, 7, 7]));

removing duplicate ids from array of arrays from a specific index

I want to remove duplicate ids from pureIDs[2]. so the index here is 2. the only condition is the arrays I want to search for duplicates are the ones with lower indices so I want to search 0 and 1.
If we want to pureIDs[3] then we should search 0 , 1 , 2 (the lower indices)
My attempt is a total failure Here:
The desired result is : [22, 9] here.
const pureIDs = [
[0, 1, 23, 5, 11],
[2, 15, 23, 25, 10],
[2, 10, 22, 9, 11], // Modify this index (2)
[20, 24],
];
// I give the index here
const ids = getModifiedIDs(pureIDs[2], 2);
console.log(ids); // This should result in : [22, 9]
function getModifiedIDs(ids, from) {
let sets = [];
for(let b = from - 1; b > -1; b--){
console.log(b)
set = ids.filter((el) => {
return !pureIDs[b].includes(el);
});
sets.push(set)
}
return sets;
}
Just a slight modification gives you the result needed.
Change your function to:
function getModifiedIDs(ids, from) {
const set = ids.filter((el) => {
for(let b = from - 1; b > -1; b--){
if (pureIDs[b].includes(el)) {
return false;
}
}
return true;
});
return set;
}
Build a set from the arrays with deletable indexes. Than you get with Array#filter and Set#has your result.
function getModifiedIDs(ids, from) {
let dels = [];
for (i=0; i<from-1; i++)
dels = dels.concat(pureIDs[i]);
let delSet = new Set(dels);
return ids.filter(id => !delSet.has(id) );
}
const pureIDs = [
[0, 1, 23, 5, 11],
[2, 15, 23, 25, 10],
[2, 10, 22, 9, 11], // Modify this index (2)
[20, 24],
];
console.log(getModifiedIDs(pureIDs[2], 3));

Codewars javascript task - help to understand

I am taking an excercise on codewars:
Given a list of integers and a single sum value, return the first two
values (parse from the left please) in order of appearance that add up
to form the sum.
Example:
sum_pairs([10, 5, 2, 3, 7, 5], 10)
# ^-----------^ 5 + 5 = 10, indices: 1, 5
# ^--^ 3 + 7 = 10, indices: 3, 4 *
# * entire pair is earlier, and therefore is the correct answer
== [3, 7]
What do you think entire pair is earlier means? IMO if the sum of it's indexes is smallest. Now based on this assumption I made my solution and one test fails:
var sum_pairs=function(ints, s){
let i = 0;
let pair = [0, 0];
let ind = [100, 100]
let found = false;
function loop(i) {
if (i > ints.length) return pair;
ints.slice(i).forEach((curr, idx) => {
ints.slice(i+1).some((num, i) => {
let sum = curr + num;
let prevIndicies = ind[0] + ind[1];
if(sum === s && prevIndicies > idx + i) {
ind = [idx, i];
pair = [curr, num];
found = true;
return true;
}
})
})
i += 1;
loop(i)
}
loop(0)
if (found) {
return pair
}
return undefined;
}
console.log(sum_pairs([1,4,8,7,3,15], 8))
Test returns error that [1, 7] is expected.
I'm pretty sure what it means is they want the second element to be as leftward in the list as possible. For example, for
l5= [10, 5, 2, 3, 7, 5];
when trying to find a sum of 10, the desired output is
[3, 7]
[10, 5, 2, 3, 7, 5];
^ ^
instead of
[5, 5]
[10, 5, 2, 3, 7, 5];
^ ^
because the last element in [3, 7], the 7, came before the second 5.
This code seems to pass all test cases - iterate in a triangular fashion, starting at indicies [0, 1], [0, 2], [1, 2], [0, 3], [1, 3], [2, 3], ...:
const sum_pairs = function(ints, s){
const { length } = ints;
for (let i = 1; i < length; i++) {
for (let j = 0; j < i; j++) {
if (ints[i] + ints[j] === s) return [ints[j], ints[i]];
}
}
}
const sum_pairs=function(ints, s){
const { length } = ints;
for (let i = 1; i < length; i++) {
for (let j = 0; j < i; j++) {
if (ints[i] + ints[j] === s) return [ints[j], ints[i]];
}
}
}
l1= [1, 4, 8, 7, 3, 15];
l2= [1, -2, 3, 0, -6, 1];
l3= [20, -13, 40];
l4= [1, 2, 3, 4, 1, 0];
l5= [10, 5, 2, 3, 7, 5];
l6= [4, -2, 3, 3, 4];
l7= [0, 2, 0];
l8= [5, 9, 13, -3];
console.log(sum_pairs(l1, 8))
console.log(sum_pairs(l2, -6))
console.log(sum_pairs(l3, -7))
console.log(sum_pairs(l4, 2))
console.log(sum_pairs(l5, 10))
console.log(sum_pairs(l6, 8))
console.log(sum_pairs(l7, 0))
console.log(sum_pairs(l8, 10))
It means that you go from left to right and take the first matching pair, and since 7 is the first element that creats a pair (going from the left) 3 and 7 is the first pair.
I would solve it a bit easier:
function sum_pairs(arr, target) {
let old = [];
let result = [];
arr.some((el) => {
let found = old.find((oldEl) => oldEl + el === target);
if (found) return result = [found, el];
old.push(el);
})
return result;
}
sum_pairs([10, 5, 2, 3, 7, 5], 10);
Edit: an explaination. I loop over all elements in the array searching for a match i all the elements I have passed. If I find a match I remember it and break out of the loop by returning a "truthy" value. (That is just how .some() works.) Finally if I have not found a match I add the element to my list of old elements and go on to the next.
function sum_pair(arr,sum){
let result = [];
arr.forEach((i, j)=>{
if(i+arr[j+1]===sum){
console.log(i,arr[j+1], i+arr[j+1])
}
})
}
sum_pair([0, 3, 7, 0, 5, 5],10)

Javascript: How to check if array of object has one and only one item with given value?

I know that in Javascript I can use .some or .every on arrays to check if they have atleast one item or every item that passes the test implemented by the provided function.
Here is some examples:
[2, 5, 8, 1, 4].some(x => x > 10); // false
[12, 5, 8, 1, 4].some(x => x > 10); // true
[12, 5, 8, 130, 44].every(x => x >= 10); // false
[12, 54, 18, 130, 44].every(x => x >= 10); // true
I'm looking for checking if an array has "one and only one" item that passes the given function.
I would like to have some method like the following:
[12, 5, 12, 13, 4].oneAndOnlyOne(x => x >= 10); // false
[2, 11, 6, 1, 4].oneAndOnlyOne(x => x >= 10); // true
Do you know any new ECMA Script 6 way or any easy/quick way, even using lodash, to check if in array there is one and one only occurrence of item that has certain values?
You could reach desired result using Array#filter.
const oneAndOnlyOne = arr => arr.filter(v => v >= 10).length == 1;
console.log(oneAndOnlyOne([12, 5, 12, 13, 4]));
console.log(oneAndOnlyOne([2, 11, 6, 1, 4]));
Probably the most efficient solution is the simple loop:
function findOne(arr) {
var foundOne = false;
for(var i = 0; i < arr.length; ++i) {
if(arr[i] > 10)
if(foundOne) return false;
else foundOne = true;
}
return foundOne;
}
There are some tools out there that already do this. Like ramda for instance. You can just find one that suits you.
Slice the array at the point the first match is found, then search in the remaining part of the array, as in
function oneAndOnlyOne(arr, fn) {
const pos = arr.findIndex(fn);
return pos >= 0 && !arr.slice(pos + 1).find(fn);
}
const tests = [[12, 5, 12, 13, 4], [2, 11, 6, 1, 4], [1, 2, 3]];
for (const test of tests)
console.log(String(test), oneAndOnlyOne(test, x => x >= 10));
Try with filter and array length match with 1 .And use callback method added with Array.prototype.oneAndOnlyOne
Array.prototype.oneAndOnlyOne=function(callback){
return this.filter(callback).length == 1
}
console.log([12, 11, 6, 1, 4].oneAndOnlyOne(x=> x >= 10)) // false
console.log([2, 11, 6, 1, 4].oneAndOnlyOne(x=> x >= 10)) // true
You can add a function to your array (since it is an object after all):
var arr = [30];
arr.oneAndOnlyOne = function(numToCheck) {
return this.filter(function(x) {
return x >= numToCheck
}).length === 1
}
arr.oneAndOnlyOne(30); //true
arr.push(30);
arr.oneAndOnlyOne(30); //false

Categories