74

I'm trying to make an array that if a value doesn't exist then it is added but however if the value is there I would like to remove that value from the array as well.

Feels like Lodash should be able to do something like this.

I'm interested in your best practises suggestions.

Also it is worth pointing out that I am using Angular.js

* Update *

if (!_.includes(scope.index, val)) {
    scope.index.push(val);
} else {
  _.remove(scope.index, val);
}
alexmac
  • 19,087
  • 7
  • 58
  • 69
Max Lynn
  • 1,738
  • 6
  • 22
  • 35

9 Answers9

110

You can use _.union

_.union(scope.index, [val]);
Afzal Hossain
  • 3,258
  • 1
  • 21
  • 13
92

The Set feature introduced by ES6 would do exactly that.

var s = new Set();

// Adding alues
s.add('hello');
s.add('world');
s.add('hello'); // already exists

// Removing values
s.delete('world');

var array = Array.from(s);

Or if you want to keep using regular Arrays

function add(array, value) {
  if (array.indexOf(value) === -1) {
    array.push(value);
  }
}

function remove(array, value) {
  var index = array.indexOf(value);
  if (index !== -1) {
    array.splice(index, 1);
  }
}

Using vanilla JS over Lodash is a good practice. It removes a dependency, forces you to understand your code, and often is more performant.

floribon
  • 19,175
  • 5
  • 54
  • 66
  • What is the support for using set? – Max Lynn May 06 '16 at 13:45
  • It's been around for a little while. IE11 though. https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Set – floribon May 06 '16 at 13:57
  • 1
    Looks like this like of code within the add() function is wrong: **array.indexOf(value) !== -1**, should be **array.indexOf(value) === -1** instead. Current code logic is: if item already exists - add. – Pjotr Sep 15 '16 at 12:50
  • 3
    Lodash vs Native porformance: https://github.com/you-dont-need/You-Dont-Know-Lodash-Underscore#_each – roadev Nov 29 '16 at 19:03
  • @roadev your link is not a performance comparison since both snippets don't do the same thing and are not equivalent. Using a native `for of` proves faster. Anyway this is unrelated to this question. – floribon Nov 30 '16 at 08:42
  • 18
    "Using vanilla JS over Lodash is a good practice." - Re-inventing the wheel is never good practice, unless, *after profiling your code* it is determined that the code in question is a bottleneck. Optimizing non-bottlenecks does not improve your app's performance and can actually have a negative overall impact since every minute spent optimizing a non-bottleneck is a minute NOT spent on something that matters. In general, it is better to use already-written & debugged code (when *profiling* does not direct you otherwise). – Troy Aug 31 '17 at 18:14
  • @Troy these are good points. I agree to not reinvent the wheel, but when 2 wheels are provided, third-party and built-in, I encourage to pick the native one (such as Set here). Then when there is a native alternative of 4 lines over sometimes a hundred I do also encourage to refrain from adding an extra dependency. That is, only if the lib is not already loaded for other reasons, in which case your point is valid. – floribon Sep 01 '17 at 17:29
  • 3
    @floribon Lodash allows us to create much more readable code and have great performances under the hook. Whether your feature is easy or complexe, I do see much more advantages using it and it brings also its complexity nevertheless this is my own opinion. As for using VanillaJS instead I have to be honest here and it just seems to me very bad to think that way. To hyperbolize this, you could also say that using VanillaJS instead instead of a Framework like Angular/React is great... Now, using TypeScript with Lodash is even more better due to a far better typing than your own usually. – C0ZEN Jul 08 '19 at 15:40
4

Perhaps _.pull() can help:

var _ = require('lodash');

function knock(arr,val){ 
   if(arr.length === _.pull(arr,val).length){
      arr.push(val);
   } 
   return arr;
}

Mutates the existing array, removes duplicates as well:

> var arr = [1,2,3,4,4,5];

> knock(arr,4);
[ 1, 2, 3, 5 ]

> knock(arr,6);
[ 1, 2, 3, 5, 6 ]

> knock(arr,6);
[ 1, 2, 3, 5 ]
S.D.
  • 29,290
  • 3
  • 79
  • 130
3

Use includes function to check that item is exists in array, and remove to delete existing item.

function addOrRemove(arr, val) {
  if (!_.includes(arr, val)) {
    arr.push(val);
  } else {
    _.remove(arr, item => item === val);
  }
  console.log(arr);
}

var arr = [1, 2, 3];
addOrRemove(arr, 1); // arr = [2, 3]
addOrRemove(arr, 4); // arr = [2, 3, 4]
addOrRemove(arr, 2); // arr = [3, 4]
<script src="https://raw.githubusercontent.com/lodash/lodash/4.11.2/dist/lodash.min.js"></script>
alexmac
  • 19,087
  • 7
  • 58
  • 69
  • I'm liking this answer alex but I tried it and it doesn't seem to be working for me. – Max Lynn May 06 '16 at 14:01
  • It seems to add very well but doesn't remove the value if the function is triggered again. I've updated my question with a code snippet – Max Lynn May 06 '16 at 14:02
1

In this case you can use 'concat' to push and 'uniq' to validate unique values:

example:

var ar = [1, 2, 3, 1, 5, 2, 4]
ar = _.uniq(_.concat(ar, 9))
//[1, 2, 3, 5, 4, 9]

e.g.: https://codepen.io/dieterich/pen/xeZNJY

ref.: https://lodash.com/docs/4.17.11#uniq

Darlan Dieterich
  • 2,369
  • 1
  • 27
  • 37
0

This single liner should do the job. If element to be inserted does not exist, it inserts the element and returns the length of the resulting array. If element exists in the array it deletes the element and returns the deleted element in a separate array.

var arr = [1,2,3,4,5],
    aod = (a,e,i=0) => !!~(i = a.indexOf(e)) ? a.splice(i,1) : a.push(e); 
    
document.write("<pre>" + JSON.stringify(aod(arr,6)) + JSON.stringify(arr) + "</pre>");
document.write("<pre>" + JSON.stringify(aod(arr,6)) + JSON.stringify(arr) + "</pre>");

Well actually i hate push since it returns the resulting array length value which is most of the time useless. I would prefer to have a reference to the resulting array to be returned so that you can chain the functions. Accordingly a simple way to achieve it is;

var arr = [1,2,3,4,5],
    aod = (a,e,i=0) => !!~(i = a.indexOf(e)) ? a.splice(i,1) : (a.push(e),a);
        
document.write("<pre>" + JSON.stringify(aod(arr,6)) + JSON.stringify(arr) + "</pre>");
document.write("<pre>" + JSON.stringify(aod(arr,6)) + JSON.stringify(arr) + "</pre>");

So now this is reasonably chainable.

Redu
  • 25,060
  • 6
  • 56
  • 76
0

If you don't need to support IE, or if you are using polyfills, you can use Array.prototype.includes()

const addUniq = (array, value) => array.includes(value) 
  ? array.length 
  : array.push(value);
0

The simplest way to do this is use _.isEmpty and _.remove Lodash functions:

if (_.isEmpty(_.remove(array, value)) {
   array.push(value);
}

After remove function will be return removed values or an empty array, and if return an empty array then we will add a new value.

jedicode
  • 145
  • 1
  • 11
0

It's an old question but what you are looking for is XOR Lodash xor

It adds the value if is not already there and removes it otherwise. Great for toggle use-cases.

cifuentes
  • 1,229
  • 1
  • 8
  • 6