1

I've an array: var old_array = [1, 2, 3, 4, 5]

If 5 exists in this array, remove it otherwise add it.

var new_array = [];
var number_existed = false;
for(var i=0,len=old_array.length;i<len;i++) {
   if(old_array[i] == 5) {
   new_array = old_array.slice(i+1);
   number_existed = true;
   break;
   } else {
      new_array.push( old_array[i] );
   }
}

if(!number_existed) {
    new_array.push(5);
}

It works, but I need improvements. How can I improve this code. I wanna see reduce function in action but just not able to come up with anything logical.

frosty
  • 15
  • 2
  • Why not if on a `contains()` or `indexOf()` and based off of that, push it in the array, or splice it out? – Taplar Mar 19 '19 at 18:31
  • *it works* ... so [1, 1, 5, 1, 1] should result in [1, 1] ? – Jonas Wilms Mar 19 '19 at 18:34
  • @JonasWilms yeah, I didn't see that one coming, thanks – frosty Mar 19 '19 at 18:35
  • Possible duplicate of [Array.push() if does not exist?](https://stackoverflow.com/questions/1988349/array-push-if-does-not-exist) – Heretic Monkey Mar 19 '19 at 18:36
  • 2
    Jeto's answer should solve this. There is no real reason to use `reduce` for this, as it would just complicate things. If you were just filtering out the existing `5`s, `reduce` would be an overcomplicated but not unreasonable alternative to `filter`. But since you also want to add a `5` if it's not there, your accumulator is going to need to carry extra state -- or you would store it somewhere worse. Such code is feasible, but quite ugly compared to the alternatives. – Scott Sauyet Mar 19 '19 at 18:41
  • @ScottSauyet I understand now, thanks. a simple filter and a boolean was all that was needed. thanks. but I'm going to accept Jonas' ans coz it's a bit cleaner. – frosty Mar 19 '19 at 18:47

7 Answers7

3

You can check if the element already exists with Array.includes, then either Array.filter it to remove it, or append it using spread syntax:

function addOrRemove5(arr) {
  return arr.includes(5) ? arr.filter(v => v !== 5) : [...arr, 5];
}

console.log(addOrRemove5([1, 2, 3, 4, 5]));
console.log(addOrRemove5([1, 2, 3, 4]));
Jeto
  • 14,596
  • 2
  • 32
  • 46
1
 let fiveFound = false;
 const result = input.filter(it => it !== 5 || !(fiveFound = true));
 if(!fiveFound) result.push(5);

reduce won't help you here.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
1

Using reduce is not best approach, because reduce is used to return a single item. In my solution I use findIndex and apply your logic.

function usingFunctions(old) {
 var index = old.findIndex( function(it) { return it == 5  } );
 var new_arr = old.slice( index + 1 );
 if( index < 0 ) new_arr.push(5);
 return new_arr
}

var old = [1, 2, 6, 8, 5, 3, 6, 9];
console.log( usingFunctions(old) );
old = [1, 2, 6, 8, 3, 6, 9];
console.log( usingFunctions(old) );
1

One more solution, expanding on my comment:

Jeto's answer should solve this. There is no real reason to use reduce for this, as it would just complicate things. If you were just filtering out the existing 5s, reduce would be an overcomplicated but not unreasonable alternative to filter. But since you also want to add a 5 if it's not there, your accumulator is going to need to carry extra state -- or you would store it somewhere worse. Such code is feasible, but quite ugly compared to the alternatives.

Here is what such uglier code might look like:

const addOrRemove5 = (arr) => { 
  const {found, xs} = arr.reduce(
    ({found, xs}, x) => x === 5 ? {found: true, xs} : {found, xs: [...xs, x]},
    {found: false, xs: []}
  )
  return found ? xs : [...xs, 5]
}

console.log(addOrRemove5([1, 5, 2, 3, 4, 5])) //=> [1, 2, 3, 4]
console.log(addOrRemove5([1, 2, 3, 4]))       //=> [1, 2, 3, 4, 5]

This uses an accumulator that looks like {found: boolean, xs: [number]}, starting with {found: false, xs: []}, updating found to be true on each 5, and adding each non-5 to the values. Then after that runs, we return either the existing values or the existing values and an additional 5, depending on the value of found.

Again, this is much uglier than Jeto's solution. I would not recommend it here. But such techniques are not terribly uncommon when you need carry additional information as well as reducing a value. And a variant of this could let you work only with expressions and not any statements, by putting the resulting {found, xs} object in a default parameter:

const addOrRemove5 = (
  arr, 
  {found, xs} = arr.reduce(
    ({found, xs}, x) => x === 5 ? {found: true, xs} : {found, xs: [...xs, x]},
    {found: false, xs: []}
  )
) => found ? xs : [...xs, 5]

Again this is not recommended in this case, but it's a useful trick to know.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
0

You don't need reduce() just use filter()

var old_array = [1, 2, 3, 4, 5]

var new_array = old_array.filter(x => x === 5);
console.log(new_array);
Maheer Ali
  • 35,834
  • 5
  • 42
  • 73
0

You can find it using indexOf to get the index, and remove it using splice or add it using push

var array1 = [1, 2, 3, 4, 5]

function modify_array(value, array) {
  let idx = array.indexOf(value)
  idx > -1 ? array.splice(idx, 1) : array.push(value)
  return array
}

// Test cases

// Remove 5
console.log(modify_array(5, array1))
// Add 10
console.log(modify_array(10, array1))
Get Off My Lawn
  • 34,175
  • 38
  • 176
  • 338
0

If you really want to use reduce():

  function togglePresenceOfFive(array) {
  if (array.length === 0) { // Reduce can't handle this case because the loop never runs
        return [5];
    } else {
        const reduceResult = array.reduce((acc, next, index, arr) => {
            const isLast = index === arr.length - 1;

            if (next === 5) {
                return {
                    newArray: acc.newArray,
                    fiveFound: true
                };
            } else if (!acc.fiveFound && isLast) {
                return {
                    newArray: acc.newArray.concat(next, 5),
                    fiveFound: false
                }
            } else {
                return {
                    newArray: acc.newArray.concat(next),
                    fiveFound: acc.fiveFound
                };
            }
            },
            { newArray: [], fiveFound: false }
        );

        return reduceResult.newArray; 
    }
}

  return reduceResult.newArray;
}

But this is more readable in my opinion (assuming you don't mind mutating the original array):

function togglePresenceOfFive(array) {
    const indexOfFive = array.indexOf(5);

    if (indexOfFive > -1) {
        array.splice(indexOfFive, 1);
    } else {
        array.push(5);
    }

    return array;
}
Mattias Martens
  • 1,369
  • 14
  • 16
  • 1
    See my answer for a cleaner version of the same `reduce` idea. It also deals with the empty array case by giving a starting value to the accumulator. But of course the point is that this is likely overkill. – Scott Sauyet Mar 19 '19 at 19:08