Check if an element is an array using recursion - javascript

I would like to check if an element inside an array is another array. I'm solving a code challenge where the problem is iterating through an array and checking for a 7 but if an
element is an array I would like to continuously check each nested array for a 7.
I have console.log() in my first 'if' statement and I've seen that sevenBoom() is being called more than once. But for some reason it's not returning 'Boom!'
SevenBoom should return 'Boom!' if there's a seven.
function sevenBoom(arr) {
if (arr.includes(7)) {
return "Boom!";
}
arr.forEach((val) => {
if (Array.isArray(val)) sevenBoom(val);
});
}
sevenBoom([1, 3, 4, 6, [7]) // Returns undefined
sevenBoom([3, 7, 8, 9]) // Returns 'Boom!'

You could take a boolean value as result and use Array#some for checking arrays.
function hasSeven(array) {
function seven(a) {
return a.includes(7) || a.some(v => Array.isArray(v) && seven(v));
}
return seven(array)
? 'Boom!'
: 'There is no 7';
}
console.log(hasSeven([7]));
console.log(hasSeven([[7]]));
console.log(hasSeven([[[7]]]));
console.log(hasSeven([]));
console.log(hasSeven([[]]));
console.log(hasSeven([[[]]]));

const sevenBoom = (arr) => {
const recurse = (arr) => arr.some(n => Array.isArray(n) ? recurse(n) : n === 7);
if (recurse(arr)) {
return 'Boom';
}
}
This is assuming what should be returned other than 'Boom' is void. It's a bit of an awkward place for recursion since you want to return a string if you meet some criteria and nothing otherwise.

Firstly you need to return the value from your second if condition too.
But forEach() cannot be interrupted (for ex: with a return statement) and will run for all items. So you can keep track using a flag variable outside the forEach() and return your result on that basis.
function sevenBoom(arr) {
if (arr.includes(7)) {
return "Boom!";
}
let found = false;
arr.forEach((val) => {
if (Array.isArray(val)) if(sevenBoom(val)) found="Boom!";
})
return found;
}
console.log(sevenBoom([1,2,3]));
console.log(sevenBoom([1,2,7]));
console.log(sevenBoom([1,2,[2,7]]));
console.log(sevenBoom([1,2,[2,3,[4,5]]]));
console.log(sevenBoom([1,2,[2,3,[4,7]]]));
Note: How sevenBoom() can be directly used inside an if statement. This is because of the concept of truthy and falsy values.
PS: As mentioned above, forEach() will run for all items, no matter what. You can use any other looping mechanism like a simple for loop, some() etc. I just copied your code and hence used forEach()

I would check if an element is seven in the same loop that you are checking if an element is an array that way you can avoid going through the array unnecessarily.
const sevenBoom = arr => {
for (const ele of arr) {
if (ele === 7) return 'Boom';
if (Array.isArray(ele)) {
//this prevents you from halting your function early
//you only want to return in the loop if a 7 is found
if (boom(ele) === 'Boom') return 'boom'
}
}
}

You can use the .flat(depth) method to flatten each array before using the .includes() method. Choose a depth that would cover all possible arrays for your project.
function sevenBoom(arr) {
return arr.flat(10).includes(7) ? 'Boom!' : '-'
}
DEMO
let a = [1, 3, 4, 6, [7]],
b = [3, 7, 8, 9],
c = [1,2,[5,6,[3,5,[7,6]]]],
d = [0],
e = [1, [5, 4], 3, 5, [7]];
function sevenBoom(arr) {
return arr.flat(10).includes(7) ? 'Boom!' : '-'
}
for(let arr of [a,b,c,d,e]) {
console.log( sevenBoom( arr ) );
}
console.log( e ); //Original array remains unchanged

If 7 is not present in the root array, then your function isn't returning anything.
Try this, just a minor refactoring:
function sevenBoom(arr) {
if (arr.includes(7)) return 'Boom!';
for (let val of arr) {
if (Array.isArray(val)) {
if (sevenBoom(val)) return sevenBoom(val);
}
}
return false;
}

Related

two function with same functionality return different value because i haven't use "return" keyword inside one function exact before function call

I wrote a function that flatten and array.
const multiArray = [1, [2, 3, [4, [5]]], 6, [7, 8, 9, [10]]]
let newArray = []
const flatenArray = (params) => {
params.map((sm) => {
if(Array.isArray(sm)) {
return flatenArray(sm)
}
newArray.push(sm);
})
return newArray
}
//console.log(flatenArray(multiArray)) result [1,2,3,4,5,6,7,8,9,10]
The above function works fine.
but if I remove the return keyword from the "if" block then does it returns something else?
let newArray2 = []
const flatenArray2 = (params) => {
params.map((sm) => {
if(Array.isArray(sm)) {
flatenArray(sm)
}
newArray2.push(sm);
})
return newArray2
}
console.log(flatenArray2(multiArray))
//result [1,[2,3,[4,[5]]],6,[7,8,9,[10]]]
Now I would like to know...
Why should I use the return keyword there even when I'm calling it?
What is happening when I use the return keyword there?
What is happening when I use the return keyword there?
The function (the callback that you're passing to map) does return a value and stop executing. This means it will not reach the next line, newArray.push(sm);, after the if statement, which does push the sm array onto the result array in your second implementation.
Why should I use the return keyword there even when I'm calling it?
You shouldn't actually. Using an else statement would be much cleaner (and also you shouldn't abuse map):
const flattenArray = (params) => {
for (const sm of params) {
if (Array.isArray(sm)) {
flattenArray(sm)
} else {
newArray.push(sm);
}
}
return newArray
}
Every function has to "return" something, and that's result value of your function!
Imagine a sum function without a return value!
const noreturn_sum = (a, b) => {
a + b
}
if you don't return something, your function returns undefined implicitly.
so if you gonna print the result of noreturn_sum, you will get:
console.log(noreturn_sum(1, 2))
>> undefined
This is true even in your example, let me explain : (may I cleanup your code a little bit?)
let newArray2 = []
const flatenArray2 = (params) => {
// PART 1 ---------
params.map((sm) => {
if(Array.isArray(sm)) {
flatenArray(sm) // PART 1.map.fn ---------
}
newArray2.push(sm);
})
// END PART 1 ---------
// PART 2 ---------
return newArray2
// END PART 2 ---------
}
console.log(flatenArray2(multiArray))
A. your function for each parameter does this:
checks if it's an array:
true => call the function again to reach to a non-array value
false [it's a value] => add it to newArray2
then returns undefined
B. then your whole params.map(... returns an array of undefined, because your function inside map (that I explained it in (1.) and (2.) returns undefined
C. you return newArray2 at the end of the function.
note that your flatenArray2 function always return the newArray2 but inside "PART 1.map.fn" you "discard" it (or you "ignore" it in other words)
but in the last experssion console.log(flatenArray2(multiArray)) you gave the returned value to the console.log function.
When you do not use the result, either by assigning, passing or returning, you are solely relying on the side effects on the function.
Eg. in your flatenArray if you pass it a [[1]] sm will be [1], thus the if test is true and it will recurse with [1]. It will then fail on the test because 1 is not an array and push 1 to newArray. It then returns newArray which is then instantly returned and no more of that function is used in that round. You never use the resulting array from map since you are never ever returning this so one could question why you didn't use forEach instead to enforce that it is just for side effects (mutating newArray). If you were to remove return it will continue down and do newArray.push(sm) even though it is an array Thus you would get both flattened results and unflattened results in the result array mixed and doubled up. This could be prevented by having the last statement in an else so that the function either did the array thing or the element thing.
The flatenArray2 doesn't recurse as it uses flatenArray that populates newArray and not newArray2 so it will only make a shallow copy of the original array due to the flattened elements being added to newArray and the unflattened elements being added to newArray2 which is the only result that is returned.
If you were to call any function twice you will get the results of the previous runs in the second.
This is how I would have made it to prevent it from keeping results from previous rounds and actually using the results returned:
const multiArray = [1, [2, 3, [4, [5]]], 6, [7, 8, 9, [10]]];
const flattenPure = (arr) => {
const helper = (acc, e) =>
Array.isArray(e)
? e.reduce(helper, acc) // reduce the array e with acc as accumulator
: [...acc, e]; // make a new array with elements from acc and the element e
return arr.reduce(helper, []); // start with an empty accumulator
}
console.log("Once Pure", flattenPure(multiArray));
console.log("Twice Pure", flattenPure(multiArray));
Using Array.proptype.push makes a faster version, however then why not just use Array.prototype.flatten)?
const multiArray = [1, [2, 3, [4, [5]]], 6, [7, 8, 9, [10]]];
const flattenSide = (arr) => {
let res = []; // this is new every time flatten is called
const helper = (e) => {
if (Array.isArray(e)) {
e.forEach(helper); // indirect recurse for each element
} else {
res.push(e);
}
// hidden return undefined here
};
helper(arr); // flatten arr with result in res
return res; // return result
}
console.log("Once Side", flattenSide(multiArray));
console.log("Twice Side", flattenSide(multiArray));

Return Command for Callback Functions

function twoSum(numbers, target) {
var result = [];
numbers.forEach(function(value, index) {
return numbers.forEach(function(value2, index2) {
if (value + value2 === target) {
result.push(index, index2);
return result;
}
})
})
return result;
}
twoSum([1, 2, 3], 4);
//Output - [ 0, 2, 1, 1, 2, 0 ]
Hi - I'm working on a particular codewars problem and I seem to be misunderstanding the usage of return for callback functions. In this particular problem I just want to find the first two sums of numbers that equal the target and push those index values into result. I don't want to keep iterating through my function after that - meaning I only want the first pair that's found. My current output gives me all the index values for the target sum. Not just the first 2. It seems I am not using my return commands correctly. My current line of thought is that return result returns a value to my nested callback of parameters (value2, index2). That result is then returned to my outside function of (value,index). Why does my loop not cease after that return?
It doesn't end because .forEach cannot be terminated early. forEach is not paying any attention to the values you return. If you want to terminate early you'll need to use a different approach.
If you want to stick with array methods, there are .some and .every. The former continues until a run of your function returns true, and the latter continues until a run of your function returns false. These are meant for doing compound OR's and compound AND's with every element of the array, but they can kinda be used for your case too.
numbers.some(function(value, index) {
return numbers.some(function(value2, index2) {
if (value + value2 === target) {
result.push(index, index2);
return true;
}
return false;
})
})
Or you could use a standard for loop, with the break keyword when you want to stop the loop.
Beside the not working return statement for a outer function, you need to take a different approach which uses only a single loop and an object for storing the index of the found value.
function twoSum(numbers, target) {
var indices = {};
for (let i = 0; i < numbers.length; i++) {
const number = numbers[i];
if (number in indices) return [indices[number], i];
indices[target - number] = i;
}
}
console.log(twoSum([1, 2, 3], 4));

How to find a specific array in an array?

I have got this code:
var test = [[1,1], "b","a"];
function findArray(element) {
return element == [1,1];
}
console.log(test.find(findArray));
It returns:
undefined
What should I do to find the [1,1] array inside of the test array? (I do not want to loop through it though)
You can't compare two objects with === or == since they are references and will evaluate to true only if they are pointing to the same address.
You need to match each element from first array with respective index in second array to check for similarity, you can use every.
var test = [ [1, 1], "b", "a"];
function findArray(element) {
if(Array.isArray(element)){
let arrayToMatchWith = [1,1]
if(element.length === arrayToMatchWith.length){
return element.every((v,i)=> v === arrayToMatchWith[i])
}
}
return false
}
console.log(test.find(findArray));
console.log([[1,2]].find(findArray));
console.log([[1,1,1]].find(findArray));
console.log([[1]].find(findArray));
Could I pass the searched array as an argument?
Yes you can. Here I am using a curried function:
var test = [[1, 1], "b", "a"];
let curried = (arr) => (element) => {
if (Array.isArray(element)) {
if (element.length === arr.length) {
return element.every((v, i) => v === arr[i])
}
}
return false
}
let curried1 = curried([1,1])
console.log(test.find(curried1));
let curried2 = curried([1,2,2])
console.log([[1, 2]].find(curried2));
let curried3 = curried([1,1,1])
console.log([[1, 1, 1]].find(curried3));
let curried4 = curried([1])
console.log([[1]].find(curried4));
The array literal in your comparison is a different object than the array inside your original array. If you derive a comparable entity from each array, you can check for that inside the find method. One way to do it is by calling the toString method.
This is only required if you really don't want to loop through the array for comparison.
var test = [[1,1], "b","a"];
function findArray(element) {
if (element.constructor === Array)
return element.toString() == [1,1].toString();
}
console.log(test.find(findArray));

High-order function "every" method PROBLEM

I have a problem to solve in ELOQUENTJS book, can somebody help and tell me what's wrong in this code.
This is my code so far.
function every(array, test) {
for (let i of array) {
let curArr = array[i];
if (test(curArr)) {
return true;
} else {
return false;
}
}
}
console.log(every([1, 3, 4, 12], n => n < 10));
// returns true
I'm expecting to see false as a return, but somehow it returns true.
Your first issue is with your return true. This line will make your function "exit", thus stopping any of the remaining code from executing. As 1 < 10, you are immediately returning true from your function. Instead, you can return true only once you have checked every element.
Your other issue is that a for..of loop will get every element in your array, not every index like you think you're doing so i infact actually is equal to your curArr variable:
function every(array, test) {
for(let curArr of array){
if(!test(curArr)){
return false;
}
}
return true;
}
console.log(every([1, 3, 4, 12], n => n < 10));

Javascript array.filter() element being skipped

I am trying to delete out the common elements in arguments given and
create a final array with unique elements.
In the below example my final array is giving me [1,3,4,5] instead of [1,4,5].
I did the debug and the one of element (3) is not going through the loop.
After element (2) it's skipping to element (4). I am confused why is this happening? Anything wrong with my code? Any help appreciated.
Thank you
function arrayBreaker(arr) {
let test = [];
test.push(arguments[1]);
test.push(arguments[2]);
let target = arguments[0];
target.filter(val => {
if (test.indexOf(val) > -1) {
target.splice(target.indexOf(val), 1);
}
});
return target;
}
console.log(arrayBreaker([1,2,3,4,5], 2, 3));
You're using .filter() like a general purpose iterator, and mutating the array you're iterating, which causes problems because the iterator is unaware of the items you're removing.
If you need to mutate the original array, instead return the result of the .indexOf() operation from the .filter() callback, or better yet, use .includes() instead, and then copy the result into the original after first clearing it.
function arrayBreaker(target, ...test) {
const res = target.filter(val => !test.includes(val));
target.length = 0;
target.push(...res);
return target;
}
console.log(arrayBreaker([1, 2, 3, 4, 5], 2, 3));
If you don't need to mutate, but can return a copy, then it's simpler.
function arrayBreaker(target, ...test) {
return target.filter(val => !test.includes(val));
}
console.log(arrayBreaker([1, 2, 3, 4, 5], 2, 3));
Array is returning a new array based on filter criteria, in this case you are checking index of test. In the callback function is expecting a true if you want to keep the value, otherwise return false. In your case you shouldn't slice the target inside filter callback, but rather:
function arrayBreaker(arr) {
let test = [];
test.push(arguments[1]);
test.push(arguments[2]);
let target = arguments[0];
let newArray = target.filter(val => {
if (test.indexOf(val) > -1) {
// filter value out
return false;
}
// keep the val inside new array
return true;
});
return newArray;
}
console.log(arrayBreaker([1,2,3,4,5], 2, 3));
for more information about array filter, check documentation here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
Depending on your use case there are few things that I think would be helpful, first I would declare the function parameters so it's clear that arrayBreaker is expecting multiple arguments. Secondly I would expect second param as an array since we are expecting multiple values. Something like below:
function arrayBreaker(arr, filterValues) {
return arr.filter(val => {
if (filterValues.indexOf(val) > -1) return false;
return true;
})
}
console.log(arrayBreaker([1,2,3,4,5], [2, 3]));

Categories