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.)

O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114
  • 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` – mplungjan Dec 06 '17 at 21:51
  • @mplungjan: Because [`indexOf` has a time complexity of O(n)](https://stackoverflow.com/questions/19287033/what-is-the-time-complexity-of-javascripts-array-indexof). 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. – O. R. Mapper Dec 06 '17 at 21:54
  • Ok. My bad. No actual mentioning of thousand or million elements in your question – mplungjan Dec 06 '17 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? – James Dec 06 '17 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. – O. R. Mapper Dec 06 '17 at 22:21
  • https://stackoverflow.com/questions/4777968/i-need-a-client-side-browser-database-what-are-my-options – mplungjan Dec 07 '17 at 06:03
  • @mplungjan: Are you sure any of these can be more performant and space-efficient than a simple object when all I'm looking for is managing a set of integers or strings (without any information attached to these integers or strings)? – O. R. Mapper Dec 07 '17 at 11:21
  • Possibly. I commented for completeness’ sake – mplungjan Dec 07 '17 at 11:22

3 Answers3

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>
Koushik Chatterjee
  • 4,106
  • 3
  • 18
  • 32
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));
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • 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 Dec 06 '17 at 21:48
  • @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. – Ori Drori Dec 06 '17 at 21:58
  • @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. – Ori Drori Dec 06 '17 at 21:59
  • 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 ... – O. R. Mapper Dec 06 '17 at 22:06
  • ... 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set), the `new Set(iterable)` constructor is not supported by MSIE. – O. R. Mapper Dec 06 '17 at 22:06
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));
dashton
  • 2,684
  • 1
  • 18
  • 15
  • 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 ;) – O. R. Mapper Dec 06 '17 at 22:11
  • Fair enough, it wouldn't really work in that case. Good luck with your quest. – dashton Dec 06 '17 at 22:38