Refactoring Javascript Function: Array transformation - javascript

I have a constant array like this:
const pie_values = [20,10,5,5,10];
The challenge is to transform the above array based on integer input.
(5) => [5, 15, 10, 5, 5, 10]
(21) => [20, 1, 9, 5, 5, 10]
(31) => [20, 10, 1, 4, 5, 10]
An input of 5 takes 5 from the first index of pie_values. But it still leaves 15.
An input of 21 can take 20 from index 0 and 1 from index 2, leaving 9
I think you can see how this is going. So (0) and (50) will return the original pie_values.
Now the challenge is to create a function that does this in a few lines of code, which is build on loops rather than on 5 if statements. In case pie_values is extended upon, the function should still work.
I have an approach working with if statements, however the latter is undoable. What would be a good approach to these kind of problems?
First I defined a helper function:
//Returns summation of pie value
// totalPieValues(1) = 20
// totalPieValues(2) = 30
// totalPieValues(3) = 35
// totalPieValues(4) = 40
// totalPieValues(5) = 50
function totalPieValues(max) {
let result = 0;
for (let i = 0; i < max; i++) {
result += PIE_VALUES[i];
}
return result;
}
Then I worked on a function getPieArray which utilizes the helper function. This is where I am stuck
function getPieArray(wp) {
for (let i = 0; i < PIE_VALUES.length; i++) {
if (wp == 0 || wp == totalPieValues(i)) {
return PIE_VALUES;
}
}
let result = [];
for (let i = 1; i <= PIE_VALUES.length; i++) {
if (wp > totalPieValues(PIE_VALUES.length - i)) {
result.push(PIE_VALUES[i]);
} else if () {
result.push(wp - totalPieValues(3));
} else {
result.push(PIE_VALUES[i] - (value - totalPieValues(3)));
}
}
return result;
}
The code that I have written and works is here:
//Returns array of exact values needed to show in pie chart
export function getPieValues(wp) {
//1 => [1, 19, 10, 5, 5, 10]
//24 => [20, 4, 1, 5, 5, 10]
//31 => [20, 10, 1, 5, 5, 5, 10]
let result;
if (wp == 0) {
result = PIE_VALUES;
} else if (wp < totalPieValues(1)) {
result = [wp - totalPieValues(0), PIE_VALUES[0] - wp, PIE_VALUES[1], PIE_VALUES[2], PIE_VALUES[3], PIE_VALUES[4]];
} else if (wp == totalPieValues(1)) {
result = PIE_VALUES;
} else if (wp < totalPieValues(2)) {
result = [PIE_VALUES[0], wp - totalPieValues(1), PIE_VALUES[1] - (wp - PIE_VALUES[0]), PIE_VALUES[2], PIE_VALUES[3], PIE_VALUES[4]];
} else if (wp == totalPieValues(2)) {
result = PIE_VALUES;
} else if (wp < totalPieValues(3)) {
result = [PIE_VALUES[0], PIE_VALUES[1], wp - totalPieValues(2), PIE_VALUES[2] - (wp - totalPieValues(2)), PIE_VALUES[3], PIE_VALUES[4]];
} else if (wp == totalPieValues(3)) {
result = PIE_VALUES;
} else if (wp < totalPieValues(4)) {
result = [PIE_VALUES[0], PIE_VALUES[1], PIE_VALUES[2], wp - totalPieValues(3), PIE_VALUES[3] - (wp - totalPieValues(3)), PIE_VALUES[4]];
} else if (wp == totalPieValues(4)) {
result = PIE_VALUES;
} else if (wp < totalPieValues(5)) {
result = [PIE_VALUES[0], PIE_VALUES[1], PIE_VALUES[2], PIE_VALUES[3], wp - totalPieValues(4), PIE_VALUES[4] - (wp - totalPieValues(4))];
} else if (wp == totalPieValues(5)) {
result = PIE_VALUES;
}
return result;
}

This is super overkill
You can just iterate through the array and "eat" the index value and continue
function pieArray(inputArray, value){
let copyOfValue = value;
return inputArray.reduce((sum, input, index) => { // <-- index here
copyOfValue -= input;
if(copyOfValue > 0){
sum.push(input);
}else{
sum.push(input+copyOfValue);
sum.push(Math.abs(copyOfValue));
copyOfValue = Number.MAX_VALUE; //Hacky solution, just change value to max value
}
return sum;
}, []);
}
Tests
pieArray([20,10,5,5,10], 5) => [5, 15, 10, 5, 5, 10]
pieArray([20,10,5,5,10], 21) => [20, 1, 9, 5, 5, 10]
pieArray([20,10,5,5,10], 31) => [20, 10, 1, 4, 5, 10]

