Related
I was wondering how I'd go about implementing a method in javascript that removes all elements of an array that clear a certain condition. (Preferably without using jQuery)
Ex.
ar = [ 1, 2, 3, 4 ];
ar.removeIf( function(item, idx) {
return item > 3;
});
The above would go through each item in the array and remove all those that return true for the condition (in the example, item > 3).
I'm just starting out in javascript and was wondering if anyone knew of a short efficient way to get this done.
--update--
It would also be great if the condition could work on object properties as well.
Ex.
ar = [ {num:1, str:"a"}, {num:2, str:"b"}, {num:3, str:"c"} ];
ar.removeIf( function(item, idx) {
return item.str == "c";
});
Where the item would be removed if item.str == "c"
--update2--
It would be nice if index conditions could work as well.
Ex.
ar = [ {num:1, str:"a"}, {num:2, str:"b"}, {num:3, str:"c"} ];
ar.removeIf( function(item, idx) {
return idx == 2;
});
You can use Array filter method.
The code would look like this:
ar = [1, 2, 3, 4];
ar = ar.filter(item => !(item > 3));
console.log(ar) // [1, 2, 3]
You could add your own method to Array that does something similar, if filter does not work for you.
Array.prototype.removeIf = function(callback) {
var i = 0;
while (i < this.length) {
if (callback(this[i], i)) {
this.splice(i, 1);
}
else {
++i;
}
}
};
To me, that's one of the coolest features of JavaScript. Ian pointed out a more efficient way to do the same thing. Considering that it's JavaScript, every bit helps:
Array.prototype.removeIf = function(callback) {
var i = this.length;
while (i--) {
if (callback(this[i], i)) {
this.splice(i, 1);
}
}
};
This avoids the need to even worry about the updating length or catching the next item, as you work your way left rather than right.
You can use Array.filter(), which does the opposite:
ar.filter(function(item, idx) {
return item <= 3;
});
You can use lodash.remove
var array = [1, 2, 3, 4];
var evens = _.remove(array, function(n) {
return n % 2 == 0;
});
console.log(array);
// => [1, 3]
console.log(evens);
// => [2, 4]
Make it a one-liner with arrow function:
ar = ar.filter(i => i > 3);
simply write the following example if condition could work on object properties as well
var ar = [ {num:1, str:"a"}, {num:2, str:"b"}, {num:3, str:"c"} ];
var newArray = [];
for (var i = 0, len = ar.length; i<len; i++) {
if (ar[i].str == "b")
{newArray.push(ar[i]);};
};
console.log(newArray);
See the example Live Example
if you need to remove exactly one item, and you know for sure that the item exists, you can use this one-liner:
ar.splice(ar.findIndex(el => el.id === ID_TO_REMOVE), 1);
// or with custom method:
let ar = [ {id:1, str:"a"}, {id:2, str:"b"}, {id:3, str:"c"}, {id:4,str:"d"} ];
ar.removeById = id => ar.splice(ar.findIndex(el => el.id === id), 1);
ar.removeById(ID_TO_REMOVE);
http://jsfiddle.net/oriadam/72kgprw5/
ES6 only
I love these kinds of questions and just a different version from me too... :)
Array.prototype.removeIf = function(expression) {
var res = [];
for(var idx=0; idx<this.length; idx++)
{
var currentItem = this[idx];
if(!expression(currentItem))
{
res.push(currentItem);
}
}
return res;
}
ar = [ 1, 2, 3, 4 ];
var result = ar.removeIf(expCallBack);
console.log(result);
function expCallBack(item)
{
return item > 3;
}
My solution for an array of numbers would be:
ar = ar.filter(item => item < 4);
For the in-place remove, my solution is
ar.filter(item => !(item > 3))
.forEach(obsoleteItem => ar.splice(ar.indexOf(obsoleteItem), 1));
Incorrect way
First of all, any answer that suggests to use filter does not actually remove the item. Here is a quick test:
var numbers = [1, 2, 2, 3];
numbers.filter(x => x === 2);
console.log(numbers.length);
In the above, the numbers array will stay intact (nothing will be removed). The filter method returns a new array with all the elements that satisfy the condition x === 2 but the original array is left intact.
Sure you can do this:
var numbers = [1, 2, 2, 3];
numbers = numbers.filter(x => x === 2);
console.log(numbers.length);
But that is simply assigning a new array to numbers.
Correct way to remove items from array
One of the correct ways, there are more than 1, is to do it as following. Please keep in mind, the example here intentionally has duplicated items so the removal of duplicates can be taken into consideration.
var numbers = [1, 2, 2, 3];
// Find all items you wish to remove
// If array has objects, then change condition to x.someProperty === someValue
var numbersToRemove = numbers.filter(x => x === 2);
// Now remove them
numbersToRemove.forEach(x => numbers.splice(numbers.findIndex(n => n === x), 1));
// Now check (this is obviously just to test)
console.log(numbers.length);
console.log(numbers);
Now you will notice length returns 2 indicating only numbers 1 and 3 are remaining in the array.
I am trying to create a little project where I can count the number of elements in an array. This part I have already done. My question is how can I fix a counting problem? I'm using a mapping method to get my element occurrence counter, but I want to put the data into an array. The only way I know how is to take the data from the mapping with .get. They way I'm doing it is by using this first part here:
let winners = [1, 2, 3, 2];
let nullArray = [];
let mode = new Map([...new Set(winners)].map(
k => [k, winners.filter(t => t === k).length]
));
for (let n in winners) {
nullArray.push(mode.get(winners[n]));
console.log(nullArray);
}
However, this will *n push matches. Like, if you have l=[1,2,2], because l[1]=2, and l[2]=2, they will be pushed into the nullArray as 2, however, it will also push l[2] and l[1] as the values are different. If you have three matches, it would duplicate 3 times, and so on. To counter this, I tried making a detector that would calculate when the same numbers in the nullArray are from the same copy but in a different order. To do this, I used the code I have below (combined with the original code)
let winners = [1, 2, 3, 2];
let nullArray = [];
let mode = new Map([...new Set(winners)].map(
k => [k, winners.filter(t => t === k).length]
));
for (let n in winners) {
nullArray.push(mode.get(winners[n]));
console.log(nullArray);
}
for (let num in nullArray) {
for (let c in nullArray) {
if (nullArray[num] === nullArray[c] && winners[num] === winners[c]) {
nullArray.splice(num, 1);
}
console.log(nullArray);
}
}
However, whenever I try this, the specific output on this array is [2,2]. How could I make a general solution that will eliminate all duplicate copies, only leaving a single copy of the number count (which in the case of [1,2,3,2], I would want nullArray=[1,2,1] as an output)
You can do something like this.
If you don't care about the order, you can just do the following.
const remove_dup_and_count = (winners = [1, 2, 3, 2]) =>{
let map = {}
//count elements
for (let n in winners) {
const curr_val = winners[n]
//duplicate, so we increment count
if(curr_val in map) map[curr_val] += 1
//first seen, so we start at 1
else map[curr_val] = 1
}
//lets grab the array of all keys in map
const keys_arr = Object.keys(map)
let count_arr = []
for(let i of keys_arr){
count_arr.push(map[i])
}
return count_arr
}
console.log(remove_dup_and_count())
If you care about the order, this is your best bet:
const remove_dup_and_count = (winners = [1, 2, 3, 2]) =>{
let map = new Map()
//count elements
for (let n in winners) {
const curr_val = winners[n]
//duplicate, so we increment count
if(map.get(curr_val)) map.set(curr_val, map.get(curr_val) + 1)
//first seen, so we start at 1
else map.set(curr_val,1)
}
let count_arr = []
//lets grab the array of all keys in map
for (const [key, value] of map) {
count_arr.push(value)
}
return count_arr
}
console.log(remove_dup_and_count())
I think you can use .reduce() method and then retrieve how many times the value is repeated in array using map.values(); something like the following snippet:
const winners = [1, 2, 3, 2];
const mapWinners = winners.reduce((winnersAccumulator, singleWinner) => winnersAccumulator.set(singleWinner, (winnersAccumulator.get(singleWinner) || 0) + 1), new Map())
console.log([...mapWinners.values()])
i would like to Replace duplicates items with different values.
eg arr = [1,1,1,1,2,2,2,3] i want to replace the duplicates with R
So the result looks like this arr = [1,R,R,R,2,R,R,3]
right now I'm using this approach:
arr = [1,1,1,1,2,2,2,3]
let previous = 0;
let current = 1;
while (current < arr.length) {
if (arr[previous] === arr[current]) {
arr[current] = 'R';
current += 1;
} else if (arr[previous] !== arr[current]) {
previous = current;
current += 1;
}
}
i wondering if there is different approach for to achieve that. for Example using Lodash (uniqwith, uniq).
Thanks
here's how I'd do it
It look if the current element is the first of the array with the current value and replace it if not
It may take some times on very big arrays
const input = [1,1,1,1,2,2,2,3]
let output = input.map(
(el, i) => input.indexOf(el) === i ? el : 'R'
)
console.log(output)
You could take a closure over a Set and check if the value is already seen or not.
let array = [1, 1, 1, 1, 2, 2, 2, 3],
result = array.map((seen => v => seen.has(v) ? 'R' : (seen.add(v), v))(new Set));
console.log(...result);
A check previous value approach
let array = [1, 1, 1, 1, 2, 2, 2, 3],
result = array.map((v, i, { [i - 1]: l }) => l === v ? 'R' : v);
console.log(...result);
This is actually very simple to do by using sets (Set).
const initialArray = [1,1,1,1,2,2,2,3];
const valuesSet = new Set();
const newArray = initialArray.map((value) => {
if (valuesSet.has(value)) {
return 'R';
} else {
valuesSet.add(value);
return value;
}
});
I have a setup like this:
docs[0]['edits'] = 1;
docs[1]['edits'] = 2;
I want to get the docs[index] of the one with the most edits.
Using Underscore I can get the appropriate array (ie the value docs[1]), but I still don't know the actual index in relation to docs.
_.max(docs, function(doc) { return doc['edits']; });
Any help would be greatly appreciated.
To do it without a library, iterate over the array (possibly with reduce), storing the highest number so far and the highest index so far in a variable, reassigning both when the item being iterated over is higher:
const edits = [
3,
4,
5,
0,
0
];
let highestNum = edits[0];
const highestIndex = edits.reduce((highestIndexSoFar, num, i) => {
if (num > highestNum) {
highestNum = num;
return i;
}
return highestIndexSoFar;
}, 0);
console.log(highestIndex);
Another way, with findIndex, and spreading the edits into Math.max (less code, but requires iterating twice):
const edits = [
3,
4,
5,
0,
0
];
const highest = Math.max(...edits);
const highestIndex = edits.indexOf(highest);
console.log(highestIndex);
Just use maxBy
const _ = require('lodash');
const docs = [{'edits': 1}, {'edits': 2}, {'edits': 0}, {'edits': 4}, {'edits': 3}]
const result = _.maxBy(docs, a => a.edits)
console.log(result)
https://repl.it/#NickMasters/DigitalUtterTechnologies
Pure JS way
const result2 = docs.reduce((result, { edits }) => edits > result ? edits : result, Number.MIN_SAFE_INTEGER)
console.log(result2)
I want to remove specific elements in the original array (which is var a). I filter() that array and splice() returned new array. but that doesn't affect the original array in this code. How can I easily remove those elements from the original array?
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}]
var b = a.filter(function (e) {
return e.name === 'tc_001';
});
b.splice(0,1);
console.log(a);
console.log(b);
The Array.prototype.filter() method is used to collect an element set not only one item. If you would like to get one item by evaluating a condition then you have three other options:
Array.prototype.indexOf()
Array.prototype.findIndex()
Array.prototype.find()
Accordingly only if you want to make an operation on more than one item you should think of using the filter function. None of the answers is complete in terms of the job that is needed to be done.
They use the filter function to isolate a set (happens to be only one item in this example) but they don't show how to get rid of the whole set. Well ok, let's clarify.
If you want to do find and delete only one item of your array it shall be done like this
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}];
a.splice(a.findIndex(e => e.name === "tc_001"),1);
console.log(a);
However since you mention "specific elements" in plural, then you will need to collect a set of selected items and do the above job one by one on each element in the set. So the proper approach would be.
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}],
b = a.filter(e => e.name === "tc_001");
b.forEach(f => a.splice(a.findIndex(e => e.name === f.name),1));
console.log(a);
Regardless of how many elements there are in your selected list, this will do your job. Yet I believe although this looks logical it does tons of redundant job. First filters and then per filtered element does index search this and that. Although I know that findIndex is crazy fast still I would expect this one to turn out to be noticeably slow especially with big arrays. Let's find an O(n) solution. Here you go:
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}];
a = a.reduce((p,c) => (c.name !== "tc_001" && p.push(c),p),[]);
console.log(a);
So this must be it.
Another way is filtering in two list like this:
const originalList = [{condition:true}, {condition: false}, {condition: true}];
// wished lists
const listWithTrue = originalList.filter(x=>x.condition);
const listWithFalse = originalList.filter(x=>!x.condition); // inverse condition
If I understood you, you want to remove the elements that matches the filter from the original array (a) but keep them in the new array (b) See if this solution is what you need:
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}]
var b = a.filter(function (e) {
return e.name === 'tc_002'
});
b.forEach(function(element) {
console.log(element)
var index = a.indexOf(element)
console.log(index)
a.splice(index, 1)
})
Result:
a = [{"name":"tc_001"},{"name":"tc_003"}] b = [{"name":"tc_002"}]
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_002'}, {name:'tc_002'}, {name:'tc_003'}];
while ( a.findIndex(e => e.name === 'tc_002' ) >= 0 )
a.splice( a.findIndex(f => f.name === 'tc_002'),1);
console.log(a);
You can assign filter array to a itself and then apply splice over it.
Example below:
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}]
a = a.filter(function (e) {
return e.name === 'tc_001';
});
a.splice(0,1);
If being able to filter multiple elements is important, how about using reduce to iterate over the array and sort them into filtered and unfiltered categories. This has the upside of not iterating over the array more than once before processing the results.
function splitArray (arr, filterFn) {
return arr.reduce((acc, el) => {
const conditionMet = filterFn(el)
if (conditionMet)
return { ...acc, filtered: [...acc.filtered, el] }
return { ...acc, unfiltered: [...acc.unfiltered, el] }
}, { filtered: [], unfiltered: [] })
}
You could then split an array into two factions and apply the second to the original:
const arr = [1, 2, 3, 4]
const {filtered, unfiltered} = splitArray(arr, el => el > 2)
console.log(filtered) // [1, 2]
console.log(unfiltered) // [3, 4]
/* Do something with filtered */
arr = unfiltered
You can either make changes to the original array:
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}]
a = a.filter(function (e) {
return e.name === 'tc_001';
});
a.splice(0,1);
Or in your own code just apply to a everything you've done to b
a = b
Perhaps if I expand on what each action does, it will be more clear.
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}]
// You now have array a with 3 elements.
b = a.filter(function (e) {
return e.name === 'tc_001';
});
// a is still 3 elements, b has one element (tc_001)
b.splice(0,1);
// You remove the first element from b, so b is now empty.
If you want to remove the elements tc_002 and tc_003 from a but keep them in b, this is what you can do:
var a = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_003'}]
b = a.filter(function (e) {
return e.name != 'tc_001';
});
a = a.filter(function(item) { return b.indexOf(item) == -1; });
console.log(a); // tc_001
console.log(b); // tc_002, tc_003
You might try this approach.
Loop the array backwards, compare on each iteration and delete if matches.
In this example the objective is to delete those arrays whose name is "tc_001"
var x = [{name:'tc_001'}, {name:'tc_002'}, {name:'tc_001'}, {name:'tc_003'}, {name:'tc_001'},{name:'tc_002'}, {name:'tc_001'}, {name:'tc_003'}, {name:'tc_001'}]
var find_and_delete = "tc_0001"
for (var i = x.length - 1; i >= 0; i--) {
if(x[i].name == find_and_delete){
x.splice(i,1)
}
}
after this the new value of x is
[{name:'tc_002'}, {name:'tc_003'}, {name:'tc_002'}, {name:'tc_003'}]
This function deletes filtered values in an array arr.
function filterpop(arr, fn) {
let filteredValues = [];
for (let i = arr.length - 1; i >= 0; i--) {
if (fn(arr[i])) {
filteredValues.push(arr.splice(i, 1)[0]);
}
}
return filteredValues;
};
Example:
const myArr = [1, 2, 3, 4, 5, 6];
const filteredArr = filterpop(arr, (v) => v > 3));
console.log(myArr)
// [1, 2, 3]
console.log(filteredArray)
// [4, 5, 6]
The truly elegant solution though, is be to create a custom array method:
Array.prototype.filterpop = function (fn) {
let filteredValues = [];
for (let i = this.length - 1; i >= 0; i--) {
if (fn(this[i])) {
filteredValues.push(this.splice(i, 1)[0]);
}
}
return filteredValues;
};
Example:
const myArray = [1, 2, 3, 4, 5, 6];
const filteredArray = myArray.filterpop((v) => v > 3); // CALL THE FILTER FUNCTION AS A METHOD ON YOUR ARRAY
console.log(myArr)
// [1, 2, 3]
console.log(filteredArray)
// [4, 5, 6]
const deleteItem = (idx) => {
array.filter((it, id) => id !== idx);
}