How to create windowed slice of array in JavaScript? - javascript

I'm looking for an array method implementation named Array.window(n) that invoked on an array with parameter n, would give a contiguous overlapping array slice.
Example:
let a = [1, 2, 3, 4, 5, 6]
a.window(2) // [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
a.window(3) // [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]
a.window(4) // [[1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6]]
a.window(10)// []
How can I accomplish this?

Here you go with a slightly shorter example using array functions:
let a = [1, 2, 3, 4, 5, 6];
function windowedSlice(arr, size) {
let result = [];
arr.some((el, i) => {
if (i + size > arr.length) return true;
result.push(arr.slice(i, i + size));
});
return result;
}
console.log(windowedSlice(a, 2));
console.log(windowedSlice(a, 3));
console.log(windowedSlice(a, 4));
console.log(windowedSlice(a, 5));

I think this would suffice:
function arrayWindow(array, n) {
if (array.length < n || n <= 0) {
return []
}
let arrayLength = array.length
let result = []
for (let i = 0; i < arrayLength; i++) {
let slicedArray = array.slice(i, n+i)
if (slicedArray && slicedArray.length === n) {
result.push(slicedArray)
continue
}
break
}
return result
}
let arr = [1, 2, 3, 4, 5, 6]
console.log(arrayWindow(arr, 2))
console.log(arrayWindow(arr, 3))
console.log(arrayWindow(arr, 4))
console.log(arrayWindow(arr, 5))
console.log(arrayWindow(arr, 10))
console.log(arrayWindow(arr, 0))

Here is a simple rolling window function. Notice that we can determine the number of iterations based on array length minus the desired window size.
/**
* Produces a rolling window of desired length {w} on a 1d array.
*
* #param {Array} a The 1d array to window.
* #param {Number} w The desired window size.
* #return {Array} A multi-dimensional array of windowed values.
*/
function rolling(a, w) {
let n = a.length;
let result = [];
if (n < w || w <= 0) {
return result;
}
for (let i = 0; i < n - w + 1; i++) {
result.push(a.slice(i, w + i));
}
return result;
}
let a = [1, 2, 3, 4, 5];
console.log(JSON.stringify(rolling(a, 3)));

Related

Get n non overlapping m-sized samples from an array

