Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I invoke Array.prototype.map with Set object [duplicate]

I want to get get unique characters from some string using ES6's Set. So let's assume that we have string var str = 'abcdeaabc'; and create set of characters from this string:

var str = 'abcdeaadbc';
var chars = new Set(str);

Now we have the set of unique characters: abcd. I was surprised that char set has forEach method and has no map method.

Set is iterable object, so why can't we use map function to iterate over set?

I tried to pass chars set and chars.values() SetIterator to Array.prototype.map this way:

Array.prototype.map.call(chars, function(element) {...});
Array.prototype.map.call(chars.values(), function(element) {...});

But every try failed.

For example, let's assume that we want to get unique symbols from string and return an array of them with preceding underscore. eg.: ['_a', '_b', '_c', '_d']

Here are my two solutions:

// First:
var result = Array.from(chars).map(function(c) {
    return '_' + c;
});

// Second:
var result = [];
chars.forEach(function(c) {
    this.push('_' + c);
}, result);

But is there a way of invoking Array.prototype's map function with Set context? And if no - why?

like image 734
Yuriy Yakym Avatar asked May 11 '16 21:05

Yuriy Yakym


3 Answers

Array.prototype.map works on array-like types that have integer indexes and a length property. Set and Map are general iterable types, but they are not array-like, so Array.prototype methods will not work on them. e.g.

var s = new Set([1, 2, 3]);
s.length === undefined;
s[0] === undefined

The main approach is Array.from kind of like your solution, but one thing to note is that Array.from takes a mapFn as the second argument, so you can do

let results = Array.from(chars, c => `_${c}`);

for a nice and short map to convert an iterable to an array.

like image 170
loganfsmyth Avatar answered Nov 09 '22 23:11

loganfsmyth


If you are desperate you can do like this

var str = 'abcdeaadbc';
var chars = new Set(str);
document.write("<pre>" + JSON.stringify([...chars].map(c => c+"up")) + "</pre>")
like image 22
Redu Avatar answered Nov 10 '22 00:11

Redu


Because even if map is generic enough to work for arbitrary array-like objects, not all iterable objects are array-like.

map just gets the length property, and then iterates properties from 0 to length-1. That can't work with sets because they have a size instead of length, and they don't provide random access to its elements.

If you really want to use map, you will need to transform the set to an array, and then transform the result back to a set.

var newSet = new Set(Array.from(set).map(myFunc));

But there is no point in doing that, it would be better to use set operations only:

var newSet = new Set();
for(let val of set) newSet.add(myFunc(val));

Or maybe a self-invoked generator function:

var newSet = new Set(function*() {
  for(let val of set) yield myFunc(val);
}());
like image 31
Oriol Avatar answered Nov 09 '22 22:11

Oriol