fstab
fstab

Reputation: 5039

Best way to replace missing/undefined values in an array?

I need to process some arrays that contains undefined values, like the following:

[ 1, 1, 1, 1, 1, , 1, 1 ]
[ 1, , 1, , , 1, 1 ]
[ 1, , , , , 1, 1 ]

What I need to achieve is not a removal of the undefined values, but I need to replace them with zeros.

I tried to use underscore.js to achieve this; without success.

The following is my solution attempt:

binarymap = _.map(binarymap, function(curr){
    // let's replace all undefined with 0s
    if(_.isUndefined(curr)) {
        return 0;
    }
    return curr;
});

Unfortunately, it does not work. underscore.js's function _.map totally ignores undefined values.

Any ideas? Elegant solutions?

Upvotes: 3

Views: 8146

Answers (5)

joemaller
joemaller

Reputation: 20616

Maybe something changed since this was posted, but both underscore (v1.8.3) and lodash (v4.13.1) handle this quite well:

var u = [1, , , , , 1, 1];
_.map(u, function(n) { return n || 0; });

// [1, 0, 0, 0, 0, 1, 1]

Native loops skip undefined entries because they explicitly only loop over defined elements of an array (ref). The source array doesn't contain undefined elements, it contains elements which were never defined at all.

These arrays are different:

[1, undefined, 1]
[1, , 1]

The first array defines its first index as undefined.
The second array does not define the first index at all.
Both have a length of 3.

This feels like it's starting to go off into the WAT fringes, but it's a subtle difference worth knowing.

Since we're in the weeds already, there is a convoluted native solution I'd never use in real life:

var u = [1, , , , , 1, 1];
Array(u.length).join(0).split('').map(function(n, i) { return u[i] || 0; })

// [1, 0, 0, 0, 0, 1, 1]

Array(u.length).join(0).split('') yields a dummy array of arbitrary strings which can then be mapped over. The map function references the original array by index, then uses a simple boolean switch to return defined values or zero.

Upvotes: 2

thefourtheye
thefourtheye

Reputation: 239623

The actual problem here is the missing array elements,

Array elements may be elided at the beginning, middle or end of the element list. Whenever a comma in the element list is not preceded by an AssignmentExpression (i.e., a comma at the beginning or after another comma), the missing array element contributes to the length of the Array and increases the index of subsequent elements. Elided array elements are not defined. If an element is elided at the end of an array, that element does not contribute to the length of the Array.

And Array.prototype.map skips all the missing array elements,

callbackfn is called only for elements of the array which actually exist; it is not called for missing elements of the array.

So, in order to make the Array elements to be considered by the map function, the simplest way I could think of is to tweak your approach a little bit, like this

var arr = [ 1, , , , , 1, 1 ];
console.log(_.map(Array.apply(null, arr), function (currentItem) {
    return _.isUndefined(currentItem) ? 0 : currentItem;
}));
# [ 1, 0, 0, 0, 0, 1, 1 ]

Here, Array.apply (which is actually Function.prototype.apply) does the important thing, converting the missing elements to undefined.

console.log(Array.apply(null, arr));
# [ 1, undefined, undefined, undefined, undefined, 1, 1 ]

Upvotes: 4

Andy
Andy

Reputation: 63569

If you wanted to you could add the function as a mixin for use in an underscore chain. Improvised from here.

_.mixin({
  dothis: function(obj, interceptor, context) {
    return interceptor.call(context, obj);
  }
});

function resetArray(arr) {
  for (var i = 0, l = arr.length; i < l; i++) {
    if (arr[i] === undefined) arr[i] = 0;
  }
  return arr;
}

var arr = [ 1, , , , , 1, 1 ];

_(arr)
  .chain()
  .dothis(resetArray)
  .tap(console.log) // [1, 0, 0, 0, 0, 1, 1]

Fiddle

Edit: actually, there's a simpler way of doing that:

_.mixin({
  resetArray: function (arr) {
    for (var i = 0, l = arr.length; i < l; i++) {
        if (arr[i] === undefined) arr[i] = 0;
    }
    return arr;
  }
});

var arr = [1, , , , , 1, 1];

_(arr)
  .chain()
  .resetArray()
  .tap(console.log)

Fiddle

Upvotes: 0

Amin Abu-Taleb
Amin Abu-Taleb

Reputation: 4511

If all you need is to remove empty elements from the array, you don't need to use underscore, just iterate over the array removing empty elements with splice.

You could add that feature prototyping Array class in javascript:

var array = [ 1, 1, , , , , 1, 1 ]; 

    Array.prototype.replaceEmptyElements = function(value) {
      for (var i = 0; i < this.length; i++) {
        if (typeof this[i] == "undefined") {         
          this[i] = value;
        }
      }
      return this;
    };

var val = //your value
array.replaceEmptyElements(val);

Upvotes: 0

EricG
EricG

Reputation: 3849

Is this what you mean..?

var arr = [ 1, , , , , 1, 1 ];
for( var i = 0; i < arr.length; i++ ) {
 if( typeof(arr[i])==="undefined" ) {
  arr[i] = 9;
 }
}
console.log( arr );

// yields [1, 9, 9, 9, 9, 1, 1] 

Upvotes: 2

Related Questions