How to divide an array to chunks with flexible output length? - javascript

How to divide an array to chunks with flexible output length?
it's necessary for render pagination, where we have limited place for elements,
and if we have several pages of elements — we need to display buttons with arrays (prev/next) instead el's.
example f() input-output, when 1st argument is Array with data,
and 2nd arg is maximum of elements in list, including nav-buttons:
f([1,2,3,4], 4) => [1,2,3,4]
f([1,2,3,4,5], 4) => [[1,2,3], [4,5]]
f([1,2,3,4,5,6], 4) => [[1,2,3], [4,5,6]]
f([1,2,3,4,5,6,7], 4) => [[1,2,3], [4,5], [6,7]]
f([7,6,5,4,3,2,1], 4) => [[7,6,5], [4,3], [2,1]]
f([1,2,3,4,5,6,7], 6) => [[1,2,3,4,5], [6,7]]
Example of design-layout with max 6 elements
1st page:
2nd page:
Last page:

First, determine if one page/two pages are enough. If not, loop the array.
let f = (array, max) => {
if (array.length / max <= 1)
return array
if (array.length / (max - 1) <= 2)
return [array.slice(0, max - 1), array.slice(max - 1, array.length)]
let result = []
let n = 0
while (n <= array.length - 1) {
if (n == 0) {
result.push(array.slice(n, n + max - 1))
n += max - 1
} else {
let pushAmount = n+max-1 >= array.length ? max-1 : max-2
result.push(array.slice(n, n + pushAmount))
n += pushAmount
}
}
return result
}
console.log(f([1, 2, 3, 4], 4))
console.log(f([1, 2, 3, 4, 5], 4))
console.log(f([1, 2, 3, 4, 5, 6], 4))
console.log(f([1, 2, 3, 4, 5, 6, 7], 4))
console.log(f([7, 6, 5, 4, 3, 2, 1], 4))
console.log(f([1, 2, 3, 4, 5, 6, 7], 6))

I hope this will work !
a=[1,2,3,4,5,6,7,8,9,2];
result=[];
start = true;
function f(arr,n){
if(arr.length >= n-2){
console.log(start)
if(start){
start=false;
result.push(arr.splice(0,n-1))
return f(arr,n)
}else{
result.push(arr.splice(0,n-2))
return f(arr,n)
}
} else {
result.push(arr);
return 1;
}
}
f(a,6);
console.log(result)

Another solution - with recursion. I felt this was a bit easier to understand.
f = (arr, max, first = true) => {
if (arr.length == 0) return []
const last = arr.length <= max - !first
const curLen = max - !first - !last
const cur = arr.slice(0, curLen)
const next = f(arr.slice(curLen), max, false)
return [cur, ...next]
}
console.log(f([1, 2, 3, 4], 4))
console.log(f([1, 2, 3, 4, 5], 4))
console.log(f([1, 2, 3, 4, 5, 6], 4))
console.log(f([1, 2, 3, 4, 5, 6, 7], 4))
console.log(f([7, 6, 5, 4, 3, 2, 1], 4))
console.log(f([1, 2, 3, 4, 5, 6, 7], 6))

Related

How to sort an array starting from the middle?

