include() method for nested arrays - javascript

I am struggling with checking an array for an array within. It doesn't seem that the include() method is designed for it because it always returns false in this case. Below is example of an identical scenario with indexOf. Maybe all I need is syntax help, any ideas are most welcome.
arr = [1,[9,9],3,4,5];
if (arr.indexOf(el) !== -1) {
console.log(`the array contains ${el}`);
} else {
console.log(`doesn't contain ${el}`);
}
Of course the above returns true for 1, 3, 4, 5 and false for 2. And now the problem. I am trying to feed this method with an array like [9,9] to see if there's one already.
let el = [9,9];
// console: "doesn't contain 9,9"
On the other hand the below is okay which makes me think it's just a syntax issue(?)
let el = arr[1];
// console: "the array contains 9,9"
I found a way around it by writing a checker function with for loop but this quickly becomes bulky as you add requirements. I would love to know a smarter way.
Thank you.

The problem you have is that arrays are reference types. Because of this, comparing two arrays will return true only if the two arrays refer to the same underlying array, and will return false for different arrays, even if they hold the same values.
const arr1 = [1, 2];
const arr2 = [1, 2];
const arr3 = arr1;
console.log(arr1 === arr3);
console.log(arr1 !== arr2);
To fix your problem, your include function needs to compare by value (deep comparison), you can do this simply using JSON.stringify().
This method is fast but limited, it works when you have simple JSON-style objects without methods and DOM nodes inside
See this SO post about object comparison in JavaScript.
Here is a working example:
function include(arr, value) {
const stringifiedValue = JSON.stringify(value);
for (const val of arr) {
if (JSON.stringify(val) === stringifiedValue) {
return true;
}
}
return false;
}
console.log(include([1, 2, 3, 4], 3));
console.log(include([1, 2, 3, [1, 2]], [1, 2]));
console.log(include([1, 2, 3, [1, 2]], [1, 2, 3]));
Here is another way to do this deep comparison without JSON.stringify(), it works for inner arrays, and scalar values:
function include(arr, value) {
const valArity = Array.isArray(value) ? value.length : 1;
for (let item of arr) {
if (valArity > 1 && Array.isArray(item) && item.length === valArity) {
if (item.every((val, i) => val === value[i])) {
return true;
}
} else if (item === value) {
return true;
}
}
return false;
}
console.log(include([1, 2, 3, 4], 3));
console.log(include([1, 2, 3, [1, 2]], [1, 2]));
console.log(include([1, 2, 3, [1, 2]], [1, 2, 3]));
console.log(include([1, 2, 'abc', 4], 'abc'));
And here is a modified version that works for simple objects with scalar properties, arrays and scalars:
function include(arr, value) {
const valArity = Array.isArray(value) ? value.length : 1;
const isObject = value instanceof Object;
for (let item of arr) {
if (valArity > 1 && Array.isArray(item) && item.length === valArity) {
if (item.every((val, i) => val === value[i])) {
return true;
}
} else if (isObject && item instanceof Object && item) {
const numEntries = Object.keys(value).length;
const entries = Object.entries(item);
if (numEntries === entries.length) {
if (entries.every(([k, v]) => value[k] === v)) {
return true;
}
}
} else if (item === value) {
return true;
}
}
return false;
}
console.log(include([1, 2, 3, 4], 3));
console.log(include([1, 2, 3, [1, 2]], [1, 2]));
console.log(include([1, 2, 3, [1, 2]], [1, 2, 3]));
console.log(include([1, 2, { a: 1 }, 4], { a: 1 }));
console.log(include([1, 2, { a: 1, b: 2 }, 4], { a: 1 }));
console.log(include([1, 2, 'abc', 4], 'abc'));

Here we can do this using Array.prototype.some()
Notice we added a helper method to check each element, I did not bother writing object checking, but you get the idea.
Tweaked to support sub-arrays
let arr = [1, [9, 9], 3, 4, 5];
let el = [9, 9];
let arr2 = [1, [1,[9, 9]], 3, 4, 5];
let el2 = [1,[9, 9]];
const someCheck = (item, compare) => {
if(typeof item !== typeof compare) return false;
if(typeof item === 'string') return item === compare;
if(typeof item === 'number') return item === compare;
if(Array.isArray(item)) {
console.log('checking array');
return item.reduce((accum, o, i) => accum && (someCheck(o,compare[i])));
}
// no plain object support yet.
return false;
}
if (arr.some(e => someCheck(e, el))) {
console.log(`the array contains ${el}`);
} else {
console.log(`doesn't contain ${el}`);
}
if (arr2.some(e => someCheck(e, el2))) {
console.log(`the array contains ${el2}`);
} else {
console.log(`doesn't contain ${el2}`);
}