This is my approach. We iterate over our array, keeping track of our current value - and substracting from it as we push out each element to the output array.
There is 3 cases:
either our current count is >= input, so we just push and move on,
current count is 0, so we just push everything left
current count is < input, but more than 0 - in this case we split.
Here is the code:
function transform(input, array) {
const total = array.reduce((previous, current) => previous + current);
// It wasn't specified what should happen when the input > total, so we will just throw an error.
if (input > total) {
throw new Error('Input cannot be bigger than the sum of elements in the array.');
}
let current = input;
let result = [];
for (let i = 0; i < array.length; i++) {
if (current >= array[i]) {
result.push(array[i]);
current -= array[i];
} else if (current === 0) {
result.push(array[i]);
} else {
result.push(current, array[i] - current);
current = 0;
}
}
return result;
}

Some of these answers are a bit overcomplicated. If you use a recursive function, you can do this in just two lines of code.
const pie_values = [20,10,5,5,10];
// The challenge is to transform the above array based on integer input.
// (5) => [5, 15, 10, 5, 5, 10]
// (21) => [20, 1, 9, 5, 5, 10]
// (31) => [20, 10, 1, 4, 5, 10]
function reshape(num, vals) {
if (num < vals[0]) return [num, vals[0] - num, ...vals.slice(1)];
return [vals[0], ...reshape(num - vals[0], vals.slice(1))];
}
console.log(reshape(5, pie_values))
console.log(reshape(21, pie_values))
console.log(reshape(31, pie_values))
The key is realizing that if the amount you need to take is less than the next value, then you can take it from that next value and the remainder of the array will stay the same.
But if you need to take more than what's available, take as much as you can get from the first value, and then take that much less from the remainder of the array.
EDIT: Note that if the number you give is larger than the sum of all the pie values, this will recurse infinitely (leading to a stack overflow). To be totally safe, you should ensure that the value is less than the total sum before calling reshape.

You want something like that ? (Works with the examples you've given)
function getPieValues(integer_input) {
"use strict";
let final_arr = pie_values,
array_sum = pie_values.reduce((pv, cv) => pv + cv , 0);
if(integer_input !== 0 && integer_input !== array_sum) { // For the cases 50 and 0, the array won't be modified
for(let i = 0; i < pie_values.length; i++) {
if(pie_values[i] < integer_input) { // if the prompted number is bigger than the current value, we keep up
integer_input -= pie_values[i];
} else { // When it becomes smaller, we add the remainder at the front of the current value, then we modify the next value, and finally we break it so that it doesn't happen next
final_arr.splice(i, 0, integer_input);
final_arr[i+1] -= integer_input;
break;
}
}
}
return final_arr;
}
Edit : Made it a function, and made it work with 0 and 50 (sorry, first post ;-) )

This one's pretty simple and efficient. It doesn't iterate the whole array, only up to the point it needs to.
const pie_values = [20,10,5,5,10];
function pied(n) {
var i = 0;
var total = pie_values[0];
while (total < n && i < pie_values.length) {
i++;
total += pie_values[i];
}
if (i < pie_values.length) {
var diff = total - n;
if (diff > 0 && n > 0) {
return [].concat(
pie_values.slice(0, i), // the part of the array up to i
pie_values[i] - diff, // the amount we used of the last element we needed
diff, // the amount left over
pie_values.slice(i + 1) // the rest of the array after i
);
} else {
// just return a copy of the original array
return pie_values.slice();
}
} else {
// n was greater than the total of all elements of the array
return "went over";
}
}
console.log(pied(5));

Using vanilla Javascript for-loop
Look at this code snippet
const pie_values = [20, 10, 5, 5, 10];
var fn = (input) => {
let array = [];
for (var i = 0; i < pie_values.length; i++) {
var n = pie_values[i];
let calc = n - input;
if (calc > 0) {
array.push(n - calc); // Push how many used, i.e n = 20, input = 10.
array.push(calc); // Push the remaining after subtraction.
array = array.concat(pie_values.slice(i + 1)); // Add the remaining values from 'pie_values'
return array;
} else {
array.push(n); // Push all this number because was insufficient, i.e n = 20, input = 30
input = Math.abs(calc); // Remaining for the next iteration.
}
}
return array;
};
console.log(fn(5));
console.log(fn(21));
console.log(fn(31));
console.log(fn(0));
console.log(fn(50));