Considering, I have an array like this [..., n-2, n-1, n, n+1, n+2, ...]. I would like to sort it in this way [n, n+1, n-1, n+2, n-2,...] with n equals to the middle of my array.
For example:
Input:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Output:
[5, 6, 4, 7, 3, 8, 2, 9, 1, 0]
let arrayNotSorted = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let positionMiddleArray = Math.trunc(arrayNotSorted.length / 2);
let arraySorted = [arrayNotSorted[positionMiddleArray]];
for(let i=1; i <= positionMiddleArray; i++){
if(arrayNotSorted[positionMiddleArray + i] !== undefined){
arraySorted.push(arrayNotSorted[positionMiddleArray + i]);
}
if(arrayNotSorted[positionMiddleArray - i] !== undefined){
arraySorted.push(arrayNotSorted[positionMiddleArray - i]);
}
}
console.log('Not_Sorted', arrayNotSorted);
console.log('Sorted', arraySorted);
What I have done works properly, but I would like to know if there is a better way or a more efficient way to do so ?
You could take a pivot value 5 and sort by the absolute delta of the value and the pivot values an sort descending for same deltas.
var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
pivot = 5;
array.sort((a, b) => Math.abs(a - pivot) - Math.abs(b - pivot) || b - a);
console.log(...array); // 5 6 4 7 3 8 2 9 1 0
You can do that in following steps:
Create an empty array for result.
Start the loop. Initialize i to the half of the length.
Loop backwards means decrease i by 1 each loop.
push() the element at current index to the result array first and the other corresponding value to the array.
function sortFromMid(arr){
let res = [];
for(let i = Math.ceil(arr.length/2);i>=0;i--){
res.push(arr[i]);
res.push(arr[arr.length - i + 1])
}
return res.filter(x => x !== undefined);
}
console.log(sortFromMid([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]))
[5, 6, 4, 7, 3, 8, 2, 9, 1, 0]

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)

Finding local maxima within an array with conditions [JS]

I'm struggling to find a solution the following code challenge that meets all of the requirements and could use some help:
var ex1 = [5, 5, 2, 1, *4*, 2, *6*, 2, 1, 2, 7, 7];
// {pos:[4,6], peaks:[4,6] }
var ex2 = [3, 2, 3, *6*, 4, 1, 2, *3*, 2, 1, 2, 3];
// {pos:[3,7], peaks:[6,3]}
var plateau = [1, *2*, 2, 2, 1];
// {pos:[1],peaks[2]}
Find the local maxima or "peaks" of a given array but ignore local
maxima at the beginning and end of the array.
If there is a "plateau", return the position and value at the
beginning of the "plateau."
Any plateaus at beginning and end of the array should be ignored.
My proposed solution uses the reduce function to look at the elements in the array before and after the current element. If these values are less than the value of the current element, the current element is a peak. "Peaks" at the edges of the array are ignored because they do not satisfy either the first or second criteria.
function pickPeaks(array) {
return array.reduce((res, curr, i, arr) => {
if(arr[i-1] < curr && curr > arr[i+1]) {
res["pos"] = res["pos"] ? res["pos"].concat([i]) : [i];
res["peaks"] = res["peaks"] ? res["peaks"].concat([curr]) : [curr];
}
return res;
},{});
}
However, this solution fails to find plateaus.
If I change my "right side" conditional logic to curr >= arr[i+1] it finds plateaus but does not ignore "edge" plateaus like so:
var plateau = [1, *2*, 2, 2, 1];
correct // {pos:[1],peaks[2]}
var ex1 = [5, 5, 2, 1, *4*, 2, *6*, 2, 1, 2, *7*, 7];
incorrect // {pos:[4,6,7], peaks:[4,6,10]}
What am I missing here? How can I check if a "plateau" is at the edge of the array or not?
You could add a while loop for getting the end of a plateau.
function getLocalMaxima(array) {
return array.reduce(function (r, v, i, a) {
var j = i;
while (v === a[++j]);
if (a[i - 1] < v && (a[i + 1] < v || a[i + 1] === v && a[j] < v)) {
r.pos.push(i);
r.peaks.push(v);
}
return r;
}, { pos: [], peaks: []});
}
var ex1 = [5, 5, 2, 1, 4, 2, 6, 2, 1, 2, 7, 7], // { pos: [4, 6], peaks:[4, 6] }
ex2 = [3, 2, 3, 6, 4, 1, 2, 3, 2, 1, 2, 3], // { pos: [3, 7], peaks: [6, 3]}
plateau = [1, 2, 2, 2, 1]; // { pos: [1], peaks[2] }
console.log(getLocalMaxima(ex1));
console.log(getLocalMaxima(ex2));
console.log(getLocalMaxima(plateau));
.as-console-wrapper { max-height: 100% !important; top: 0; }
My solution is to remove plateaus at the end in advance, and a minor correction on your updating the result of peaks:
function trimEndingPlateaus(array){
var end = array.length -1;
for (var i = 1; i < array.length -1; i++){
if (array[array.length - 1 - i] === array[end]) end--;
else break;
}
return array.slice(0, end);
}
function pickPeaks(array) {
return trimEndingPlateaus(array).reduce((res, curr, i, arr) => {
// find peaks
if(arr[i-1] < curr && curr >= arr[i+1]) {
res["pos"] = res["pos"] ? res["pos"].concat([i]) : [i];
res["peaks"] = res["peaks"] ? res["peaks"].concat([curr]):[curr];
}
return res;
},{});
}
Test cases:
var plateau = [1, 2, 2, 2, 1];
var ex1 = [5, 5, 2, 1, 4, 2, 6, 2, 1, 2, 7, 7];
var ex2 = [3, 2, 3, 6, 4, 1, 2, 3, 2, 1, 2, 3];
console.log(pickPeaks(plateau));
//Outputs: {"pos":[1],"peaks":[2]}
console.log(pickPeaks(ex1));
//Outputs: {"pos":[4,6],"peaks":[4,6]}
console.log(pickPeaks(ex2));
//Outputs: {"pos":[3,7],"peaks":[6,3]}

