Get n non overlapping m-sized samples from an array - javascript

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>

Related

Find position of Paired Numbers in Array

let input arr=[9,4,4,8,90,4,9,4,4,4,4,4,4,4,4,4,7,9,2,4,4,4,4,4,8,4,4,4,4];
let output arr=[1,7,9,11,13,15,19,21,25,27];
Above is an input array of numbers which contain mostly 4, if there is a pair of 4 which means (Input elements has 2 of the number 4 consecutively), its array position will be displayed in the output array. I have tried my code below but I am still unsure on how to solve this :). May I know how to solve this?
console.clear();
let arr=[8,4,4,4,4,4,4,4,4,4,7,9,2,4,4,4,4,4,8,4,4,4,4];
console.log("his")
for (let i=0;i<arr.length;i++){
if (arr[i]!==arr[i+1] &&arr[i]!==4 ){
console.log(i)
}
if (arr[i]!==arr[i+1] &&arr[i+1]!==4 ){
console.log(i+1)
}
}
This is a possible solution:
console.clear();
let arr=[8,4,4,4,4,4,4,4,4,4,7,9,2,4,4,4,4,4,8,4,4,4,4];
let pair = false;
for (let i=0;i<arr.length;i++){
if (pair == false) {
if (arr[i]==arr[i+1]){
console.log(i)
pair = true; // show that we have found a pair
} // thus we skip a 'for' loop
}
else {
pair = false; // reset the pair variable
}
}
output: [1, 3, 5, 7, 13, 15, 19, 21]
Do you want this pair:
let arr=[8,4,4,4,4,4,4,4,4,4,7,9,2,4,4,4,4,4,8,4,4,4,4];
to be counted, as well?
You could look to the next item and if the last index is not in the result array, the add the actual index to the result.
const
input = [9, 4, 4, 8, 90, 4, 9, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7, 9, 2, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4],
result = input.reduce((r, v, i, a) => {
if (v === a[i + 1] && r[r.length - 1] !== i - 1) r.push(i);
return r;
}, []);
console.log(...result);
An approach for n elements with a closure over a counting variable c.
const
getIndices = (array, n) => input.reduce((c => (r, v, i, a) => {
if (!c) {
if (v === a[i + 1]) c = 1;
return r;
}
c = v === a[i - 1] ? c + 1 : 0;
if (c === n) {
r.push(i - n + 1);
c = 0;
}
return r;
})(0), []),
input = [9, 4, 4, 8, 90, 4, 9, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7, 9, 2, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4];
console.log(...getIndices(input, 2));
console.log(...getIndices(input, 3));
console.log(...getIndices(input, 4));
console.log(...getIndices(input, 5));

Replace consecutive duplicate values from array

In an array of numbers, I need to find repeating values and replace them with null.
Examples
Replace 6 in the middle of array if its neighbors are also 6
[1, 4, 3, 6, 6, 6, 6, 3, 2] => [1, 4, 3, 6, null, null, 6, 3, 2]
Replace 6 at the end of the array if the penultimate value is 6 :
[2, 6, 6, 6, 5, 2, 6, 6] => [2, 6, null, 6, 5, 2, 6, null]
Replace 6 at the start of the array if the next value is 6
[6, 6, 2, 3, 5, 6] => [null, 6, 2, 3, 5, 6]
Any ideas how to achieve this? I'm open to using lodash / underscore if needed
You have to iterate through the array and check for the cases you mentioned.
Check if first element is equal to second element
Check if last element is equal to penultimate element
Check if element is equal to either neighboring element
The following code should achieve what you asked for.
function replaceRepeats(arr) {
for (let i = 0; i < arr.length; i++) {
if (i === 0) {
if (arr[i] === arr[i + 1]) {
arr[i] = null;
}
} else if (i === arr.length - 1) {
if (arr[i] === arr[i - 1]) {
arr[i] = null;
}
} else {
if (arr[i] === arr[i - 1] || arr[i] === arr[i + 1]) {
arr[i] = null;
}
}
}
return arr;
}
You could use map so that you have access to the original array while replacing:
const maskDupes = arr =>
arr.map((val, i, arr) =>
(arr[i-1]??val) == val && (arr[i+1]??val) == val ? null : val
);
console.log(...maskDupes([1, 4, 3, 6, 6, 6, 6, 3, 2]));
console.log(...maskDupes([2, 6, 6, 6, 5, 2, 6, 6]));
console.log(...maskDupes([6, 6, 2, 3, 5, 6]));
Or if the array should be mutated, you could keep track of the current value that must be compared, so you still have it available even when in the array it was replaced by a null:
const maskDupes = arr => {
for (let i = 0, prev = arr[0]; i < arr.length; i++) {
if (prev == arr[i] && arr[i] == (arr[i+1]??prev)) arr[i] = null;
else prev = arr[i];
}
}
let arr = [1, 4, 3, 6, 6, 6, 6, 3, 2];
maskDupes(arr);
console.log(...arr);
arr = [2, 6, 6, 6, 5, 2, 6, 6];
maskDupes(arr);
console.log(...arr);
arr = [6, 6, 2, 3, 5, 6];
maskDupes(arr);
console.log(...arr);

