Loop Through Nested Array Elements With JavaScript - javascript

I'm trying to loop through nested arrays to determine if an element in the array is either "open" or "senior":
function openOrSenior(data) {
for (let i = 0; i <= data.length; i++) {
let dataElement = data[i];
for (let j = 0; j <= dataElement.length; j++) {
if (dataElement[0] >= 55 && dataElement[1] > 7) {
return ["Senior"];
}
return ["Open"];
}
}
}
Given the input of
[[18, 20],[45, 2],[61, 12],[37, 6],[21, 21],[78, 9]]
The function should output
["Open", "Open", "Senior", "Open", "Open", "Senior"]
But currently it looks like it's only looping through the first element in the main array ([18, 20]) because my function is only returning:
["Open"]
Why is this function failing to continue to loop through the other nested arrays and return either "Open" or "Senior"? Possibly a problem with the scope?
https://www.codewars.com/kata/categorize-new-member/train/javascript
I was trying to implement what I found here, which suggested a for-loop within a for-loop.

You are returning whenever the check succeeds or not, and you've got a redundant for loop. You should iterate the array with a single for loop, and according to the check push Senior or Open to the result array. In the end return the result array.
function openOrSenior(data) {
const result = [];
for (let i = 0; i < data.length; i++) {
const dataElement = data[i];
if (dataElement[0] >= 55 && dataElement[1] > 7) {
result.push("Senior");
} else result.push("Open");
}
return result;
}
console.log(openOrSenior([[18, 20],[45, 2],[61, 12],[37, 6],[21, 21],[78, 9]]));
Or you can use Array.map():
const openOrSenior = data =>
data.map(([a, b]) =>
a >= 55 && b > 7 ? 'Senior' : 'Open'
)
console.log(openOrSenior([[18, 20],[45, 2],[61, 12],[37, 6],[21, 21],[78, 9]]))

You need only a single loop and an array for the values.
function openOrSenior(data) {
var result = [];
for (let i = 0; i < data.length; i++) {
let [left, right] = data[i];
result.push(left >= 55 && right > 7
? "Senior"
: "Open"
);
}
return result;
}
console.log(openOrSenior([[18, 20], [45, 2], [61, 12], [37, 6], [21, 21], [78, 9]]));

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.

Combining 2 Callback Functions into One

Here is the question...
"Add code to the function eitherCallback in the place marked "ADD CODE HERE" in order to achieve the desired console logs. The result of using eitherCallback to combine two callbacks into one callback and then passing that one callback into filterArray should match the results of simply passing the two callbacks into eitherFilter in the previous challenge."
Here is the previous challenge's solution which I know works...
function eitherFilter(array, callback1, callback2) {
// ADD CODE HERE
const newArr = [];
for (let i = 0; i < array.length; i++) {
if (callback1(array[i]) || callback2(array[i])) {
newArr.push(array[i]);
}
}
return newArr;
}
// Uncomment these to check your work!
const arrOfNums = [10, 35, 105, 9];
const integerSquareRoot = n => Math.sqrt(n) % 1 === 0;
const over100 = n => n > 100;
console.log(eitherFilter(arrofNums, integerSquareRoot, over100)); // should log: [105, 9]
Here is the code given...
function eitherCallback(callback1, callback2) {
// ADD CODE HERE
}
// Uncomment these to check your work!
function filterArray(array, callback) {
const newArray = [];
for (let i = 0; i < array.length; i += 1) {
if (callback(array[i], i, array)) newArray.push(array[i]);
}
return newArray;
}
const arrOfNums = [10, 35, 105, 9];
const integerSquareRoot = n => Math.sqrt(n) % 1 === 0;
const over100 = n => n > 100;
const intSqRtOrOver100 = eitherCallback(integerSquareRoot, over100);
console.log(filterArray(arrOfNums, intSqRtOver100)); // should log: [105, 9]
I am confused as to what to do. Can anyone give me some tips? I do not know how to even start to answer it!
Thanks in advance...
You don't need much - all you need is to make eitherCallback a higher-order function that takes the two callbacks as initial argument, and executes and returns the logic you're carrying out here:
callback1(array[i]) || callback2(array[i])
Like:
const eitherCallback = (callback1, callback2) => x => callback1(x) || callback2(x);
// Uncomment these to check your work!
function filterArray(array, callback) {
const newArray = [];
for (let i = 0; i < array.length; i += 1) {
if (callback(array[i], i, array)) newArray.push(array[i]);
}
return newArray;
}
const arrOfNums = [10, 35, 105, 9];
const integerSquareRoot = n => Math.sqrt(n) % 1 === 0;
const over100 = n => n > 100;
const intSqRtOrOver100 = eitherCallback(integerSquareRoot, over100);
console.log(filterArray(arrOfNums, intSqRtOrOver100)); // should log: [105, 9]
You could also try:
function eitherCallback(callback1, callback2) {
// ADD CODE HERE
return (element, i, array) => {
//element representing array[i]
return callback1(element, i, array) || callback2(element, i, array);
}
}
The idea is for eitherCallback to return a truly value for either callback called within the function. Also, I believe the "x" in the previous solution from certain performance represents the arguments array[i], i and array. It's just a cleaner way to write it out with x instead of having to repeat it all. Keeping things DRY.

