Can anyone tell me why my JS is not iterating through the loop and deleting items that are not "10" in the array?
As far as I understand it, it should be checking each item in the array and deleting any that are not 10, then returning the remaining items that are "10".
My current output is: [ <1 empty item>, 10, 50, 10 ]
I've seen some answers that create a separate array and push items that are 10 to it and then return that, but why would my code not work?
function getElementsThatEqual10AtProperty(obj, key) {
if (obj.key.length === 0) {
return [];
} else if (!obj.hasOwnProperty(key)) {
return [];
} else {
for (var i = 0; i < obj.key.length; i++) {
if (obj[key][i] !== 10) {
delete obj[key][i];
}
return obj.key;
}
}
}
var obj = {
key: [1000, 10, 50, 10]
};
var output = getElementsThatEqual10AtProperty(obj, 'key');
console.log(output);
This is a strange design for a method. It mutates and returns a result? Generally, it's better to do one or the other (see CQS).
Also, your original method is overly-coupled to an object structure. It would be better to make it operate on an array only, leaving arbitrary object keys out of it.
Consider the following:
function getElementsThatEqual10(array) {
return array.filter(n => n == 10);
}
var obj = {
key: [1000, 10, 50, 10]
};
var output = getElementsThatEqual10(obj.key);
console.log(output);
The early return breaks out of the loop after it finished deleting the first element only (here simplified to simple array since the object isn't relevant):
let objKey = [1000, 10, 50, 10];
for (var i = 0; i < objKey.length; i++) {
if (objKey[i] !== 10) {
// Here it deletes 1000, the first element
delete objKey[i];
}
// then it returns [undefined, 10, 50, 10]
return objKey;
}
You could move return statement out of the loop to before getElementsThatEqual10AtProperty function ends.
Others have already suggested more elegant ways of doing this.
Well why your code was not working
Your return statement was inside for loop so it will return control as soon as your if condition fails.
You're using delete which will delete the element but the index of other element will still be same as before. so you will get something like [undefined,10,undefined,10].
So here's working mode of you code.
function getElementsThatEqual10AtProperty(obj, key) {
if (obj.key.length === 0) {
return [];
} else if (!obj.hasOwnProperty(key)) {
return [];
} else {
let temp = []
for (var i = 0; i < obj.key.length; i++) {
if (obj[key][i] == 10) {
temp.push(obj[key][i])
}
}
return temp;
}
}
var obj = {
key: [1000, 10, 50, 10]
};
var output = getElementsThatEqual10AtProperty(obj, 'key');
console.log(output);
But i suggest using this one because it's clean and simple
function getElementsThatEqual10(array) {
return array.filter(element => element === 10);
}
var obj = {
key: [1000, 10, 50, 10]
};
var output = getElementsThatEqual10(obj.key);
console.log(output);
What's wrong with your code
The primary issue is that you're doing a return from within the loop, meaning the function will exit completely when it hits that line during your very first iteration.
The second issue is more of a suggestion, but I wouldn't recommend using delete. While the object property is deleted, delete does not update the array's length or or indexes.
var array = [1, 2, 3, 4];
delete array[0];
console.log(array.length);
Hypothetically you could chain a .filter(n=>n) to it and all would be well, but it's still an extra set of unnecessary iterations.
How to resolve it
The easiest way to filter items out of an array is by using the method of that name: Array.filter()
Your updated function could look something like this instead.
function getElementsThatEqual10AtProperty(obj, key) {
if (Array.isArray(obj[key]) //Is it an array?
return obj[key].filter(n => n === 10); //Filter it for 10s
else
return [];
}
var obj = {
key: [1000, 10, 50, 10]
};
var output = getElementsThatEqual10AtProperty(obj, 'key');
console.log(output);
Or, if you prefer brevity:
const get10s = (obj, key) => Array.isArray(obj[key]) ? obj[key].filter(n => n === 10) : [];
var obj = { key: [1000, 10, 50, 10] };
console.log( get10s(obj, 'key') );
Alternative
If you are open to another method, you may try using filter, to parse the values you're after:
const getElementsThatEqual10AtProperty = (obj, key) => obj[key].filter(v => v === 10)
var obj = {
key: [1000, 10, 50, 10]
};
var output = getElementsThatEqual10AtProperty(obj, 'key');
console.log(output);
Your Code
Given your current method there are three issues:
you use obj.key in places that should probably be obj[key] since you are passing key in as an argument
delete obj[key][i] does not behave how you might expect, when operating on an array. As #TinyGiant mentions, "it doesn't reorder the indices to remove the resulting empty slot."
return obj.key should be return obj[key] and it should be moved outside the loop because it is exiting the loop at the end of the first iteration
If you want to keep this method, I would recommend using obj[key].splice(i,1) as opposed to delete, which will alter the array. However, because you are mutating the active array, you will also have to modify the i since the elements have shifted. See below:
function getElementsThatEqual10AtProperty(obj, key) {
if (obj[key].length === 0) {
return [];
} else if (!obj.hasOwnProperty(key)) {
return [];
} else {
for (var i = 0; i < obj.key.length; i++) {
if (obj[key][i] !== 10)
obj[key].splice(i--,1) // remove value then alter i
}
return obj[key] // moved outside of the loop
}
}
var obj = {
key: [1000, 10, 50, 10]
};
var output = getElementsThatEqual10AtProperty(obj, 'key');
console.log(output);
You can write like this to remove the correct items, you were returning inside the for loop so only the first item was being removed.
You can use the splice method instead of delete, because delete only deletes the reference and not the position of the array.
function getElementsThatEqual10AtProperty(obj, key) {
if (obj[key].length === 0) {
return [];
} else if (!obj.hasOwnProperty(key)) {
return [];
} else {
for (var i = 0; i < obj[key].length; i++) {
if (obj[key][i] !== 10) {
obj[key].splice(i, 1);
i--;
}
}
return obj[key];
}
}
var obj = {
key: [1000, 10, 50, 10]
};
var output = getElementsThatEqual10AtProperty(obj, 'key');
console.log(output);
Related
I'm creating a polyfill of Array.flat() method, however, I'm facing issues while calling the function within itself after checking that the looped element is an array and thats need to be flattened further. When a write a code that is not prototypal, the flattening is proper, however when I try to create a prototype function, I'm unable to get the flattened array. I'm pretty sure that the issue is related with the 'this' keyword. Please have a look at my code.
Here is the code
let arrayFlat = [1, 2, 3, [4, 5, 6, [7, 8, [9]], 10, [11, 12]], [13, [14, 15]]];
const flatArray = (array) => {
let output = [];
const flatten = (array) => {
for (let i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flatten(array[i]);
} else {
output.push(array[i]);
}
}
return output;
};
return flatten(array);
};
Array.prototype.myFlat = function () {
let output = [];
for (let i = 0; i < this.length; i++) {
if (Array.isArray(this[i])) {
console.log(this[i]);
this[i].myFlat();
} else {
output.push(this[i]);
}
}
return output;
};
In your first piece of code, you create a single output array. When you recursively call flatten, the code is always pushing to the exact same output array, which is in the closure of flatten. Then once everything is done, you return that array.
In the second code, you create a new array every time you recurse. Each recursion will create an array, flatten itself, and then return that new array. But the return value is ignored, so these values don't go anywhere.
You have a few options
Make the code basically identical to your first one, with an internal function for doing the recursion, and a closure variable used by all:
Array.prototype.myFlat = function () {
let output = [];
const flatten = (array) => {
for (let i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flatten(array[i]);
} else {
output.push(array[i]);
}
}
return output;
};
return flatten(this);
}
Pass the output array as a parameter when you recurse:
// VVVVVV--- added parameter
Array.prototype.myFlat = function (output = []) {
for (let i = 0; i < this.length; i++) {
if (Array.isArray(this[i])) {
this[i].myFlat(output); // <---- forward the array along
} else {
output.push(this[i]);
}
}
return output;
};
Continue having separate arrays, but then merge them together as the stack unwinds:
Array.prototype.myFlat = function () {
let output = [];
for (let i = 0; i < this.length; i++) {
if (Array.isArray(this[i])) {
output.push(...this[i].myFlat()); // <---- added output.push
} else {
output.push(this[i]);
}
}
return output;
};
I am a strong proponent of keeping classes as thin as possible, wrapping functional interfaces wherever possible -
function myFlat(t) {
return Array.isArray(t)
? t.reduce((r, v) => r.concat(myFlat(v)), [])
: [t]
}
Array.prototype.myFlat = function() { return myFlat(this) }
console.log([1,[2,[3],4],[[5]],6,[[[7]]]].myFlat())
// [ 1, 2, 3, 4, 5, 6, 7 ]
The problem is to find the unique number in a array such as [2,2,2,5].
The output should be 5 as it is the 1 unique element in the array.
I have attempted this:
function findUniq(arr) {
var b= arr[0];
var c;
for(var i=0; i<arr.length; i++)
{
if(arr[i]===b )
{
b=arr[i]
}
else
{
c=arr[i];
}
}
return c
console.log(findUniq([3, 5, 3, 3, 3]))
This works fine unless the unique number is the first element in the array. How do I fix this?
You can use indexOf and lastIndexOf to see if a value occurs more than once in the array (if it does, they will be different), and if so, it is not the unique value. Use filter to process the array:
let array = [2,2,2,5];
console.log(array.filter(v => array.indexOf(v) === array.lastIndexOf(v)));
array = [5,3,3,3,3];
console.log(array.filter(v => array.indexOf(v) === array.lastIndexOf(v)));
array = [4,4,5,4];
console.log(array.filter(v => array.indexOf(v) === array.lastIndexOf(v)));
You can create a recursive function that will take the first element of the array and see if it exists in the rest of it, if it does, it will take the next element and do the same, return the element if it doesn't exist in the rest of the array :
const arr = [3, 3, 3, 5, 3];
const find = arr => {
const [f, ...rest] = arr;
if(rest.includes(f))
return find(rest);
else
return f;
}
const result = find(arr);
console.log(result);
Note that this will return the last element if all of them are the same [3,3,3] will return 3
Try something like this using a set, which only stores unique elements:
var set = new Set(arr);
// count instances of each element in set
result = {};
for(var i = 0; i < a.length; ++i) {
if(!result[arr[i]])
result[arr[i]] = 0;
++result[arr[i]];
}
for (var value in result) {
if (value == 1) {
return value;
}
}
// if there isn't any
return false;
This should work, please tell me if it doesn't.
This is another implementation that is surely less efficient than that of #Nick's, but it is a valid algorithm anyway:
function findUniq(arr) {
var elemCount = new Map();
var uniq = [];
// Initialize elements conts
for (var k of arr.values()) {
elemCount.set(k, 0);
}
// Count elements
for (var k of arr.values()) {
elemCount.set(k, elemCount.get(k) + 1);
}
// Add uniq elements to array
for (var [k, v] of elemCount.entries()) {
if (v === 1) uniq.push(k);
}
return uniq;
}
console.log(findUniq([3, 5, 3, 3, 3]))
if you prefer .reduce over .map for your use case (for performance/etc. reasons):
function existance(data) {
return data.reduce((a, c) => (data.indexOf(c) === data.lastIndexOf(c)) ? a.concat(c) : a, []);
}
console.log(existance([1,1,1,2]));
console.log(existance([1,1,2,3,4,5,5,6,6,6]));
I have re-created foreach + map + reduce functions in js:
function forEach(array, callback) {
for (var i=0;i<array.length;i++) {
callback(array[i])
}
}
function mapWith(array, callback) {
var output= [];
forEach(array , function(el){
return output.push(callback(el))
});
return output;
}
function reduce(array, callback, initialValue) {
mapWith(array, function(el){
return initialValue = callback(initialValue, el);
})
return initialValue;
}
Now how would i use reduce to find the intersection between a set of arrays?
function intersection(arrays) {
}
// console.log(intersection([5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20]));
// should log: [15, 5]
Also, how would I compare input arrays and return a new array that contains all elements. If there are duplicate elements, only added once to the new array. The order of the elements starting from the first element of the first input array is preserved.
function union() {
}
// console.log(union([5, 10, 15], [15, 88, 1, 5, 7], [100, 15, 10, 1, 5]));
// should log: [5, 10, 15, 88, 1, 7, 100]
I think Will Sentance will be OK with this too :)
const union = (arrays) => { return arrays.reduce((a, b) => Array.from(new Set(a.concat(b)))) };
const intersection = (arrays) => { return arrays.reduce((a, b) => a.filter(ele => b.includes(ele))) };
Now how would i use reduce to find the intersection between a set of arrays?
An implementation using reduce would be to take each array in turn and eliminate (filter out) elements from the result if they don't exist in that array.
function intersection(arrays) {
return reduce(arrays, (result, array) =>
filter(result, e => array.includes(e)));
};
This assumes you have written your own filter:
function filter(array, callback) {
var output= [];
forEach(array , function(el) {
if (callback(el)) output.push(el);
});
return output;
}
Another idea would be to start off by concatenating all the arrays:
function concat(arrays) { return [].concat(...arrays); }
And then filter to include only elements which occur in all arrays:
function intersection(arrays) {
return concat(arrays).filter(e => arrays.every(a => a.includes(e));
}
If you don't want to use the built-in Array#every, and continue down the path of writing your own:
function every(array, callback) {
for (var i = 0; i < array.length; i++)
if (!callback(array[i])) return false;
return true;
}
Using that and your own filter, intersect then becomes:
function intersection(arrays) {
return filter(concat(arrays), e => every(arrays, a => a.includes(e)));
}
Array#includes is ES7 and might not be supported in your favorite browser. In that case, use a.indexOf(e) !== -1 instead, or write your own.
Some people might like to write that more "semantically" as:
function intersection(arrays) {
const inAll = e => every(arrays, a => a.includes(e));
return filter(concat(arrays), inAll);
}
Also, how would I compare input arrays and return a new array that contains all elements. If there are duplicate elements, only added once to the new array. The order of the elements starting from the first element of the first input array is preserved.
I don't know what you mean by "compare". Anyway, to do what you apparently want, concatenate them and apply some uniq-like utility:
function union(arrays) {
return uniq(concat(arrays));
}
There are many implementations of uniq out there. Here's a real simple one:
function uniq(arr) {
return arr.filter((elt, i) => arr.indexOf(elt) === i);
}
Using custom reduce and foreach
function forEach(array, callback) {
for(i = 0; i < array.length; i++){
callback(array[i])
}
}
function reduce(array, callback, initialValue) {
for(let i of array){
initialValue = callback(initialValue, i)
}
return initialValue
}
function intersection(init, ...arrays) {
return reduce(arrays, (current, next) => {
const filtered = []
forEach(next, (el) => {
if(current.includes(el)) filtered.push(el)
})
return filtered
}, init)
}
Alternative using in-built reduce and forEach
function intersection(...arrays) {
return arrays.reduce((current, next) => {
const filtered = []
next.forEach((el) => {
if(current.includes(el)) filtered.push(el)
})
return filtered
})
}
Refer to specification for algo definition as to how reduce functions without a specified initialValue.
https://tc39.es/ecma262/#sec-array.prototype.reduce
Without a defined initial array, I didn't expect it to work.
Ensure you test your implementation with multiple test cases. My initial solution was producing the correct answers but was not even looking at the last array. I had an error in my reduce.
Compare the firs and the second array and return a new array with the common elements, continue comparing the returned array with the rest of the arrays one by one and every time return a new array with the common elements
how to compare 2 arrays for common elements? use the filter method..
function intersection(...arrays) {
return arrays.reduce((resultArray, currentArray) => {
return resultArray.filter(el => currentArray.includes(el))
})
}
console.log(intersection([5, 10, 15, 20], [15, 88, 17, 5, 7], [1, 10, 15, 5, 20]));
// should log: [5, 15]
try this
const a = [1,2,3,4,5,6];
const b = [1,2,3,9,45,15];
const c = [13,2,5,3,10];
function intersection (...lists){
const all = [...new Set([].concat(...lists))]; //to remove duplicates
return all.reduce( ( accumulator, currentValue ) => {
if(lists.every( list => list.includes(currentValue) )){
return [...accumulator, currentValue];
}else{
return [...accumulator]
}
}, [])
}
console.log( 'intersection', intersection(a,b,c) ); //returns [2,3]
you can try this
// spread operator returns an array of arrays
function intersection(...arrays) {
// setting output to array
const output = []
// initial value is of accumulator is an object
reduce(arrays,function(acc,currentArray){
// for each item check if item exist in accumulator(object)
forEach(currentArray,function(item){
// if it does
if(item in acc) {
// increment its value
acc[item]++
}else {
// else make it a property of accumulator(object) and set value to 1
acc[item] = 1;
}
// if accumulator property of current item is === length of arrays i.e appears the same amount of times as total amount of arrays in arrays
if(acc[item] === arrays.length){
// push item onto output
output.push(item)
}
})
// returns accumulator into reduce function
return acc
},{})
//returns output as the intersection
return output
}
The difference between intersection and union would be in the last if statement instead of checking against the total amount of arrays in arrays we would check if its value is equals to 1 (===1) now to the code:
// spread operator returns an array of arrays
function union(...arrays) {
// setting output to array
const output = []
// initial value is of accumulator is an object
reduce(arrays,function(acc,currentArray){
// for each item check if item exist in accumulator(object)
forEach(currentArray,function(item){
// if it does
if(item in acc) {
// increment its value
acc[item]++
}else {
// else make it a property of accumulator(object) and set value to 1
acc[item] = 1;
}
// if accumulator property of current item is === length of arrays i.e appears once
if(acc[item] === 1){
// push item onto output
output.push(item)
}
})
// returns accumulator into reduce function
return acc
},{})
//returns output as the union
return output
}
hope someone finds this helpful
If you want to solve this problem without using built-in functions like filter
function intersection(arrays) {
return reduce(arrays, function(arrA, arrB) {
forEach(arrA, function(item) {
if (arrB.indexOf(item) == -1) {
arrA.splice(arrA.indexOf(item), 1);
}
});
return arrA;
}, arrays[0]);
}
function union(arrays) {
return reduce(arrays, function(arrA, arrB) {
forEach(arrB, function(item) {
if (arrA.indexOf(item) == -1) {
arrA.push(item);
}
});
return arrA;
}, arrays[0]);
}
I'm trying to create a function that puts each array element in its own array, recursively.
I think my base case is correct, but my recursive call doesn't appear to be working. any insight?
function ownList(arr){
if (arr.length === 1) {
arr[0] = [arr[0]];
return;
} else {
return arr[0].concat(ownList(arr.slice(1)));
}
}
var arr = [1,2,3]
console.log(ownList(arr))// returns []
//should return [[1],[2],[3]]
Here I'm trying to put each pair in it's own list (recursive only). This code below is correct (update)
function ownListPair(arr){
if (arr.length === 0)
return arr;
else if(arr.length === 1)
return [[arr[0], 0]];
else
return [[arr[0], arr[1]]].concat(ownListPair(arr.slice(2)));
}
// var arr = [3,6,8,1,5]
var arr = [2,7,8,3,1,4]
//returns [ [ 2, 7 ], [ 8, 3 ], [ 1, 4 ]]
console.log(ownListPair(arr))
I prefer this solution for several reasons:
function ownList(a) {
return a.length == 0
? []
: [[a[0]]].concat(ownList(a.slice(1)))
}
It's shorter and more concise
It works for empty arrays as well
The actual wrapping happens only once in the last line. Treating length == 1 separately -- as suggested by others -- is not necessary.
It would more appropriate to make a length of 0 be the null case. Then you just have to get the brackets right. The thing on the left side of the concat should be an array consisting of the array containing the first element.
function ownList(arr) {
return arr.length ? [[arr[0]]].concat(ownList(arr.slice(1))) : [];
}
Here's an alternative, take your pick:
function ownList(arr) {
return arr.length ? [[arr.shift()]] . concat(ownList(arr)) : [];
}
Using a bit of ES6 magic for readability:
function ownList([head, ...tail]) {
return head === undefined ? [] : [[head]] . concat(ownList(tail));
}
Here the [head, ...tail] is using parameter destructuring which pulls the argument apart into its first element (head) and an array of remaining ones (tail).
Instead of concat you could also use the array constructor:
function ownList([head, ...tail]) {
return head === undefined ? [] : Array([head], ...ownList(tail));
}
I think your basic assumption is wrong. What you need to do is check if each item in the array is an array, if not just add the item to the new array, if so have the function run itself on the array item.
That is recursion.
This code does that kind of recursion...
function ownList(arr)
{
var newArr = [];
var length = arr.length;
for (var i = 0; i < length; i++) {
if (typeof(arr[i]) === 'object') {
newArr.push(ownList(arr[i]));
continue;
}
newArr.push([arr[i]]);
}
return newArr;
}
var arr = [1, 2, 3];
console.log(ownList(arr));
Would something like this work:
var arr = [1, 2, 3, ["a", "b", "c", ["str"]]],
result = [];
function flatten(input){
input.forEach(function(el){
if(Array.isArray(el)){
flatten(el)
}else{
result.push([el]);
}
});
}
flatten(arr);
console.log(JSON.stringify(result));
//[[1],[2],[3],["a"],["b"],["c"],["str"]]
JSBIN
Edit:
var result = [];
function flatten(input){
if (input.length === 0){
console.log( "result", result ); //[[1],[2],[3],["a"],["b"],["c"],["str"]]
return;
}
//if zeroth el of input !array, push to result
if (!Array.isArray(input[0])){
result.push(input.splice(0, 1));
flatten(input);
}else{
flatten(input[0]); //else, give input[0] back to flatten
}
}
window.onload = function(){
var arr = [1, 2, 3, ["a", "b", "c", ["str"]]];
flatten(arr);
}
JSBIN
After struggling through this today, turns out that this works :)
function ownList(arr){
//base case:
if (arr.length === 1) {
return [arr];
}
//recurse
//have to do two brackets here --> (arr.slice(0,1)) since length > 1
return [arr.slice(0,1)].concat(ownList(arr.slice(1)));
}
var arr = [1,2,3]
console.log(ownList(arr))// returns [[1],[2],[3]]
Using pure javascript, starting with an array, I would like to return and array by removing values that match any value in a second array.
I have solved this problem, but I believe with more code than is really necessary.
I am hoping for a more concise or elegant solution using only javascript.
function removeValues(arr){
array = arguments[0];
args = Array.prototype.slice.call(arguments);
len = arguments.length;
filtered = array.filter(function(n){
x = true;
for (var i = 1; i < len; i++) {
if (n == args[i]) { x = false; }
}
return x;
});
return filtered;
}
removeValues([1,2,3,1,2,3],2,3);
Should use a function that removes values from the first argument (an array) using values in one or more additional arguments.
When you're working with the filter function is not necessary to use loops because you're already in a loop. After converting the arguments into an array with [].slice.call(arguments), you could use indexOf that is responsible for returning the position of a value in an array, if a value is not exists, this returns -1, so we will take all the results that are -1
Your code could be reduced as well:
function removeValues(arr){
return arr.filter(function(val){
return [].slice.call(removeValues.arguments).slice(1).indexOf(val) === -1
})
}
console.log(removeValues([1,2,3,1,2,3],2,3))
ES6 Method: Using Rest parameters and Arrow Functions
var removeValues = (arr, ...values) => arr.filter(val => values.indexOf(val) === -1)
Try this instead...
function removeValues(){
var args = Array.prototype.slice.call(arguments).slice(1);
return arguments[0].filter(function(value) {
return args.indexOf(value) === -1;
});
}
removeValues([1, 2, 3, 1, 2, 3], 2, 3);
It does the exact same thing, but tidies it slightly.
Try like this:
var array1 = [ 1, 2, 3, 4, 5 ];
var array2 = [ 2, 3 ];
var result = array1.filter( function ( elem ) {
return array2.indexOf( elem ) === -1;
});
See example: Running code