4

I'm looking for a concise way of creating an object whose property names match values from an array, where all properties have the same value (e.g. true). As an example:

My ID array looks like these arbitrary (in the actual software, not necessarily unique and not necessarily numeric, but primitive) values:

var ids = [4, 15, 8, 16, 42, 23];

I am looking for some simple call to convert this into the following object:

{ 4: true, 15: true, 8: true, 16: true, 42: true, 23: true }

With JavaScript objects internally being hashmaps, such an object should be much more convenient for quickly checking whether a given ID is contained in the array.


Now, of course I know how to generate this object. It's a four-liner:

var idMap = {};
ids.forEach(function (v) {
    idMap[v] = true;
});

As this pattern occurs over and over in my code (lots of different item IDs being handled in this application), I feel the percentage of "boilerplate" in this is still too large. Furthermore, it appears to me that the four-liner occasionally distracts from the actually tricky algorithms for doing something with various of these ID lists. Therefore, I am looking for a shorter way to write this, hoping that there is something built-in either to standard JavaScript or to lodash, which is already in use in our application.

The most related question I could find appears to be lodash: mapping array to object. Yet, its proposed solutions are not really shorter and more legible than the above, basically replacing one sort of boilerplate code with another sort (as in my case, I'd usually throw away what the suggested solutions use as values for the object properties and always use true or some similarly small truthy value).

Is there something built-in that allows for a very simple command like

var idMap = arrayToMap(ids);

or

var idMap = arrayToMap(ids, true);

whose actual name I have not yet found?

(Obviously, the fallback solution is to simply write such a function in our application. I'd prefer to stick as closely as possible with the existing functions, though, hence this question.)

8
  • Why not just use indexOf: var ids = [4, 15, 8, 16, 42, 23]; console.log(ids.indexOf(16)!=-1); ? Then remove it to have false or change it to x16 Commented Dec 6, 2017 at 21:51
  • @mplungjan: Because indexOf has a time complexity of O(n). It would be rather unwise to use it when dealing with ID sets that can easily have thousands or even a million elements, with many such checks in quick succession. With objects internally being hash maps, accessing an object property should be at O(1), in contrast. Commented Dec 6, 2017 at 21:54
  • Ok. My bad. No actual mentioning of thousand or million elements in your question Commented Dec 6, 2017 at 22:11
  • Since you're worried about O(1) vs O(n) performance due to the size of your arrays, I wonder if you have considered using some technology other than javascript to do this? Like a database? Commented Dec 6, 2017 at 22:18
  • @James: Possibly, suggesting a solution using a different technology that can still be used offline from within the client part of a web application could be a valid answer, as well. Commented Dec 6, 2017 at 22:21

3 Answers 3

3

Well, I think you can easily map the source array to array of pairs, and then construct the object (or even to ES6 Map) easily.

Just do:

_.fromPairs(ids.map(a=>[a,true])) and that's it.

Here is an working example:

var ids = [4, 15, 8, 16, 42, 23];

var idsMap = _.fromPairs(ids.map(a=>[a,true])); // however true can be replaced with a variable

console.log(idsMap);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

Another way:

_(ids).keyBy(v=>v).mapValues(v=>true).value()

Here is the working example:

var ids = [4, 15, 8, 16, 42, 23];

var idsMap = _(ids).keyBy(v=>v).mapValues(x=>true).value();

console.log(idsMap)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

And, If you are really sure that (consideration) all of your id will be truthy value, then you can take that as an advantage, and just create a map with the same key/value pair, and check for truthiness with any id directly with O(1)

Just Do:

_.keyBy(ids) And you are done :)

And you don't need the additional .mapValues(v=>true) from the 2nd solution as you are aware about truthiness of values.

Here is the working snippet:

var ids = [4, 15, 8, 16, 42, 23];

var idsMap = _.keyBy(ids);

console.log(idsMap);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

Sign up to request clarification or add additional context in comments.

6 Comments

Well, that's basically what a now deleted answer suggested. I concede your version is a lot shorter, although it still seems like a wasteful detour to create possibly thousands of pair-arrays just to match the signature of the target function. The second suggestion looks interesting, though. I'll try and understand how it works.
ok, so question is, will your ids will contain 0, if none of your id is a falsy value then I have better approach towards making a map object of truthy values
I think I can rule out any falsy values from being IDs.
So, You are sure, that your values inside IDs will be truthy value right?
Yes, all IDs are truthy values.
|
1

You can create a simple function that generates maps for you:

var ids = [4, 15, 8, 16, 42, 23];

function hash(arr, prop) {
  return arr.reduce(function(r, n) {
    r[prop ? n[prop] : n] = true;
    
    return r;
  }, {});
}

console.log(hash(ids));

var objs = [{ a: 1 }, { a: 2 }, { a: 3}];

console.log(hash(objs, 'a'));

You can use an ES6 Set:

const ids = [4, 15, 8, 16, 42, 23];

const set = new Set(ids);

console.log(set.has(16));

5 Comments

Promising. The only drawback I see in this is that internally, the values appear to be saved as objects (with one property value, that contains the value again). That seems slightly wasteful to me, as my code can easily be dealing with ID sets with a million entries that get subjected to a _.cloneDeep.
@O.R.Mapper - Set just saves the values, instead of saving a pair of prop: true. The value: number that you see is the values that the iterator will produce (lazily), if you'll use the Set's iterator.
@O.R.Mapper - in addition, if you've got hundred thousand entries or more, it's better to handle them on the server side, or if you can't help something like indexeddb on the client side.
Aah, good to know about the value objects that I'm seeing in the debugger. As for handling those entries on the server side, we're a bit restricted there, given that the IDs are closely connected (and sometimes obtained from/forwarded to) complex visual stuff that is entirely on the client side (displayed by a 3rd party component). As for using indexeddb, my understanding was that that's meant for semi-permanently (across sessions/page visits) storing (depending on the environment, slightly limited) amounts of data. I'm not sure that is the appropriate solution for highly temporary and ...
... quickly changing objects. So far, using Set seems like the best bet, assuming that retrieval runs at something like O(1) runtime complexity. The only possible problem with that is that according to the docs, the new Set(iterable) constructor is not supported by MSIE.
1

You could add it to the Array.prototype in your application:

var ids = [4, 15, 8, 16, 42, 23];

Array.prototype.toThing = function(propVal) {
    return this.reduce((res, val) => {
        res[val] = propVal;
        return res;
    }, {})
}
console.log(ids.toThing(true));

2 Comments

I'll upvote this because it is a viable solution for some projects. I would, if this were my project, but in a large development project with over 50 devs, adding anything global in "random" places can be a good way to get your commit priviledges revoked ;)
Fair enough, it wouldn't really work in that case. Good luck with your quest.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.