Nick
Nick

Reputation: 12037

How to efficiently find a value inside nested objects using underscore's each function?

In my game, I need find a certain monster that is contained in an "units" array. This array is inside a spatial cell structure inside a world object. How can I find this unit without writing ugly code?

var foundUnit = null;
_.each(worldHandler.world, function(zone) {
  if ( foundUnit ) return;
  _.each(zone, function(cellX) {
    if ( foundUnit ) return;
    _.each(cellX, function(cellY) {
      if ( foundUnit ) return;
      if ( !_.isUndefined(cellY.units) ) {
        _.each(cellY.units, function(unit) {
          if ( foundUnit ) return;

          if ( unit.id === id ) foundUnit = unit;
        });
      }
    });
  });
});
return foundUnit;

The trouble here is that I can't use return when I found the right value. Return inside the _.each() will just continue that current loop. Is there a better/cleaner way to find a certain value inside a nested object?

Example data:

{ // World
    '1': { // Zone
        '-1': { // Cell X
            '-1': { // Cell Y
                'units': []
            },
            '0': {
                'units': [{id:5}]
            },
            '1': {
                'units': []
            }               
        }
    } {
        '0': {
            '-1': {
                'units': []
            },
            '0': {
                'units': []
            },
            '1': {
                'units': []
            }   
        }
    } {
        '1': {
            '-1': {
                'units': []
            },
            '0': {
                'units': []
            },
            '1': {
                'units': []
            }
        }
    }
}

Upvotes: 4

Views: 6592

Answers (2)

Mike Valenty
Mike Valenty

Reputation: 8981

You could flatten your nested structure into an array of units and then use _.find on it:

var zones = _.flatten(_.map(world, _.values));
var cells = _.flatten(_.map(zones, _.values));
var units = _.flatten(_.map(cells, _.values));
var unit = _.find(units, function(u) { return u.id == 7 });

If you're concerned about performance and you're looking up by unit.id then you should consider building an index:

var indexById = {};
_.each(units, function(u) {
    indexById[u.id] = u;
});

Then you can do constant time lookups: var unit = indexById[7];

Upvotes: 1

user663031
user663031

Reputation:

Check out _.some.

var foundUnit = null;
_.some(worldHandler.world, function(zone) {
    return _.some(zone, function(cellX) {
        return _.some(cellX, function(cellY) {
            return _.some(cellY.units, function(unit) {
                if ( unit.id === id ) {foundUnit = unit; return true; }
            });
        });
    });
});
return foundUnit;

Note that _.some no-ops if the object is null, so no need to check for that.

Upvotes: 6

Related Questions