const pie_values = [20,10,5,5,10];
function rebaseArr(input){
var retArr = [];
var total = 0;
let isDone = false;
for(var i in pie_values){
let currentVal = pie_values[i];
total += currentVal;
if(total > input && !isDone){
let rem = total - input;
let rem1 = currentVal - rem;
rem1 !== 0 ? retArr.push(rem1) : 0;
retArr.push(rem);
isDone = true;
} else {
retArr.push(currentVal);
}
}
return retArr;
}
console.log(rebaseArr(31));
console.log(rebaseArr(1));
console.log(rebaseArr(10));
Can you please try with above code.
Hope it helps :)

I wouldn't normally advise recursion in JS however just for fun you may implement an Haskellesque pattern matching by using spread and rest operators through destructuring and may come up with something like below;
It wasn't clear to me what to do when the difference is zero so being a remarkably lazy person i choose to do nothing. (Last test won't return [20,10,5,0,5,10])
var extend = ([x,...xs],n) => n && x ? x > n ? [n, x-n, ...xs]
: [x, ...extend(xs, n-x)]
: [x,...xs],
pvs = [20,10,5,5,10];
console.log(extend(pvs,5));
console.log(extend(pvs,21));
console.log(extend(pvs,31));
console.log(extend(pvs,35));
.as-console-wrapper {
max-height : 100% !important
}

Related

Implement an algorithm to summarize the number of items within buckets of ranges