Given an array, how can I extract n non overlapping random samples of size m from it?
For example, given the array:
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
calling sample(arr, 3, 2) would for example return [[7, 8], [4, 5], [2, 3]], calling sample(arr, 2, 4) would necessarily return [[1, 2, 3, 4], [5, 6, 7, 8], and calling sample(arr, 5, 2) would throw an error.
EDIT - Maybe this wasn't clear in the initial question: samples should be lists of contiguous elements. That is why sample(arr, 2, 4) can only return [[1, 2, 3, 4], [5, 6, 7, 8] and not [[2, 3, 1, 6], [5, 4, 7, 8], for example.
You could start off by first creating a list with the format of the return value:
[ 1, 2, 3, 4, 5, 6, 7, 8]
[<---->, <---->, <---->, <>, <>] // sample(array, 3, 2)
[<------------>, <------------>] // sample(array, 2, 4)
These format arrays could be written out using the lengths:
[1, 2, 3, 4, 5, 6, 7, 8]
[ 2, 2, 2, 1, 1] // sample(array, 3, 2)
[ 4, 4] // sample(array, 2, 4)
Then shuffle the format arrays to gain a random sample selection:
[1, 2, 3, 4, 5, 6, 7, 8]
[ 2, 1, 2, 2, 1] // sample(array, 3, 2)
[ 4, 4] // sample(array, 2, 4)
Then for each element of the format array, remove the the first n elements from the input array. Then store them unless it was a filler (one size chunks that are put in to reach the array length).
[1, 2, 3, 4, 5, 6, 7, 8]
[[1,2], [4,5], [6,7]] // sample(array, 3, 2)
[[1,2,3,4], [5,6,7,8]] // sample(array, 2, 4)
Lastly shuffle the resulting samples.
[1, 2, 3, 4, 5, 6, 7, 8]
[[4,5], [1,2], [6,7]] // sample(array, 3, 2)
[[5,6,7,8], [1,2,3,4]] // sample(array, 2, 4)
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(sample(arr, 3, 2));
console.log(sample(arr, 2, 4));
console.log(sample(arr, 5, 2));
function randomInt(limit) {
return Math.floor(Math.random() * limit);
}
function shuffle(array) {
for (let limit = array.length; limit > 0; --limit)
array.push(...array.splice(randomInt(limit), 1));
}
function sample(array, sampleCount, sampleLength) {
let elementCount = sampleCount * sampleLength;
if (elementCount > array.length)
throw "invalid sampleCount/sampleLength arguments";
const filler = {valueOf: () => 1};
const fillerCount = array.length - elementCount;
const lengths = Array.from(
{length: sampleCount + fillerCount},
(_, i) => i < sampleCount ? sampleLength : filler
);
shuffle(lengths);
const samples = Array.from(array);
for (const length of lengths) {
const sample = samples.splice(0, length);
if (length === filler) continue;
samples.push(sample);
}
shuffle(samples);
return samples;
}
Note that === is important in length === filler. If you use ==, filler would also equal 1. This would then conflict with a call like sample(array, 5, 1) where each sample length is 1.
const filler = {valueOf: () => 1};
console.log("1 == filler //=>", 1 == filler);
console.log("2 == filler //=>", 2 == filler);
console.log("filler == filler //=>", filler == filler);
console.log("1 === filler //=>", 1 === filler);
console.log("2 === filler //=>", 2 === filler);
console.log("filler === filler //=>", filler == filler);
you can use a greedy algorithm, and take m-sized n tuples from the shuffled array:
const arr = [2, 1, 3, 4, 5, 6, 7, 8];
function sample(arr, length, size){
if(arr.length < length*size)
throw new Error("too short");
arr.sort(() => Math.random() - 0.5);
let res = [];
for(let i = 0; i < length; i++) res.push(arr.slice(i*size, i*size+size));
return res;
}
console.log(sample(arr, 2, 4));
I think the best implementation would shuffle first. Here's my two cents:
function shuffle(array){
let a = array.slice(), i = a.length, n, h;
while(i){
n = Math.floor(Math.random()*i--); h = a[i]; a[i] = a[n]; a[n] = h;
}
return a;
}
function sample(array, chunks, count){
const r = [], a = shuffle(array);
for(let n=0; n<chunks; n++){
r.push(a.splice(0, count));
}
return r;
}
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
console.log(sample(arr, 3, 2)); console.log(sample(arr, 2, 4));
You can do this with Rando.js (which is cryptographically secure), map, and splice pretty easily. Just use randojs' randoSequence function to shuffle the provided array and splice n size-m arrays out of that shuffled array to get everything we need to return. If the provided array has too few values, the later arrays that we return will just be shorter.
function sample(arr, n, m){
arr = randoSequence(arr).map(i => i.value), sample = [];
for(var i = 0; i < n; i++) sample[i] = arr.splice(-m);
return sample;
}
console.log(sample([1, 2, 3, 4, 5, 6, 7, 8], 3, 2));
<script src="https://randojs.com/2.0.0.js"></script>

Why JS is passing a number into the array as a string?

I'm trying to obtain // [2,6,0,8,4] from the function:
let getValidPassword = arr => {
let x = [];
for (let i in arr) {
for (let j in arr[i]) {
if (arr[i][j] % 2 !== 0) {
break;
} else {
x += arr[i][j];
}
}
}
return x
};
var loggedPasscodes = [
[1, 4, 4, 1],
[1, 2, 3, 1],
[2, 6, 0, 8],
[5, 5, 5, 5],
[4, 3, 4, 3]
];
console.log(getValidPassword(loggedPasscodes));
However when I run the typeof x, I'm getting a string(I though it was a number) and when I print x I get 26084 instead of [26084]
what witchcraft is this?
I though setting x to [ ] would make the trick...
thank you.
The problem here is that you have declared x=[] but you are modifying it as x += arr[i][j]; as soon as javascript gets to this line. It treats the array as string calling x.toString() internally and appending to that string. For example if you declare an array as a=[] and call a+=1 then a will become "1". In javascript everything is value typed, it doesn't matter what you declare when you assign some value to it or do some operation on the variable, it gets converted to the type of value.
I would recommend you to go through this
let getValidPassword = arr => {
let x = [];
let temp = [];
for (let i in arr) {
for (let j in arr[i]) {
if (arr[i][j] % 2 !== 0) {
break;
} else {
temp.push(arr[i][j]);
}
if(temp.length == arr[i].length)
x = temp.slice();
}
}
return x
};
var loggedPasscodes = [
[1, 4, 4, 1],
[1, 2, 3, 1],
[2, 6, 0, 8],
[5, 5, 5, 5],
[4, 3, 4, 3]
];
console.log(getValidPassword(loggedPasscodes));
The problem is that you are incrementing your variable, to add an element to an array you need to use the push() method.
Correct code:
let getValidPassword = arr => {
let x = [];
for (let i in arr) {
for (let j in arr[i]) {
if (arr[i][j] % 2 !== 0) {
break;
} else {
x.push(arr[i][j]);
}
}
}
return x
};
var loggedPasscodes = [
[1, 4, 4, 1],
[1, 2, 3, 1],
[2, 6, 0, 8],
[5, 5, 5, 5],
[4, 3, 4, 3]
];
console.log(getValidPassword(loggedPasscodes));
When you concatenate an array (which is what += is doing) it first converts the array and the value being appended to strings.
In order to add elements to the x array use x.push(arr[i][j]), this will insert them without type conversion.

Retrieve diagonal values from a 2d array

I'm trying to create a function that retrieves the diagonal values from a 2-d array:
input = [
[1, 2, 3, 4],
[5, 1, 2, 3],
[9, 5, 1, 2]
]
output = [[9], [5, 5], [1, 1, 1], [2, 2, 2], [3, 3], [4]]
I'm having trouble figuring out how to manipulate the indices in a nested loop... This is what I'm currently working with:
const diagonalValues = arr => {
let output = new Array(2*input.length);
for (let i = 0; i < output.length; i++) {
output[i] = [];
if (i < input.length) {
for (j = input.length-1; j>i-input.length; --j) {
console.log(i, j)
}
}
}
}
How can I accomplish this?
You could use get number of rows which is just number of arrays and number of columns which is number of elements in each inner array (assuming all arrays have the same number of elements), and based on that calculate the diagonal matrix.
const input = [
[1, 2, 3, 4],
[5, 1, 2, 3],
[9, 5, 1, 2]
]
const rows = input.length;
const columns = input[0].length;
const total = columns + rows - 1;
const result = [];
for (let i = rows - 1; i >= 0; i--) {
const row = input[i];
for (let j = 0; j < columns; j++) {
const el = input[i][j];
const pos = j + rows - i - 1;
if (!result[pos]) {
result[pos] = []
}
result[pos].unshift(el)
}
}
console.log(JSON.stringify(result))
You can do the same with reduceRight and forEach methods.
let input = [
[1, 2, 3, 4, 4],
[5, 1, 2, 8, 3],
[9, 5, 1, 2, 2],
[9, 5, 1, 2, 1]
]
const result = input.reduceRight((r, a, i) => {
a.forEach((e, j) => {
const pos = j + (input.length - i - 1)
if(!r[pos]) r[pos] = []
r[pos].unshift(e)
})
return r;
}, []);
console.log(JSON.stringify(result))
You can use this algorithm to retrieve the diagonal values from 2d-input array.
const input = [
[1, 2, 3, 4],
[5, 1, 2, 3],
[9, 5, 1, 2]]
let output = []
input.forEach(res => {
res.forEach(resp => {
// if length of array is equel to 1
if (output.filter(x => x == resp).length > 0) {
output.filter(x => x == resp)[0].push(resp)
//if length of array is greater than 1
} else if (output.filter(x => x[0] == resp).length > 0) {
output.filter(x => x[0] == resp)[0].push(resp)
} else {
let temp = []
temp.push(resp)
output.push(temp)
}
})
})
output.forEach(o => console.log(JSON.stringify(o)));
let input = [
[1, 2, 3, 4],
[5, 1, 2, 8],
[9, 5, 1, 2],
[9, 5, 1, 2],
[9, 5, 1, 2],
];
let out = [];
for (let i = 1 - input.length; i < input[0].length; i++) {
let o = [];
let y = Math.max(-i, 0);
let x = Math.max(i, 0);
while (x < input[0].length && y < input.length)
o.push(input[y++][x++]);
out.push(o)
}
out.forEach(o => console.log(JSON.stringify(o)));

JavaScript array cross random number?

In the array randomly take three numbers, but the number of up and down the left and right non-adjacent (array cross random number)
This is my code, how can I optimize (get the number more evenly)?
Thanks~
array
var a = [
[0, 1],
[2, 3],
[4, 5],
[6, 7]
];
function select() {
var a = [
[0, 1],
[2, 3],
[4, 5],
[6, 7]
];
var lastSelect = -1;
for (var i = 0; i < a.length; i++) {
var index = getRandomNumber(lastSelect, a[i].length);
console.log(a[i][index]);
lastSelect = index;
}
}
function getRandomNumber(lastSelect, max) {
var random = Math.floor(Math.random() * max);
if (random == lastSelect) return getRandomNumber(lastSelect, max);
else return random;
}
select();
Javascript has a modulus operator %, that finds the remainder of division.
var a = [
[0, 1],
[2, 3],
[4, 5],
[6, 7]
];
function select() {
//generating random 0 or 1
var random = Math.round(Math.random());
for (var i = 0; i < a.length; i++) {
console.log(a[i][(i+random)%2]);// 0 or 1
}
console.log("-----------");
}
//test
for(var j=0;j<5;j+=1){
select();
}

How to sum elements at the same index in array of arrays into a single array?

Let's say that I have an array of arrays, like so:
[
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
]
How do I generate a new array that sums all of the values at each position of the inner arrays in javascript? In this case, the result would be: [17, 10, 19]. I need to be able to have a solution that works regardless of the length of the inner arrays. I think that this is possible using some combination of map and for-of, or possibly reduce, but I can't quite wrap my head around it. I've searched but can't find any examples that quite match this one.
You can use Array.prototype.reduce() in combination with Array.prototype.forEach().
var array = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
],
result = array.reduce(function (r, a) {
a.forEach(function (b, i) {
r[i] = (r[i] || 0) + b;
});
return r;
}, []);
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Update, a shorter approach by taking a map for reducing the array.
var array = [[0, 1, 3], [2, 4, 6], [5, 5, 7], [10, 0, 3]],
result = array.reduce((r, a) => a.map((b, i) => (r[i] || 0) + b), []);
console.log(result);
Using Lodash 4:
function sum_columns(data) {
return _.map(_.unzip(data), _.sum);
}
var result = sum_columns([
[1, 2],
[4, 8, 16],
[32]
]);
console.log(JSON.stringify(result));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
For older Lodash versions and some remarks
Lodash 4 has changed the way _.unzipWith works, now the iteratee gets all the values passed as spread arguments at once, so we cant use the reducer style _.add anymore. With Lodash 3 the following example works just fine:
function sum_columns(data) {
return _.unzipWith(data, _.add);
}
var result = sum_columns([
[1, 2],
[4, 8, 16],
[32],
]);
console.log(JSON.stringify(result));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
_.unzipWith will insert undefineds where the row is shorter than the others, and _.sum treats undefined values as 0. (as of Lodash 3)
If your input data can contain undefined and null items, and you want to treat those as 0, you can use this:
function sum_columns_safe(data) {
return _.map(_.unzip(data), _.sum);
}
function sum_columns(data) {
return _.unzipWith(data, _.add);
}
console.log(sum_columns_safe([[undefined]])); // [0]
console.log(sum_columns([[undefined]])); // [undefined]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
This snipet works with Lodash 3, unfortunately I didn't find a nice way of treating undefined as 0 in Lodash 4, as now sum is changed so _.sum([undefined]) === undefined
One-liner in ES6, with map and reduce
var a = [ [0, 1, 3], [2, 4, 6], [5, 5, 7], [10, 0, 3] ];
var sum = a[0].map((_, i) => a.reduce((p, _, j) => p + a[j][i], 0));
document.write(sum);
Assuming that the nested arrays will always have the same lengths, concat and reduce can be used.
function totalIt (arr) {
var lng = arr[0].length;
return [].concat.apply([],arr) //flatten the array
.reduce( function(arr, val, ind){ //loop over and create a new array
var i = ind%lng; //get the column
arr[i] = (arr[i] || 0) + val; //update total for column
return arr; //return the updated array
}, []); //the new array used by reduce
}
var arr = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
];
console.log(totalIt(arr)); //[17, 10, 19]
Assuming array is static as op showned.
a = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
]
b = []
for(i = 0; i < a[0].length; i++){
count = 0
for(j = 0; j < a.length; j++){
count += a[j][i]
}
b.push(count)
}
console.log(b)
So far, no answer using the for ... of mentioned in the question.
I've used a conditional statement for different lengths of inner arrays.
var a = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
];
i = 0;
r = []
for (const inner of a) {
j = 0;
for (const num of inner) {
if (j == r.length) r.push(num)
else r[j] += num
j++;
}
i++;
}
console.log(r);
True, in this case, the classic for cycle fits better than for ... of.
The following snippet uses a conditional (ternary) operator.
var a = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
];
r = [];
for (i = 0; i < a.length; i++) {
for (j = 0; j < a[i].length; j++) {
j==r.length ? r.push(a[i][j]) : r[j]+=a[i][j]
}
}
console.log(r);
A solution using maps and reductions, adding elements from different lengths of arrays.
var array = [
[0],
[2, 4],
[5, 5, 7, 10, 20, 30],
[10, 0]
];
b = Array(array.reduce((a, b) => Math.max(a, b.length), 0)).fill(0);
result = array.reduce((r, a) => b.map((_, i) => (a[i] || 0) + (r[i] || 0)), []);
console.log(result);
const ar = [
[0, 1, 3],
[2, 4, 6],
[5, 5, 7],
[10, 0, 3]
]
ar.map( item => item.reduce( (memo, value)=> memo+= value, 0 ) )
//result-> [4, 12, 17, 13]

Categories

Resources