You need to loop through the array and check if each element is an array, then you check each index. Something like this:
let arr = [1,[9,9],3,4,5];
let el = [9,9];
let contains = true;
for (var i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
var equals = true
if(arr[i].length !== el.length)
equals = false;
for(var j = arr[i].length; j--;) {
if(arr[i][j] !== el[j])
equals = false;
}
if (equals) {
contains = true;
break;
}
} else {
if (arr.indexOf(el) !== -1){
contains = true;
break;
}
else{contains = false;}
}
}
if (contains) {
console.log(`the array contains ${el}`);
} else {
console.log(`doesn't contain ${el}`)
}

Related

Sorting an Array that consists of numbers, BUT can include strings

I am facing an issue where I get results from API(mainly array of numbers), but if the devs make mistake and leave the field empty I will get empty string ("").
I am trying to sort this array in an ascending order and move the empty strings in the back of the Array, like that:
let arr = [3, 4, "", 1, 5, 2] // Example Array from api
This array, when modified should become:
let res = [1, 2, 3, 4, 5, ""]
I tried using the arr.sort() method, but the results look like that:
let res = ["",1 ,2 ,3 ,4 ,5]
For some reason when the element is string, the sort method puts it in the front, not in the end like it does with undefined or null for example.
Method 1
let arr = [3, 4, "", 1, 5, 2];
const res = arr.sort((a, b) => {
if (typeof a === 'string') {
return 1;
} else if (typeof b === 'string') {
return -1;
} else {
return a - b;
}
}
);
console.log(res)
Output:
[ 1, 2, 3, 4, 5, '' ]
Method 2
const res = (arr) => {
let newArr = [];
let strArr = [];
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] === 'string') {
strArr.push(arr[i]);
} else {
newArr.push(arr[i]);
}
}
return newArr.concat(strArr);
}
console.log(res(arr));

maximize the groups of combinations to use all values

Given an array of combinations (to a certain sum), I'm struggling to find pairs of unique combinations that use all the numbers from the source.
function maximizeGroups(groups, source) {}
const groups = [[10], [1, 3, 6], [1, 9], [3, 7], [4, 6]];
const source = [6, 1, 3, 10, 4, 7, 9]
console.log(maximizeGroups(groups, source))
-> expected result [[6, 4], [7, 3], [10], [9, 1]]
Here [1, 3, 6] is discarded.
Since use of this combination doesn't allow to use the number 4
Function used:
const groups = [[10], [1, 3, 6], [1, 9], [3, 7], [4, 6]];
const source = [6, 1, 3, 10, 4, 7, 9];
function maximizeGroups(groups, source) {
const sorted = source
.slice()
.sort((a, b) => a - b)
.join('');
for (let i = 0; i < groups.length - 1; i++) {
const copy = groups.slice();
copy.splice(i, 1);
const check = copy
.slice()
.flat()
.sort((a, b) => a - b)
.join('');
if (check === sorted) {
return copy;
}
}
return null;
}
Here is what should be a more efficient solution. Basically as I'm piecing together the groupings I'm making sure that I'm only considering groups that could wind up summing to the target.
If you have a large number of groups, and there is no solution, this could fail in exponential time. But normally it will finish fairly quickly.
// Helper functions to extract the parts of a "pos:value" pair.
function pairToPos (pair) {
return Number(pair.split(":")[0]);
}
function pairToValue (pair) {
return Number(pair.split(":")[1]);
}
function sum (nums) {
let total = 0;
nums.forEach( (value) => {total += value} );
return total;
}
function recursiveSolution(nums, target, maxGroups, sumLookup, pos, partialAnswers) {
if (pos == nums.length) {
return partialAnswers;
}
// Try starting a new group.
if ((partialAnswers.length < maxGroups)
&& ([pos, target].join(":") in sumLookup)) {
// Try adding a new group.
partialAnswers.push([nums[pos]]);
let attempt = recursiveSolution(nums, target, maxGroups, sumLookup, pos+1, partialAnswers);
if (attempt != null) {
return partialAnswers;
}
// Get rid of my new group.
partialAnswers.pop();
}
// Try adding this value to each group.
let finalAnswers = null;
partialAnswers.forEach( (group) => {
if (finalAnswers != null) {
// Do nothing, we have the answer.
}
else if ([pos, target - sum(group)].join(":") in sumLookup) {
// Try adding this value.
group.push(nums[pos]);
let attempt = recursiveSolution(nums, target, maxGroups, sumLookup, pos+1, partialAnswers);
if (attempt != null) {
finalAnswers = partialAnswers;
}
else {
// Get rid of my new value.
group.pop();
}
}
});
return finalAnswers;
}
function deriveGroups(nums, target) {
// Use dynamic programming to find all paths to the target.
let sumLookup = {};
// We constantly use "pos:value" pairs as keys.
// This entry means "the empty sum off the array is 0".
sumLookup[[nums.length, 0].join(":")] = null;
// We go backwards here to get the future sum from current value + pos.
for (let i = nums.length-1; 0 <= i; i--) {
let term = nums[i];
Object.keys(sumLookup).forEach( (pair) => {
let prevPos = pairToPos(pair);
let prevValue = pairToValue(pair);
let nextPair = [i, prevValue + term].join(":");
if (! (nextPair in sumLookup)) {
sumLookup[nextPair] = [];
}
sumLookup[nextPair].push(prevPos);
});
}
return recursiveSolution(nums, target, sum(nums)/target, sumLookup, 0, []);
}
console.log(deriveGroups([9, 2, 13, 10, 2, 3], 13));

