Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object-preserving map() function #220

Closed
ThiefMaster opened this issue May 31, 2011 · 28 comments
Closed

Object-preserving map() function #220

ThiefMaster opened this issue May 31, 2011 · 28 comments

Comments

@ThiefMaster
Copy link

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
Copy link

amasad commented Jun 4, 2011

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

@greylurk
Copy link

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
Copy link

amasad commented Jun 28, 2011

@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
Copy link
Author

I think he meant map to return a new object.

@jashkenas
Copy link
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.

@mrkurt
Copy link

mrkurt commented Jul 21, 2011

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

@adrianheine
Copy link

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
Copy link
Contributor

yuchi commented Sep 30, 2011

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
Copy link

mahemoff commented Oct 1, 2011

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

@yuchi
Copy link
Contributor

yuchi commented Oct 1, 2011

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

@jashkenas
Copy link
Owner

+1 for Overscore.js

@eeroan
Copy link

eeroan commented Apr 11, 2012

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
Copy link

eethann commented Aug 23, 2012

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
Copy link

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

@JensRantil
Copy link
Contributor

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
Copy link

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
Copy link

nh2 commented Jun 24, 2013

This would be super useful.

@jonahkagan
Copy link

@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
Copy link
Owner

That is an extremely cool implementation.

@marekventur
Copy link
Contributor

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

@acjay
Copy link

acjay commented Oct 18, 2013

@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
Copy link

Strato commented Nov 7, 2013

+1 for Overscore.js

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

@mindrones
Copy link

You might use:

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

@jbenet
Copy link

jbenet commented May 26, 2014

+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".

@heneryville
Copy link

+1 This would be a great addition to Underscore.

Would you accept a pull request?

@wilzbach
Copy link
Contributor

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

+1 for getting mapValues into underscore!

@megawac
Copy link
Collaborator

megawac commented Nov 30, 2014

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

@wilzbach
Copy link
Contributor

@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
Projects
None yet
Development

No branches or pull requests