I am trying to write a function that takes an array of numbers (always ascendingly sorted) and an array of buckets where each bucket is a tuple (array of two items) that represents a range (no overlaps). Every adjacent tuple only diff by 1. For example, [[0, 59], [60, 90]]. And they are always sorted.
For example,
summarize( [0, 10, 60, 120],[[0, 59], [60, 90]]) gives us [2, 1] because within [0, 59] there are two elements 0 and 10 and between [60, 90] there is one element 60.
Here is my attempt:
function summarize(array, buckets) {
let i = 0
const results = Array.from({ length: buckets.length }, () => 0)
for (const item of array) {
if (item >= buckets[i][0] && item <= buckets[i][1]) results[i]++
else if (item > buckets[i][1] && i !== buckets.length - 1) {
if (item <= buckets[i + 1][0]) {
results[i + 1]++
i++
if (i === buckets.length) break
}
}
}
return results
}
The code seems to be working but it looks not clean and brittle. I wonder if there is another way to do it?
The time complexity should be dependent on two dimensions: the size of the first array (n), and the number of buckets (m). The best we can get is O(n+m), because certainly all values of the first array must be visited, and since the output has one entry per bucket, we must also do O(m) work to produce that output.
Your code is aiming for that time complexity, but it has an issue. For instance, the following call will not produce the correct result:
summarize([9], [[1, 3], [4, 6], [7, 9]])
The issue is that your code is not good at skipping several (more than one) bucket to find the place where a value should be bucketed. Concretely, both if conditions can be false, and then nothing happens with the currently iterated value -- it is not accounted for.
Since the output has the same size as the bucket list, we could consider mapping the bucket list to the output. Then the i index becomes an auxiliary index for the first array.
Here is how the code could look:
function summarize(array, buckets) {
let i = 0;
return buckets.map(([start, end]) => {
while (array[i] < start) i++;
let j = i;
while (array[i] <= end) i++;
return i - j;
});
}
// Example run
console.log(summarize([0, 10, 60, 120],[[0, 59], [60, 90]]));
Your code seems to rely on buckets being both non overlapping AND adjacent.
The code below only requires that each "bucket" array be in ascending order. As it is (with the "break" commented) it doesn't require the numbers to be in any order, and the buckets can overlap, etc.
HTH
function summarize(array, buckets) {
const results = new Array(buckets.length).fill(0);
for (i in array) {
for (j in buckets) {
if (array[i] >= buckets[j][0] && array[i] <= buckets[j][1]) {
results[j]++;
// break;
}
}
}
return results;
}
console.log(summarize([0, 10, 60, 120], [
[0, 59],
[60, 90]
]));
This doesn't require the input or buckets to be sorted and it allows the buckets to overlap:
summarize=(a,b)=>b.map(x=>a.filter(y=>y>=x[0]&&y<=x[1]).length)
Edit: The nisetama2 function in the following benchmark requires that the input and buckets are sorted and that the buckets do not overlap:
let nisetama=(a,b)=>b.map(x=>a.filter(y=>y>=x[0]&&y<=x[1]).length)
function nisetama2(a,b){
let min=b[0][0],max=b[0][1],n=0,out=Array(b.length).fill(0)
for(let i=0,l=a.length;i<l;i++){
let v=a[i]
while(v>max){if(n==b.length-1)return out;n++;min=b[n][0];max=b[n][1]}
if(v>=min)out[n]++
}
return out
}
function nistetama2_for_of(a,b){
let min=b[0][0],max=b[0][1],n=0,out=Array(b.length).fill(0)
for(let v of a){
while(v>max){if(n==b.length-1)return out;n++;min=b[n][0];max=b[n][1]}
if(v>=min)out[n]++
}
return out
}
function OP(array, buckets) {
let i = 0
const results = Array.from({ length: buckets.length }, () => 0)
for (const item of array) {
if (item >= buckets[i][0] && item <= buckets[i][1]) results[i]++
else if (item > buckets[i][1] && i !== buckets.length - 1) {
if (item <= buckets[i + 1][0]) {
results[i + 1]++
i++
if (i === buckets.length) break
}
}
}
return results
}
function WolfD(array, buckets) {
const results = new Array(buckets.length).fill(0);
for (i in array) {
for (j in buckets) {
if (array[i] >= buckets[j][0] && array[i] <= buckets[j][1]) {
results[j]++;
}
}
}
return results;
}
function WolfD_let(array, buckets) {
const results = new Array(buckets.length).fill(0);
for (let i in array) {
for (let j in buckets) {
if (array[i] >= buckets[j][0] && array[i] <= buckets[j][1]) {
results[j]++;
}
}
}
return results;
}
function trincot(array, buckets) {
let i = 0;
return buckets.map(([start, end]) => {
while (array[i] < start) i++;
let j = i;
while (array[i] <= end) i++;
return i - j;
});
}
let a=Array(1e4).fill().map((_,i)=>i)
let b=Array(1e2).fill().map((_,i)=>[i*100,(i+1)*100-1])
let opt=['nisetama','nisetama2','nisetama2_for_of','OP','WolfD','WolfD_let','trincot']
opt.sort(()=>Math.random()-.5)
for(let opti of opt){
let t1=process.hrtime.bigint()
eval(opti+'(a,b)')
let t2=process.hrtime.bigint()
console.log(t2-t1+' '+opti)
}
Here's the median time of a thousand runs in ms (updated to add trincot's function):
0.43 trincot
0.67 nisetama2
3.10 nisetama2_for_of
4.03 OP
12.66 nisetama
45.32 WolfD_let
201.55 WolfD
Wolf D.'s solution became about 4 times faster when I modified it to use block-scoped variables.
In order to reduce the effect of optimizations for running the same code multiple times, I ran the benchmark like for i in {0..999};do node temp.js;done instead of running each option a thousand times inside the script.

Find the length of the longest continuous stretch of a single number in an array