How to check if two int arrays are permutations in JavaScript

How can I check if two integers arrays are permutations? I need to do it in JavaScript.
For example, I have two arrays:
a = [1, 2, 3, 4, 5]
and
b = [2, 3, 5, 1, 4]
I need the program to return true.
You could use a Map to store the occurrence count and then decrease that count whenever you find a mapping occurrence in the other array:
function isPermutation(a, b) {
if (a.length !== b.length) return false;
let map = new Map(a.map(x => [x, { count: 0 }]));
a.forEach(x => map.get(x).count++);
return b.every(x => {
let match = map.get(x);
return match && match.count--;
});
}
let a =[1,2,3,4,5,1];
let b = [2,3,1,5,1,4];
console.log(isPermutation(a, b));
The lazy solution:
let a = [1,2,3,4,5],
b = [2,3,5,1,4];
let res = JSON.stringify(a.sort()) === JSON.stringify(b.sort())
console.log(res)
The more efficient solution:
function perm (a,b) {
let map = a.reduce((acc,c) => {acc[c] = (acc[c] || 0) + 1; return acc},{})
for (el of b) {
if (!map[el] || map[el] == 0) {
return false;
} else {
map[el]--;
}
}
for (k in map) {
if (map[k] != 0) {
return false;
}
}
return true;
}
console.log(perm([1, 2, 3, 4, 5],[2, 3, 5, 1, 4])) // => true
console.log(perm([1, 2, 3, 4, 5, 5, 5],[2, 3, 5, 1, 4])) // => false
console.log(perm([1,1,2],[1,2,2])) // => false
console.log(perm([1,2,3,4,5,1],[2,3,1,5,1,4])) // => true
This solution is in hindsight similar to the one of #trincot but I guess slightly different enough to keep it posted.
The idea is the following: We create a map from the first array via reduce where the value is a count of occurrences. We then take the other array and subtract occurrences from the respective keys of map. If the key doesn't exist is or the value is already zero, we know this is not a permutation. Afterwords we loop the map, checking whether all values are exactly zero.
var a = [1, 2, 3, 4, 5];
var b = [2, 3, 5, 1, 4];
return a.filter(x => !b.includes(x)).length === 0
This will return true if all of the values in a exists in b, regardless of position.
This worked:
var a =[1,2,3,4,5,1];
var b = [2,3,1,5,1,4];
console.log(a.sort().toString() === b.sort().toString())

Remove Only One Duplicate from An Array