how to reduce 0(n) for finding the first instance of pairs that sum specific value

I am doing a codewars problem, the instructions are as follows: 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.
The solution works, but it is too slow for long arrays, how would someone do this without using two for loops? I have been trying to reduce the time complexity, but I am at a loss at how to accomplish this when I need to look at all possible pairs.
function sumPairs(ints, s){
var lowestIdx1 = Infinity;
var lowestIdx2 = Infinity;
for (var i = 0; i < ints.length-1; i++) {
var cur = ints[i]
for (var k = i+1; k < ints.length; k++) {
var next = ints[k]
if(cur + next === s){
if(i <= lowestIdx1 && k <= lowestIdx1 || i <= lowestIdx2 && k <=lowestIdx2){
lowestIdx1 = i
lowestIdx2 = k
}
}
}
}
if(lowestIdx1 !== Infinity){
return [ints[lowestIdx1], ints[lowestIdx2]]
}
}
To be more clear on the problem here are some sample input outputs:
sum_pairs([11, 3, 7, 5], 10)
# ^--^ 3 + 7 = 10
== [3, 7]
sum_pairs([4, 3, 2, 3, 4], 6)
# ^-----^ 4 + 2 = 6, indices: 0, 2 *
# ^-----^ 3 + 3 = 6, indices: 1, 3
# ^-----^ 2 + 4 = 6, indices: 2, 4
# * entire pair is earlier, and therefore is the correct answer
== [4, 2]
sum_pairs([0, 0, -2, 3], 2)
# there are no pairs of values that can be added to produce 2.
== undefined
You could use some speeding mechanisms, like
single loop,
hash table for visited values
variable a for element array[i]
Long list of Sum of Pairs on Codewars needs 153 ms.
var sum_pairs = function (array, s) {
var a, i,
hash = Object.create(null);
for (i = 0; i < array.length; i++) {
a = array[i];
if (hash[s - a]) {
return [s - a, a];
}
if (!hash[a]) {
hash[a] = true;
}
}
};
console.log(sum_pairs([11, 3, 7, 5], 10)); // [3, 7]
console.log(sum_pairs([4, 3, 2, 3, 4], 6)); // [4, 2]
console.log(sum_pairs([0, 0, -2, 3], 2)); // undefined
console.log(sum_pairs([10, 5, 2, 3, 7, 5], 10)); // [3, 7]
console.log(sum_pairs([1, 2, 3, 4, 1, 0], 2)); // [1, 1]
console.log(sum_pairs([1, -2, 3, 0, -6, 1], -6)); // [0, -6]
console.log(sum_pairs([0, 2, 0], 0)); // [0, 0]
console.log(sum_pairs([5, 9, 13, -3], 10)); // [13, -3]
.as-console-wrapper { max-height: 100% !important; top: 0; }
The solution below runs in O(n) time, check out the steps for how it was solved:
// steps
// loop through array
// for each member
// first check if it's value in hash
// then store in hash with key as sum-member
// and value as member
// if value in hash already
// return [k,v]
function sumPairs(ints, s) {
const possible_pairs={}
// loop through array
for(let ints_i=0;ints_i<ints.length;ints_i+=1){
// for each member
let element = ints[ints_i].toString()
// first check if it's value in hash
// if value in hash already
// return [k,v]
if (possible_pairs[element]) return [parseInt(possible_pairs[element], 10), parseInt(element, 10)]
// else store in hash with key as value-member
// and value as member
possible_pairs[s-element]=element
}
return undefined ;
}
console.log(sumPairs([ 0, -6], -6)) //[0, -6]
console.log(sumPairs([10, 5, 2, 3, 7, 5], 10)) //[3, 7]

