Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Object-preserving map() function #220

Closed
ThiefMaster opened this Issue · 28 comments
@ThiefMaster

Would it be possible to add a map()-like function (since changing the current one would break things) which works exactly like map() but returns an object instead of an array if an object was passed?

Example:

_.map2({abc: 123, xyz: '6', foo: '9'}, function(num, key) {
    return parseInt(num, 10);
});
=> {abc: 123, xyz: 6, foo: 9}

It might also make sense to have it return [key, value] tuples but just modifying the values might be sufficient for many cases.

@amasad

That would be very handy.
Having both options of returning either a key/value tuple or a single value would be great.

@greylurk

That seems like the same functionality could be obtained by slightly re-writing your iterator to an each() function:

_.each({abc: 123, xyz: '6', foo: '9'}, function( key, value, obj ) {
  obj[key] = parseInt(val,10);
});

=> {abc: 123, xyz: 6, foo: 9}

Is this just a syntactic sugar?

@amasad

@greylurk Two reasons why your technique can't accomplish what is needed:

  • The native forEach and the _.each methods always returns undefined and not the list.
  • By definition the map function would return a new list and keeps the original list unmodified (which is essential).
@ThiefMaster

I think he meant map to return a new object.

@jashkenas
Owner

What you proposed isn't a true object map, which would be able to specify the keys as well as the values of the returned object. There are several interesting versions of object mapping, none of which are mainstream enough to deserve to make it into Underscore proper -- all of which are quite useful as _.mixin functions, for your own purposes, or for an Underscore extension.

@jashkenas jashkenas closed this
@mrkurt

Would a mapValues be more generically useful? I find myself wanting to do that pretty frequently after a groupBy.

@adrianheine

I recently used an implementation like the following:

_.mixin({
    mapValues: function (input, mapper) {
        return _.reduce(input, function (obj, v, k) {
            obj[k] = mapper(v, k, input);
        }, {});
    }
});
@yuchi

Completely OT: I propose a hipster-underscore extension which implements all the mixins not enough mainstream to make it into the official repo.

The motto is: I used an associative map function way before it made into underscore

@mahemoff

yuchi - an "overscore" library, good move that.

@yuchi

+1 for Overscore.js: Underscore.js for hipsters

@jashkenas
Owner

+1 for Overscore.js

@eeroan

You forgot to return the obj. With this modification it works:

_.mixin({
    mapValues: function (input, mapper) {
        return _.reduce(input, function (obj, v, k) {
            obj[k] = mapper(v, k, input);
            return obj;
        }, {});
    }
});
@eethann

Apparently hipsters don't care about context.

This update allows _.mapValues(list, iterator, this) to work, etc.

_.mixin({
  mapValues: function (input, mapper, context) {
    return _.reduce(input, function (obj, v, k) {
      obj[k] = mapper.call(context, v, k, input);
      return obj;
    }, {}, context);
  }
});

Created a gist to track any further revisions: https://gist.github.com/3430971

@kennknowles

I was about to submit a pull request for this and I am surprised to see that it has been discarded as "not mainstream". There are two major reasons for my surprise:

(1) It is mainstream in the sense of being in basically all standard libraries for languages advanced enough to think about such things:

Racket, Ruby, and Python share Underscore's behavior of (more or less) coercing dictionaries to a list first. Languages such as Java, Go, and C# are too first-order (in spirit, though at this point they could advance) to have equivalent libraries at all. Common Lisp doesn't even return a value... Basically the further back in time you go, the less like mapValues you get.

Underscore is a step ahead of Java but a step behind modern FP. Clojure is evidence that it does not require types, though they undoubtedly played a part in people eventually figuring this out. And this is figured out, which is my second reason.

(2) This is the fundamental map over dictionaries in the sense of them being a functor - the concept that unifies all "container" data structures (used in random incorrect ways by C++, Ruby, and Prolog, so just don't go there). Those languages advanced enough to express this have done so: Haskell, Scala, Clojure. Someone even experimented with the idea in our own Javascript.

As you say, there are several interesting maps on dictionaries. A good start would be mapKeys and mapValues. I know that Javascript objects are not exactly dictionaries, and that the current map behavior comes from the (appropriate) behavior on arrays, but just use a different name and save everyone from reimplementing this over and over. It really seems like it is in the spirit of Underscore's adoption of pithy and standard functions.

Please reconsider, @jashkenas.

+1

-- Kenn

@kennknowles kennknowles referenced this issue in lodash/lodash
Closed

mapValues for objects #169

@JensRantil

An alternative would be to implement the inverse of _.pairs(...) that converts an array of pairs into an object. This would open up to using all collections functions (including both maps and reduces) on an object. It would also have the added feature that operations can be made on keys.

The Python built-in dict(...) supports this construct.

Example:

function pairToObject(arr) {
  var obj = {};
  _.each(arr, function(el) {
    var k = el[0];
    var v = el[1];
    obj[k] = v;
  });
  return obj;
}
@kennknowles

This is already included in the overloaded functionality of _.object. If Javascript had destructuring then it would be about as easy to compose _.object, _.map and _.pairs to recreate the standard mapValues. But since it does not, having a library of these one-liners is a big part of underscore's value.

@nh2

This would be super useful.

@jonahkagan

@kennknowles, I'm totally with you. mapValues is not only theoretically pleasing, but also very useful in everyday coding.

FWIW, here's the implementation I use:

_.mixin({ mapValues: function (obj, f_val) { 
  return _.object(_.keys(obj), _.map(obj, f_val));
}});

I only include it since it's terser than the other implementations I've seen on this thread (though probably slower - would be cool to do some benchmarking) and also demonstrates how other Underscore functions compose to create mapValues.

@jashkenas
Owner

That is an extremely cool implementation.

@marekventur

+1 for reopening this. I constantly have to rewrite this.

@acjay

@jashkenas Do you have good links for the other types of object mappings you mentioned earlier? In my mind, there's probably also a mapKeys, which would be pretty much identical to mapValues (except for requiring a key deduplication strategy), and then I suppose also a sort of 2-in, 2-out thing that would do a map2 for both keys and values, taking the input key and input value (also with some type of deduplication).

@Strato

+1 for Overscore.js

I just needed this functionnality. Sad to find out it's not included. It's super useful!

@mindrones

You might use:

_.chain({abc: 123, xyz: '6', foo: '9'})
 .map(function(val, key, obj) { return [key, parseInt(val,10)] })
 .object()
 .value();
@jbenet

+1 -- i also have to add it everywhere. It would be very nice to have it standard. After all, Underscore "provides a whole mess of useful functional programming helpers".

@notcome notcome referenced this issue from a commit
Commit has since been removed from the repository and is no longer available.
@heneryville

+1 This would be a great addition to Underscore.

Would you accept a pull request?

@greenify

There are some many issues of people running into this problem :(

+1 for getting mapValues into underscore!

@megawac
Collaborator

Feel free to open a pr @greenify as #1869 hasn't been closed yet

@greenify

@megawac I opened a PR (#1953) for this :)
Hopefully we finally get this feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.