JS Number of occurences in a sequence is a prime number

I have two arrays (X and Y) and I need to create array Z that contains all elements from array X except those, that are present in array Y p times where p is a prime number.
I am trying to write this in JS.
For Example:
Array X:
[2, 3, 9, 2, 5, 1, 3, 7, 10]
Array Y:
[2, 1, 3, 4, 3, 10, 6, 6, 1, 7, 10, 10, 10]
Array Z:
[2, 9, 2, 5, 7, 10]
So far I have this:
const arrX = [2, 3, 9, 2, 5, 1, 3, 7, 10]
const arrY = [2, 1, 3, 4, 3, 10, 6, 6, 1, 7, 10, 10, 10]
const arrZ = []
const counts = [];
// count number occurrences in arrY
for (const num of arrY) {
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
// check if number is prime
const checkPrime = num => {
for (let i = 2; i < num; i++) if (num % i === 0) return false
return true
}
console.log(counts[10]);
// returns 4
Any hint or help appreciated. Thanks!
You're on the right track. counts should be an object mapping elements in arrY to their number of occurrences. It's easily gotten with reduce.
The prime check needs a minor edit, and the last step is to filter arrX. The filter predicate is just a prime check on the count for that element.
// produce an object who's keys are elements in the array
// and whose values are the number of times each value appears
const count = arr => {
return arr.reduce((acc, n) => {
acc[n] = acc[n] ? acc[n]+1 : 1;
return acc;
}, {})
}
// OP prime check is fine, but should handle the 0,1 and negative cases:
const checkPrime = num => {
for (let i = 2; i < num; i++) if (num % i === 0) return false
return num > 1;
}
// Now just filter with the tools you built...
const arrX = [2, 3, 9, 2, 5, 1, 3, 7, 10]
const arrY = [2, 1, 3, 4, 3, 10, 6, 6, 1, 7, 10, 10, 10]
const counts = count(arrY);
const arrZ = arrX.filter(n => checkPrime(counts[n]));
console.log(arrZ)

Comparing Elements Values In An Array, and returning Boolean

Can anyone help I am nearly there I can get the code to return true for the first 2 test below, but not return false for the next 2 tests, what I need to do is to check that all the numbers in the array are in ascending order so every element number is greater than the last for the length of the array. Any ideas? Kind regards Jon.
Test.expect(inAscOrder([1, 2, 4, 7, 19])
Test.expect(inAscOrder([1, 2, 3, 4, 5])
Test.expect(!inAscOrder([1, 6, 10, 18, 2, 4, 20])
Test.expect(!inAscOrder([9, 8, 7, 6, 5, 4, 3, 2, 1])
function inAscOrder(arr) {
let result = false;
for (let i = 0; i <= arr.length; i++) {
for (let k = 0; k <= arr.length; k++) {
if (arr[i] < arr[k+1]) {
result = true;
}
}
}
return result;
}
You were very close to answer
function inAscOrder(arr) {
let result = true;
for (let i = 0; i <= arr.length; i++) {
if (arr[i] > arr[i+1]) {
result = false;
break;
}
}
return result;
}
Try this,
You can use the functional way instead.
const inAscOrder = arr => arr.slice(1).every((elem,i) => elem > arr[i]);
console.log(inAscOrder([1,2,5]));
The easiest way to compare two arrays, is to convert them to a strings and then compare the strings.
const isAscending = (arr) => arr.join("") == arr.sort((a, b) => a - b).join("");
console.log(isAscending([1, 2, 4, 7, 19]));
console.log(isAscending([1, 2, 3, 4, 5]));
console.log(isAscending([1, 6, 10, 18, 2, 4, 20]));
console.log(isAscending([9, 8, 7, 6, 5, 4, 3, 2, 1]));
For every element in the array, return true if it's the first element or it's greater than the previous element.
const isAscending = (arr) => arr.every((el, i, arr) => i === 0 || el > arr[i - 1]);
console.log(isAscending([1, 2, 4, 7, 19]));
console.log(isAscending([1, 2, 3, 4, 5]));
console.log(isAscending([1, 6, 10, 18, 2, 4, 20]));
console.log(!isAscending([9, 8, 7, 6, 5, 4, 3, 2, 1]));

Array element not splicing into separate array - FreeCodeCamp > Chunky Monkey

I'm working through the FreeCodeCamp challenges and I'm stuck on the following one:
Our goal for this Algorithm is to split arr (first argument) into
smaller chunks of arrays with the length provided by size (second
argument). There are several green checks (objectives) our code needs
to pass in order to complete this Algorithm:
(['a', 'b', 'c', 'd'], 2) is expected to return [['a', 'b'], ['c', 'd']]
([0, 1, 2, 3, 4, 5], 3) is expected to return [[0, 1, 2], [3, 4, 5]]
([0, 1, 2, 3, 4, 5], 2) is expected to return [[0, 1], [2, 3], [4, 5]]
([0, 1, 2, 3, 4, 5], 4) is expected to return [[0, 1, 2, 3], [4, 5]]
([0, 1, 2, 3, 4, 5, 6, 7, 8], 2) is expected to return [[0, 1], [2, 3],
[4, 5], [6, 7], [8]].
Here is the code I've come up with:
function chunkArrayInGroups(arr, size) {
var newArray = [];
var holdArray = [];
for (var i = 0; i <= arr.length; i += size) {
holdArray = arr.slice(0, size);
removed = arr.splice(0, size);
newArray.push(holdArray);
}
if (arr.length !== 0) {
newArray.push(arr);
return newArray;
}
else return newArray;
}
chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
The algorithm works for all of the objectives, except for the last one. Instead of returning:
[[0, 1], [2, 3], [4, 5], [6, 7], [8]]
it's returning:
[[0, 1], [2, 3], [4, 5], [6, 7, 8]] - and I can't work out why.
Any help shedding some light on where I'm going wrong would be much appreciated!
I don't blame you for being confused, it took me a while to find the problem, too.
It started with identifying that your for loop was a bit odd - you weren't using the i variable for anything. Additionally, you were splice()-ing the original array down each iteration, so your intention was correct - to wait until there is less than size elements left in the array.
So I expressed that as a while loop instead, and voila, it works:
function chunkArrayInGroups(arr, size) {
var newArray = [];
var holdArray = [];
while (arr.length >= size) {
holdArray = arr.slice(0, size);
removed = arr.splice(0, size);
newArray.push(holdArray);
}
if (arr.length !== 0) {
newArray.push(arr);
return newArray;
}
else return newArray;
}
chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
You are recaluculating length again and again, but you forgot that you are actually removing elements from arr, so length would not be the same as size increases length decreases,either you can give as while loop or keep original length in a variable and check
function chunkArrayInGroups(arr, size) {
var newArray = [];
var holdArray = [];
var length=arr.length;
for (var i = 0; i <=length; i += size) {
holdArray = arr.slice(0, size);
removed = arr.splice(0, size);
newArray.push(holdArray);
}
if (arr.length !== 0) {
newArray.push(arr);
return newArray;
}
else return newArray;
}
chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);

Categories

Resources