How do I access this data

I'm currently going through codesmith's CSX and I came across this problem. The idea is to create a function that takes two callbacks as arguments and if any input passed either one of those functions return the function that passed. I can't figure out how to access the input data. Sorry if I'm not clear enough.
function eitherCallback(cb1, cb2) {
// ADD CODE HERE
}
//please try to solve without editing the code underneath
function filterArray(array, callback) {
const newArray = [];
for (let i = 0; i < array.length; i += 1) {
if (callback(array[i], i, array)) newArray.push(array[i]);
}
return newArray;
}
const arrOfNums = [10, 35, 105, 9];
const integerSquareRoot = n => Math.sqrt(n) % 1 === 0;
const over100 = n => n > 100;
const intSqRtOrOver100 = eitherCallback(integerSquareRoot, over100);
console.log(filterArray(arrOfNums, intSqRtOrOver100)); // should log: [105, 9]
You just need to make eitherCallback return a function which takes an array item and tests whether it passes cb1's test or cb2's test:
function eitherCallback(cb1, cb2) {
return item => cb1(item) || cb2(item);
}
//please try to solve without editing the code underneath
function filterArray(array, callback) {
const newArray = [];
for (let i = 0; i < array.length; i += 1) {
if (callback(array[i], i, array)) newArray.push(array[i]);
}
return newArray;
}
const arrOfNums = [10, 35, 105, 9];
const integerSquareRoot = n => Math.sqrt(n) % 1 === 0;
const over100 = n => n > 100;
const intSqRtOrOver100 = eitherCallback(integerSquareRoot, over100);
console.log(filterArray(arrOfNums, intSqRtOrOver100)); // should log: [105, 9]
Another way to think of it - if you have two callbacks and you want to filter an array by whether an item passes either callback, you would do:
arr.filter(
item => cb1(item) || cb2(item)
);
This is the same sort of thing, except that the function is funneled through some slightly confusing levels of additional abstraction.

How can I check if an array includes another array? [duplicate]