Here is the full description of the problem:
//Write a function which takes a list of numbers and returns the length of the
// longest continuous stretch of a single number. For example, on the input [1,7,7,3],
// the correct return is 2 because there are two 7's in a row. On the input
// [1,7,7,3,9,9,9,4,9], the correct return is 3, since there are three 9’s in a row.
Here is my solution:
let sequenceChecker = (arr) => {
let finalNum = 0;
let secondPass = false;
const bigestNumber = arr.sort()[arr.length - 1]
arr.forEach(num => {
if(num === bigestNumber){
finalNum++
}
else if(num != bigestNumber && finalNum > 0 ){
secondPass = true
}
else if (secondPass == true && num === bigestNumber){
finalNum = 0
}
})
return finalNum
}
console.log((sequenceChecker([1,7,7,3])).toString());
console.log((sequenceChecker([1,7,7,3,9,9,9,4,9])).toString());
I really don't understand why my code won't solve this problem. The first else if the statement never gets executed but the statement should evaluate to true and the code inside should execute.
I would do like this. Create one function that return the length of the stretch by a number, It will return -1 if the array does not include the number.
Then, I would just compare the returns for each number and return the biggest number.
const getLongestStretch = (arr, number) => {
if (!arr.includes(number)) return -1;
const filteredArrayLength = arr.filter(
(n, i) => n === number && (arr[i - 1] === number || arr[i + 1] === number)
).length;
if (filteredArrayLength === 0) return 1;
return filteredArrayLength;
};
const sequenceChecker = (arr) =>
arr.reduce(
(result, number) => Math.max(result, getLongestStretch(arr, number)),
1
);
arr = [1, 7, 7, 3, 9, 9, 9, 4, 9, 9, 9, 9, 9];
let count = 1;
let journal = new Map();
for (let i = 0; i < arr.length; i++) {
if(arr[i] == arr[i+1]) {
++count;
}else {
if(journal.get(arr[i]) < count || !journal.get(arr[i])) {
journal.set(arr[i], count);
}
count = 1;
}
}
console.log(journal) // Map(5) {1 => 1, 7 => 2, 3 => 1, 9 => 5, 4 => 1}
Try this:
i checking the number of the sequence with the previous e increment the count in a variable
var arr = [1,7,7,3,9,9,9,4,4,4,4,4,4,9,9,9,9,9,9,9];
var prev;
var count=0;
var finalnum=0;
$.each(arr, function(index,num){
if(prev != num) {
prev = num;
finalnum = (finalnum <= count ? count : finalnum);
count = 1;
}
else {
count = count + 1;
}
if(index == (arr.length - 1))
{
finalnum = (finalnum <= count ? count : finalnum);
}
});
console.log(finalnum);

Get permutations of all the array [1, 1, 1, 1, 1, 0, 0, 0, 0]

I am trying to create a script which generates all the various permutations of binary switches where there should be 5 1 and 4 0. And the array should be of size 9.
I tried the following code. The conditions for the permutations are:
1. The array set should be unique.
2. Not more than 3 1 should be next to each other
const row = [1, 1, 1, 1, 1, 0, 0, 0, 0];
const list = [];
const fullList = [];
// To make sure that no more than 3 `1` are next to each other
const isRowValid = (row) => {
let isValid = true;
for(let i = 0; i+2 < row.length; i++) {
if(row[i] === 1 && row[i+1] === 1 && row[i+2] === 1) {
isValid = false;
break;
}
}
return isValid;
}
const combinations = (row, baseIndex, currentIndex, iterationLevel, list) => {
if(currentIndex > row.length - iterationLevel) {
baseIndex++;
currentIndex = 0;
}
if(baseIndex + iterationLevel > row.length) {
baseIndex = 0;
iterationLevel++;
}
if(iterationLevel === 5) {
return;
}
let rowCopy = [...row]
if(baseIndex > currentIndex ) {
let first = [...row.slice(0, currentIndex)];
let second = [...row.slice(currentIndex)];
let value = second.splice(baseIndex - currentIndex, iterationLevel);
rowCopy = [...first, ...value, ...second]
} else if(baseIndex < currentIndex) {
let first = [...row.slice(0, currentIndex + iterationLevel)];
let second = [...row.slice(currentIndex + iterationLevel)];
let value = first.splice(baseIndex, iterationLevel);
rowCopy = [...first, ...value, ...second];
}
if(isRowValid(rowCopy)) {
list.push(rowCopy);
}
console.log(rowCopy);
combinations(row, baseIndex, currentIndex + 1, iterationLevel, list);
}
combinations(row, 0, 0, 1, list);
list.forEach(l => combinations(l, 0, 0, 1, fullList));
// To remove duplicates
for(let i = 0; i < fullList.length; i++) {
const base = fullList[i]
for(let j = i + 1; j < fullList.length; j++) {
const isSame = fullList[j].every((l, m) => base[m] === l);
if(isSame) {
fullList[j] = [];
}
}
}
let filtered = fullList.filter(l => l.length !== 0);
console.log(filtered.length);
filtered.slice(0, 100).map(i => console.log(i));
console.log(fullList.length);
JS Bin
If I understand correctly, you meant permutations rather than combinations, where in each permutation there shouldn't be more than 3 sequential switches that are "on".
Whenever you have to generate permutations or combinations you can use a recursive backtracking algorithm.
The idea is simple, at every step you follow the possible choices until a base condition is met (e.g. permutation is complete because perm.length === switchCount). When taking a step you reflect that choice on the problem's state and when the recursive call returns you undo these effects.
In order to determine what choices can be made at each step we need to keep track of the problem's state. Here we only need to know how many on/off switches we have left and how many sequential on switches we have so far (seqOn).
const perms = permute(5, 4);
console.log(perms.length);
console.log(perms);
function permute(on, off) {
const switchCount = on + off;
const perm = [], perms = [];
p(on, off, 0);
return perms;
function p(on, off, seqOn) {
if (perm.length === switchCount) {
perms.push([...perm]);
return;
}
if (on && seqOn < 3) {
perm.push(1);
p(on - 1, off, seqOn + 1);
perm.pop();
}
if (off) {
perm.push(0);
p(on, off - 1, 0);
perm.pop();
}
}
}
If we have many permutations to enumerate we can save on memory by using generators too. Here I yield the same perm array which saves the O(n) time copy. As long as you don't need to keep a copy and just enumerate switches it's fine.
for (const perm of permute(5, 4)) {
console.log(perm);
}
function* permute(on, off) {
const switchCount = on + off;
const perm = [];
yield* p(on, off, 0);
function* p(on, off, seqOn) {
if (perm.length === switchCount) {
yield perm;
return;
}
if (on && seqOn < 3) {
perm.push(1);
yield* p(on - 1, off, seqOn + 1);
perm.pop();
}
if (off) {
perm.push(0);
yield* p(on, off - 1, 0);
perm.pop();
}
}
}