Chunking array within array algorithm

first question on stackoverflow, i'm struggling with this algorithm. This is supposed to slice my array in 5 like "[[0, 1], [2, 3], [4, 5], [6, 7], [8]]" but all i got is "[ [ 0, 1 ], [ 2, 3 ], [ 4, 5 ], [ 6, 7, 8 ] ]"
function chunkArrayInGroups(arr, size) {
var newArr = [];
console.log(Math.floor(arr.length / size));
for (i = 0; i <= (Math.floor(arr.length / size)) + 1; ++i) {
var cut = size;
newArr.push(arr.splice(0, cut));
}
if (arr.length > 0) {
newArr.push(arr.splice(0, size + (arr.length - size)));
}
return newArr;
}
chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
// expected - [[0, 1], [2, 3], [4, 5], [6, 7], [8]]
If you have any tips about the way of asking questions, i'd be happy to receive any advice !
Use a simple for loop with Array#slice, because slice doesn't change the length of the original array:
function chunkArrayInGroups(arr, size) {
var chunked = [];
for(var i = 0; i < arr.length; i += size) { // increment i by the size
chunked.push(arr.slice(i, i + size));
}
return chunked;
}
var result = chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
console.log(result);
Since you are removing element using Array#splice the array length get decreased so instead of calculating the range cache the range for the for loop condition. Although use Math.ceil and avoid the unnecessary if statement.
function chunkArrayInGroups(arr, size) {
var newArr = [],
range = Math.ceil(arr.length / size);
for (i = 0; i < range; i++) {
newArr.push(arr.splice(0, size));
}
return newArr;
}
console.log(chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2));
Check this out.
function chunkArrayInGroups(arr, size) {
newArr = [];
for (i=0,j=arr.length; i<j; i+=size) {
newArr.push(arr.slice(i,i+size));
}
return newArr;
}
console.log(chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2));
// expected - [[0, 1], [2, 3], [4, 5], [6, 7], [8]]
Another way to do it is Array#reduce:
function chunkArrayInGroups(arr, size) {
return arr.reduce(function (accum, elem) {
var curr = accum[accum.length - 1];
if (curr.length < size) curr.push(elem); else accum.push([elem]);
return accum;
}, [[]]);
}
var result = chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2);
console.log( JSON.stringify(result) );
Welcome to SO. Here is how I would do that. Let me know if you have any questions about this approach.
function chunkArrayInGroups(arr, size) {
var newArr = [];
while(arr.length > 0){
newArr.push(arr.splice(0, size));
}
return newArr;
}
Functionally you can do as follows;
function chunkArrayInGroups(a,g){
return Array(Math.ceil(a.length/g)).fill()
.map((_,i) => [a[g*i]].concat(a.slice(g*i+1, g*i+g)));
}
var arr = [0, 1, 2, 3, 4, 5, 6, 7, 8];
result = [];
result = chunkArrayInGroups(arr,2);
console.log(JSON.stringify(result));
result = chunkArrayInGroups(arr,3);
console.log(JSON.stringify(result));
result = chunkArrayInGroups(arr,4);
console.log(JSON.stringify(result));
You could use a while loop and splice the length of the wanted size for a grouped array.
function chunkArrayInGroups(array, size) {
var result = [];
while (array.length) {
result.push(array.splice(0, size));
}
return result;
}
console.log(chunkArrayInGroups([0, 1, 2, 3, 4, 5, 6, 7, 8], 2));

Categories