I needed 2d arrays, so I made a nested array since JavaScript doesn't allow them.
They look like this:
var myArray = [
[1, 0],
[1, 1],
[1, 3],
[2, 4]
]
How can I check if this array includes a specific element (i.e. one of these [0,1] arrays) in vanilla JS?
Here is what I tried, with no success (everything returns false) (EDIT: I included the answers in the snippet):
var myArray = [
[1, 0],
[1, 1],
[1, 3],
[2, 4]
]
var itemTrue = [2, 4];
var itemFalse = [4, 4];
function contains(a, obj) {
var i = a.length;
while (i--) {
if (a[i] === obj) {
return true;
}
}
return false;
}
// EDIT: first answer's solution
function isArrayInArray(x, check) {
for (var i = 0, len = x.length; i < len; i++) {
if (x[i][0] === check[0] && x[i][1] === check[1]) {
return true;
}
}
return false;
}
// EDIT: accepted answer's solution
function isArrayInArray2(x, check) {
var result = x.find(function(ele) {
return (JSON.stringify(ele) === JSON.stringify(check));
})
return result !=null
}
console.log("true :" + myArray.includes(itemTrue));
console.log("false :" + myArray.includes(itemFalse));
console.log("true :" + (myArray.indexOf(itemTrue) != -1));
console.log("false :" + (myArray.indexOf(itemFalse) != -1));
console.log("true :" + contains(myArray, itemTrue));
console.log("false :" + contains(myArray, itemFalse));
// EDIT: first answer's solution
console.log("true :" + isArrayInArray(myArray, itemTrue));
console.log("false :" + isArrayInArray(myArray, itemFalse));
// EDIT: accepted answer's solution
console.log("true :" + isArrayInArray2(myArray, itemTrue));
console.log("false :" + isArrayInArray2(myArray, itemFalse));
It could look like duplicate but I couldn't find a similar question. If it is, feel free to tag it as such.
Short and easy, stringify the array and compare as strings
function isArrayInArray(arr, item){
var item_as_string = JSON.stringify(item);
var contains = arr.some(function(ele){
return JSON.stringify(ele) === item_as_string;
});
return contains;
}
var myArray = [
[1, 0],
[1, 1],
[1, 3],
[2, 4]
]
var item = [1, 0]
console.log(isArrayInArray(myArray, item)); // Print true if found
check some documentation here
A nested array is essentially a 2D array, var x = [[1,2],[3,4]] would be a 2D array since I reference it with 2 index's, eg x[0][1] would be 2.
Onto your question you could use a plain loop to tell if they're included since this isn't supported for complex arrays:
var x = [[1,2],[3,4]];
var check = [1,2];
function isArrayInArray(source, search) {
for (var i = 0, len = source.length; i < len; i++) {
if (source[i][0] === search[0] && source[i][1] === search[1]) {
return true;
}
}
return false;
}
console.log(isArrayInArray(x, check)); // prints true
Update that accounts for any length array
function isArrayInArray(source, search) {
var searchLen = search.length;
for (var i = 0, len = source.length; i < len; i++) {
// skip not same length
if (source[i].length != searchLen) continue;
// compare each element
for (var j = 0; j < searchLen; j++) {
// if a pair doesn't match skip forwards
if (source[i][j] !== search[j]) {
break;
}
return true;
}
}
return false;
}
console.log(isArrayInArray([[1,2,3],[3,4,5]], [1,2,3])); // true
You can't do like that .instance you have to do some thing by your own ..
first you have to do a foreach from your array that you want to search and run 'compareArray' function for each item of your array .
function compareArray( arrA, arrB ){
//check if lengths are different
if(arrA.length !== arrB.length) return false;
for(var i=0;i<arrA.length;i++){
if(arrA[i]!==arrB[i]) return false;
}
return true;
}
Here is an ES6 solution:
myArray.some(
r => r.length == itemTrue.length &&
r.every((value, index) => itemTrue[index] == value)
);
Check the JSFiddle.
Take a look at arrow functions and the methods some and every of the Array object.
The code provided by D. Young's comment that checks for any length array is faulty. It only checks if the first element is the same.
A corrected version of D. Young's comment:
function isArrayInArray(source, search) {
var searchLen = search.length;
for (var i = 0, len = source.length; i < len; i++) {
// skip not same length
if (source[i].length != searchLen) continue;
// compare each element
for (var j = 0; j < searchLen; j++) {
// if a pair doesn't match skip forwards
if (source[i][j] !== search[j]) {
break;
} else if (j == searchLen - 1) {return true}
}
}
return false;
}
For those who are interested in finding an array inside another and get back an index number, here's a modified version of mohamed-ibrahim's answer:
function findArrayInArray(innerArray, outerArray) {
const innerArrayString = JSON.stringify(innerArray);
let index = 0;
const inArray = outerArray.some(function (element) {
index ++;
return JSON.stringify(element) === innerArrayString;
});
if (inArray) {
return index - 1;
} else {
return -1;
}
}
findArrayInArray([1, 2, 3], [[3, .3], [1, 2, 3], [2]]); // 1
findArrayInArray([1, 2, 3], [[[1], 2, 3], [2]]) // -1
This function returns the index of the array you are searching inside the outer array and -1 if not found.
Checkout this CodePen.

Check if Javascript array values are in ascending order