Check if an array is descending, ascending or not sorted?

I'm just beginning with programming using javascript and I need to practice some questions to get EXP with the logic of code build.
I got this question for homework but I can't make it work for some reason, even though it seems "logic" to me.
Check if an array is descending, ascending or not sorted using loops.
I'm just a noob so please try and help me figure this out as I only got to loops in my studies (:
this is the code I wrote:
var array = [1, 2, 3, 7 ];
var d = 0;
var c =0 ;
var b = 1;
var a = 0;
for (var i = 1; i <= array.length; i++)
{
if (array[c]<array[b] && a!== -1 ){
d = -1;
c =c+1;
b = b+1;
if(c==array.length){
console.log("asc");
break;
}else{
continue;
}
} else if (array[c]>array[b] && d!==-1 ){
a = -1;
d= d+1;
b = b+1;
if(i=array.length){
console.log("dsc");
break;
}else{continue;}
} else{
console.log("unsorted array");
break;
}
}
Array.prototype.every passes its predicate an index, which you can use to get an element’s predecessor:
function isAscending(arr) {
return arr.every(function (x, i) {
return i === 0 || x >= arr[i - 1];
});
}
Here, we’re checking that every item (x) is greater than or equal to the item before it (arr[i - 1]) or has no item before it (i === 0).
Flip >= to <= for isDescending.
"Check if an array is descending, ascending or not sorted using loops"
// define the array
var array = [1,2,3,7];
// keep track of things
var isDescending = true;
var isAscending = true;
// we're looking ahead; loop from the first element to one before the last element
for (var i=0, l=array.length-1; i<l; i++)
{
////////////////////////////////////////////////////////////
// log to the console to show what's happening for each loop iteration
// this is the ith iteration
console.log("loop iteration %s", i);
// breaking isDescending down:
// is this value greater than the next value?
console.log("A: (%s > %s) = %s", array[i], array[i+1], (array[i] > array[i+1]));
// have all values been descending so far?
console.log("B: isDescending: %s", isDescending);
// if this value is greater than the next and all values have been descending so far, isDescending remains true. Otherwise, it's set to false.
console.log("are A and B both true? %s", (isDescending && (array[i] > array[i+1])));
// add a line break for clarity
console.log("");
////////////////////////////////////////////////////////////
// true if this is greater than the next and all other so far have been true
isDescending = isDescending && (array[i] > array[i+1]);
// true if this is less than the next and all others so far have been true
isAscending = isAscending && (array[i] < array[i+1]);
}
if (isAscending)
{
console.log('Ascending');
}
else if (isDescending)
{
console.log('Descending');
}
else
{
console.log('Not Sorted');
}
This is a question that requires some sort of loop, with several if statements because there are several cases you need to tackle:
Array is empty or has only one element.
All items in the array are equal
Array is ascending - delta between 2 elements > 0, but some deltas may be 0
Array is descending - delta between 2 elements < 0, but some deltas may be 0
Not sorted - some deltas are > 0, and some are < 0
Depending on how the sorted is defined in the question, cases 1 & 2 might be regarded as unsorted as well.
function findSortOrder(arr) {
if(arr.length < 2) { // case 1
return 'not enough items'; // can also be 'unsorted'
}
var ascending = null;
var nextArr = arr.slice(1); // create an array that starts from the 2nd element of the original array
for(var i = 0; i < nextArr.length; i++) {
if (nextArr[i] === arr[i]) { // neutral - do nothing
} else if(ascending === null) { // define the the direction by the 1st delta encountered
ascending = nextArr[i] > arr[i];
} else if (ascending !== nextArr[i] > arr[i]) { // case 5
return 'unsorted';
}
}
if(ascending === null) { // case 2
return 'all items are equal'; // can also be 'unsorted'
}
return ascending ? 'ascending' : 'descending'; // cases 3 & 4
}
console.log(findSortOrder([1])); // case 1
console.log(findSortOrder([1, 1, 1, 1])); // case 2
console.log(findSortOrder([1, 1, 2, 3, 7, 7])); // case 3
console.log(findSortOrder([7, 2, 2, 1])); // case 4
console.log(findSortOrder([7, 2, 1, 3, 2, 1])); // case 5
You could use a copy from the second element and check the predecessor for the wanted sort order.
function checkArray(array) {
var aa = array.slice(1);
if (!aa.length) {
return "Just one element";
}
if (aa.every((a, i) => array[i] > a)) {
return "Ascending";
}
if (aa.every((a, i) => array[i] < a)) {
return "Descending";
}
return "Unsorted";
}
console.log(checkArray([1, 2, 3, 4, 5]));
console.log(checkArray([5, 4, 3, 2, 1]));
console.log(checkArray([3, 1, 4, 2, 5]));
console.log(checkArray([42]));
function isAscending(arr = []) {
for (let i = 0; i < arr.length; i++) {
if (arr[i + 1] <= arr[i]) {
return false;
}
}
return true;
}
function isAscending(arr) {
return arr
.slice(1)
.every((num,i) => num >= arr[i]);
}
console.log(isAscending([1,2,3])); // true
console.log(isAscending([12,38,25])); // false
console.log(isAscending([103,398,52,629])); // false
arr.slice(1) --> allows us to start iteration at index 1 instead of 0
we'll iterate over "arr" with "every" & compare the current "num" against the previous "num" with "arr[i]"
function findOrder(array) {
var asc = true;
var desc = true;
if(array.length < 2){
return 'array is too small'
}
for(var i=1, len=array.length;i<len;i++){
//if current element is bigger than previous array is not descending
if(array[i]>array[i-1]){
desc = false;
//if current element is smaller than previous array is not ascending
}else if(array[i]<array[i-1]){
asc = false;
}
if(!asc && !desc){
return 'not sorted'
}
}
if(asc && desc){
return 'array values are equal'
}else if (asc){
return 'array is ascending'
}else {
return 'array is descending'
}
}
Actually we may do even more by creating an Array method like natureOf which can tell us more about the nature of the array than just ascending, descendig or flat. Array.natureOf() shall give us a value between -1 and 1. If it is -1 the array is fully descending and 1 would mean fully ascending of course. Any value inbetween would give us the inclination of the array. As you would guess 0 would mean totally random or flat. (it's fairly easy to insert the logic to distinguish random from flat if that's also needed)
Array.natureOf = function(a){
var nature = a.reduce((n,e,i,a) => i && a[i-1] !== e ? a[i-1] < e ? (n.asc++, n)
: (n.dsc++, n)
: n, {asc:0, dsc:0});
return (nature.asc - nature.dsc) / (a.length-1);
};
var arr = [1,2,3,4,5,6,7,8,9,10,7,11,13,14,15],
brr = Array.from({length:2000000}, _ => ~~(Math.random()*1000000000));
console.log(`The nature of "arr" array is ${Array.natureOf(arr)}`);
console.log(`The nature of "brr" array is ${Array.natureOf(brr)}`);
console.log(checkSort([1, 2, 3, 3, 4, 5]));
console.log(checkSort([5, 4, 3, 2, 1, 1]));
console.log(checkSort([2, 5, 8, 9, 4, 6]));
function checkSort(arr){
var isDescending, isAscending;
isDescending = isAscending = true;
const len = arr.length - 1;
for (var index = 0 ; index < len ; index++){
if(isAscending)
isAscending = arr[index] <= arr[index + 1];// '<=' so as to check for same elements
if(isDescending)
isDescending = arr[index] >= arr[index + 1];//'<=' so as to check for same elements
}
var result = "Array is ";
if (isAscending)
return result.concat("sorted in ascending order");
if (isDescending)
return result.concat("sorted in descending order");
return result.concat("not sorted");
const isAscending=(arr)=>{
for(let i=0;i<arr.length;i++){
if(arr[i+1]<arr[i]){ return false } } return true }

Javascript reduce() until sum of values < variable

I am fetching an array of video durations (in seconds) from a JSON file in Javascript, that, to simplify, would look like this:
array = [30, 30, 30]
I would like to add each value to the previous value until a condition is met (the sum being less than a variable x) and then to get both the new value and the index position in the array of the video to play.
For example if x=62 (condition), I would like the first two values in the array to be added (from my understanding reduce() is appropriate here), and the index = 2 (the second video in the array).
I've got the grasp of reduce():
var count = array.reduce(function(prev, curr, index) {
console.log(prev, curr, index);
return prev + curr;
});
But can't seem to get beyond this point.. Thanks
You could use Array#some, which breaks on a condition.
var array = [30, 30, 30],
x = 62,
index,
sum = 0;
array.some(function (a, i) {
index = i;
if (sum + a > x) {
return true;
}
sum += a;
});
console.log(index, sum);
With a compact result and this args
var array = [30, 30, 30],
x = 62,
result = { index: -1, sum: 0 };
array.some(function (a, i) {
this.index = i;
if (this.sum + a > x) {
return true;
}
this.sum += a;
}, result);
console.log(result);
var a = [2,4,5,7,8];
var index;
var result = [0, 1, 2, 3].reduce(function(a, b,i) {
var sum = a+b;
if(sum<11){
index=i;
return sum;
}
}, 2);
console.log(result,index);
What about using a for loop? This is hack-free:
function sumUntil(array, threshold) {
let i
let result = 0
// we loop til the end of the array
// or right before result > threshold
for(i = 0; i < array.length && result+array[i] < threshold; i++) {
result += array[i]
}
return {
index: i - 1, // -1 because it is incremented at the end of the last loop
result
}
}
console.log(
sumUntil( [30, 30, 30], 62 )
)
// {index: 1, result: 60}
bonus: replace let with var and it works on IE5.5
You could do
var limit = 60;
var array = [30,30,30];
var count = array.reduce(function(prev, curr, index) {
var temp = prev.sum + curr;
if (index != -1) {
if (temp > limit) {
prev.index = index;
} else {
prev.sum = temp;
}
}
return prev;
}, {
sum: 0,
index: -1
});
console.log(count);
What about this : https://jsfiddle.net/rtcgpgk2/1/
var count = 0; //starting index
var arrayToCheck = [20, 30, 40, 20, 50]; //array to check
var condition = 100; //condition to be more than
increment(arrayToCheck, count, condition); //call function
function increment(array, index, conditionalValue) {
var total = 0; //total to add to
for (var i = 0; i < index; i++) { //loop through array up to index
total += array[i]; //add value of array at index to total
}
if (total < conditionalValue) { //if condition is not met
count++; //increment index
increment(arrayToCheck, count, condition); //call function
} else { //otherwise
console.log('Index : ', count) //log what index condition is met
}
}
// define the max outside of the reduce
var max = 20;
var hitIndex;
var count = array.reduce(function(prev, curr, index) {
let r = prev + curr;
// if r is less than max keep adding
if (r < max) {
return r
} else {
// if hitIndex is undefined set it to the current index
hitIndex = hitIndex === undefined ? index : hitIndex;
return prev;
}
});
console.log(count, hitIndex);
This will leave you with the index of the first addition that would exceed the max. You could try index - 1 for the first value that did not exceed it.
You can create a small utility method reduceWhile
// Javascript reduceWhile implementation
function reduceWhile(predicate, reducer, initValue, coll) {
return coll.reduce(function(accumulator, val) {
if (!predicate(accumulator, val)) return accumulator;
return reducer(accumulator, val);
}, initValue)
};
function predicate(accumulator, val) {
return val < 6;
}
function reducer(accumulator, val) {
return accumulator += val;
}
var result = reduceWhile(predicate, reducer, 0, [1, 2, 3, 4, 5, 6, 7])
console.log("result", result);

Categories

Resources