I'm trying to only remove one of the 2s from an array, but my code removes all of them. My code is as follows:
var arr = [2,7,9,5,2]
arr.filter(item => ((item !== 2)));
and:
var arr = [2,7,9,2,2,5,2]
arr.filter(item => ((item !== 2)));
Both remove all the 2s. I thought about removing duplicates, where it works if there's only one duplicate - e.g.:
Array.from(new Set([2,7,9,5,2]));
function uniq(a) {
return Array.from(new Set(a))
}
But fails if there's multiple duplicates as it just removes them all, including any other duplicated numbers:
Array.from(new Set([2,7,9,9,2,2,5,2]));
function uniq(a) {
return Array.from(new Set(a))
}
Does anyone know how to only remove one of the 2s? Thanks for any help here.
You could use indexOf method in combination with splice.
var arr = [2,7,9,5,2]
var idx = arr.indexOf(2)
if (idx >= 0) {
arr.splice(idx, 1);
}
console.log(arr);
You could take a closure with a counter and remove only the first 2.
var array = [2, 7, 9, 2, 3, 2, 5, 2],
result = array.filter((i => v => v !== 2 || --i)(1));
console.log(result);
For any other 2, you could adjust the start value for decrementing.
var array = [2, 7, 9, 2, 3, 2, 5, 2],
result = array.filter((i => v => v !== 2 || --i)(2));
console.log(result);
There are various ways to do that; one relatively simple way would be to use indexOf; see this other post: https://stackoverflow.com/a/5767357/679240
var array = [2, 7, 9, 5, 2];
console.log(array)
var index = array.indexOf(2);
if (index > -1) {
array.splice(index, 1);
}
// array = [7, 9, 5, 2]
console.log(array);
you can follow the following method
var arr= [2,3,4,2,4,5];
var unique = [];
$.each(arr, function(i, el){
if($.inArray(el, unique) === -1) unique.push(el);
})
You can do:
const arr = [2, 7, 9, 2, 2, 5, 2];
const result = arr
.reduce((a, c) => {
a.temp[c] = ++a.temp[c] || 1;
if (a.temp[c] !== 2) {
a.array.push(c);
}
return a;
}, {temp: {}, array: []})
.array;
console.log(result);
Most simple way to filter all duplicates from array:
arr.filter((item, position) => arr.indexOf(item) === position)
This method skip element if another element with the same value already exist.
If you need to filter only first duplicate, you can use additional bool key:
arr.filter((item, position) => {
if (!already && arr.indexOf(item) !== position) {
already = true
return false
} else return true
})
But this method have overheaded. Smartest way is use for loop:
for (let i = 0; i < arr.length; i++) {
if (arr.indexOf(arr[i]) !== i) {
arr.splice(i,1);
break;
}
}

compare arrays using .some()

so i am trying to compare 2 arrays to see if their contents are the same, see example below
var array1 = [0,2,2,2,1]
var array2 = [0,2,2,2,3]
i want to compare those 2 arrays using the some method
so i will write a function that returns true if some of the numbers are in both arrays.
i have used the method perfectly fine on one array trying to find a particular value
function testArray(){
var bool = array1.some(function(value){
return value ===1;
});
}
console.log(bool)
but how to use it for 2 arrays? any help is appreciated
A solution with Array.prototype.every()
The every() method tests whether all elements in the array pass the test implemented by the provided function.
and Array.prototype.some()
The some() method tests whether some element in the array passes the test implemented by the provided function.
for the same task. Please watch the bangs!
var array1 = [0, 2, 2, 2, 1],
array2 = [0, 2, 2, 2, 3];
function compareEvery(a1, a2) {
if (a1.length !== a2.length) { return false; }
return a1.every(function (a, i) {
return a === a2[i];
});
}
function compareSome(a1, a2) {
if (a1.length !== a2.length) { return false; }
return !a1.some(function (a, i) {
return a !== a2[i];
});
}
document.write(compareEvery(array1, array2) + '<br>');
document.write(compareEvery(array1, array1) + '<br>');
document.write(compareSome(array1, array2) + '<br>');
document.write(compareSome(array1, array1) + '<br>');
You could use this approach
function compare(arr1, arr2) {
if (arr1.length != arr2.length) return false;
for (var i = 0; i < arr2.length; i++) {
if (arr1[i].compare) {
if (!arr1[i].compare(arr2[i])) return false;
}
if (arr1[i] !== arr2[i]) return false;
}
return true;
}
var array1 = [0, 2, 2, 2, 1];
var array2 = [0, 2, 2, 2, 3];
compare(array1, array2);
If you want to test if two arrays contains the same value, you can use some in combination with indexOf:
function containsSameValue(arr1, arr2) {
return arr1.some(function(value) {
return arr2.indexOf(value) > -1;
});
}
var array1 = [0, 2, 2, 2, 1];
var array2 = [0, 2, 2, 2, 3];
containsSameValue(array1, array2); // true
containsSameValue([1, 2, 3], [4, 5, 6]); // false

Categories

Resources