Say I have an array of integers in Javascript, that I would like to check if all of its values are in ascending order. What i want is to save the array key in another array in case the algorithm finds a value that is lower (or equal) not only comparing the immediate previous one, but also comparing any value that is before it.
What I did was this:
arr = [], nonvalid = [];
for (var j = 1; j < arr.length; j++){
if ( arr[j+1] <= arr[j] ){
nonvalid.push(j);
}
}
Obviously the above algorightm checks only for values that are lower comparing the one before it.
An array might contain values like these:
arr = 1, 2, 3, 10, 5, 11, 12, 2, 4, 25
The non valid values are the bold ones. If I run the above loop, it won't "catch" the second last one (4), because it's higher than its closest left brother, but not that high than its all left brothers.
EDIT:
Tried the following solutions and none return all the nonvalid values for this array except mine . :(
They returned the last two values correctedly, but not the second one.
I don't understand why though.
[24398, 24397, 25004, 25177, 26302, 28036, 29312, 29635, 29829, 30476, 32595, 33732, 34995, 36047, 36363, 37310, 38022, 38882, 40746, 41212, 42846, 43588, 44029, 44595, 44846, 45727, 46041, 47293, 48002, 48930, 49858, 51184, 51560, 53895, 54247, 54614, 55713, 56813, 57282, 57480, 57875, 58073, 58403, 60321, 61469, 62051, 62310, 62634, 63217, 64505, 65413, 65677, 65940, 66203, 66572, 67957, 68796, 68964, 69098, 69233, 69435, 69759, 71496, 72577, 72823, 73007, 73252, 73743, 73866, 76405, 77037, 77416, 77669, 79691, 80885, 81339, 81794, 82067, 82431, 83244, 84861, 86836, 88632, 89877, 90296, 91049, 91885, 92351, 92614, 93141, 93733, 93930, 94531, 95206, 95882, 96895, 97732, 97973, 99261, 99422, 99583, 100332, 100599, 101666, 102066, 102600, 103504, 104432, 105174, 107216, 109085, 110181, 110679, 111177, 111988, 112553, 113005, 113457, 600, 600]
One other very nice functional way of doing this could be;
var isAscending = a => a.slice(1)
.map((e,i) => e > a[i])
.every(x => x);
console.log(isAscending([1,2,3,4]));
console.log(isAscending([1,2,5,4]));
Nice code but there are redundancies in it. We can further simplify by consolidating .map() and .every() into one.
var isAscending = a => a.slice(1)
.every((e,i) => e > a[i]);
console.log(isAscending([1,2,3,4]));
console.log(isAscending([1,2,5,4]));
Keep track of the largest value you have seen (see the fiddle):
function find_invalid_numbers(arr) {
var nonvalid, i, max;
nonvalid = [];
if (arr.length !== 0) {
max = arr[0];
for (i = 1; i < arr.length; ++i) {
if (arr[i] < max) {
nonvalid.push(arr[i]);
} else {
max = arr[i];
}
}
}
return nonvalid;
}
When you find an element out of order, look at the next elements until they are no longer out of order relative to the element before the out of order one.
Add the out of order elements to the second array and continue from the new in order element.
var outs= [], L= A.length, i= 0, prev;
while(i<L){
prev= A[i];
while(A[++i]<prev) outs.push(i);
}
alert(outs)
Why not compare with the last known good number?
var arr = [1, 2, 3, 10, 5, 11, 12, 2, 4, 25],
nonvalid = [],
lastGoodValue = 0;
for (var j = 1; j < arr.length; j++) {
if (j && arr[j] <= lastGoodValue) {
//if not the first number and is less than the last good value
nonvalid.push(arr[j]);
} else {
//if first number or a good value
lastGoodValue = arr[j];
}
}
console.log(arr, nonvalid)
DEMO
var arr = [24398, 24397, 25004, 25177, 26302, 28036, 29312, 29635, 29829, 30476, 32595, 33732, 34995, 36047, 36363, 37310, 38022, 38882, 40746, 41212, 42846, 43588, 44029, 44595, 44846, 45727, 46041, 47293, 48002, 48930, 49858, 51184, 51560, 53895, 54247, 54614, 55713, 56813, 57282, 57480, 57875, 58073, 58403, 60321, 61469, 62051, 62310, 62634, 63217, 64505, 65413, 65677, 65940, 66203, 66572, 67957, 68796, 68964, 69098, 69233, 69435, 69759, 71496, 72577, 72823, 73007, 73252, 73743, 73866, 76405, 77037, 77416, 77669, 79691, 80885, 81339, 81794, 82067, 82431, 83244, 84861, 86836, 88632, 89877, 90296, 91049, 91885, 92351, 92614, 93141, 93733, 93930, 94531, 95206, 95882, 96895, 97732, 97973, 99261, 99422, 99583, 100332, 100599, 101666, 102066, 102600, 103504, 104432, 105174, 107216, 109085, 110181, 110679, 111177, 111988, 112553, 113005, 113457, 600, 600],
nonvalid = [],
max = arr[0];
for(var j=0; j<arr.length; j++){
var test= arr[j+1]<=max ? nonvalid.push(arr[j+1]) : max=arr[j];
}
alert(nonvalid); // 24397, 600, 600
a simple functional way to do it inline without loops or variables:
arr.filter(function(a,b,c){
return Math.max.apply(Math, c.slice(0,b)) > a ;
});
You can use map with this example:
const ascendingArray = [1,2,3,4,5,6,7];
const descendingArray = [1,2,3,7,5,6,4]
const isAscending = array => array.map((a, i) => a > array[i + 1]).indexOf(true) === -1
console.log(isAscending(descendingArray)); // should be false
console.log(isAscending(ascendingArray)); // should be true
Or, you can use filter with this example:
const ascendingArray = [1,2,3,4,5,6,7];
const descendingArray = [1,2,3,7,5,6,4]
const isAscending = array => array.filter((a, i) => a > array[i + 1]).length === 0;
console.log(isAscending(ascendingArray)); // should be true
console.log(isAscending(descendingArray)); // should be false
Copy the array first, remove any element that is not in order with array.splice(index, 1), and continue. That way, any element must by greater than the one right before it, but the one right before it will always be the max.
Answering my own question after taking your advices I tried the following algorightm. It seems to do its job but its a bit overkill.
for (var i = 0; i < arr.length; i++){
for (var j = 1; j < arr.length; j++){
if ( arr[j] > 0 && arr[i] > 0 && j != i ){
if ( arr[j] <= arr[i] && j > i ){
if ( jQuery.inArray(j, nonvalid) == - 1) nonvalid.push(j);
}
}
} }

Categories

Resources