How to specify custom call behaviour in classes? - javascript

Given an instance v of a class Vector, say v = new Vector(3, 5, 7), is it somehow possible to use the syntax v(k) to call a specific method of the class Vector on v with argument(s) k?
To provide some context, I'd like to be able to use v(k) to call the method getElem(k) on v, which retrieves the k-th vector element of v. For example, v(2) would return 7. In other words, v(k) would act as an alias or shorthand for v.getElem(k).
Of course it would be possible to write a (custom) pre-processor to achieve the above, I just wondered whether there is a built-in way to realise it.
This question was inspired by the syntax of the C++ library Eigen, which allows one to get/set matrix elements in a similar way. It would be lovely to have something like this in JavaScript.
A bit of code to accompany the class mentioned above —
class Vector {
constructor(...vecElems) {
this.vecElems = vecElems;
}
getElem(k) {
return this.vecElems[k];
}
dot(v) {
return this.vecElems.reduce((aV, cV, cI) => aV + cV * v.vecElems[cI], 0);
}
}
const v = new Vector(3, 5, 7);
const w = new Vector(4, 6, 8);
console.log(v.getElem(2), v.dot(w));

Finally was pointed out a fairly elegant injection-safe way to do it in ES6+ class syntax with a Proxy:
class Vector extends Function {
constructor(...vecElems) {
super();
this.vecElems = vecElems;
return new Proxy(this, {
apply(target, _, args) {
return target.getElem(...args);
}
});
}
getElem(k) {
return this.vecElems[k];
}
dot(v) {
return this.vecElems.reduce((aV, cV, cI) => aV + cV * v.vecElems[cI], 0);
}
}
const v = new Vector(3, 5, 7);
const w = new Vector(4, 6, 8);
console.log(v.getElem(2), v.dot(w));
console.log(v(2), v.dot(w));
This uses Proxy's handler.apply to wrap a callable object, which is in this case the class instance itself because it extends Function. It has to extend Function for the x(...) call syntax to be valid.

You can make the syntax v[i] return v.getElem(i) too, by subclassing Array itself:
class Vector extends Array {
constructor(...vecElems) {
super(...vecElems);
}
getElem(k) {
return this[k];
}
dot(v) {
return this.reduce((aV, cV, cI) => aV + cV * v[cI], 0);
}
}
const v = new Vector(3, 5, 7);
const w = new Vector(4, 6, 8);
console.log(v.getElem(0), v.getElem(1));
console.log(v[0], v[1]);
console.log(v.dot(w));

Seems what you want is an object-generator (i.e. class) that returns functions that also have you custom properties.
Here's a way you can create such a "class":
function Vector(...vecElems) {
let members = {
vecElems,
getElem: function (k) {
return this.vecElems[k];
},
dot: function (v) {
return this.vecElems.reduce((aV, cV, cI) => aV + cV * v.vecElems[cI], 0);
}
};
return Object.assign(members.getElem.bind(members), members);
}
const v = new Vector(3, 5, 7);
const w = new Vector(4, 6, 8);
console.log(v.getElem(0), v.getElem(1));
console.log(v.dot(w));
console.log(v(0), v(1));
This will be more performant than the ES6-class-syntax with Proxy approach, because member access via Proxy suffers its indirection.

Related

How can I access the return value of the following STATIC method in the GET methods below?

class Palindromes {
static generate(obj) {
const arr = [];
const arrPalindrome = [];
let j = obj.minFactor;
while (j <= obj.maxFactor) {
for (let i = obj.minFactor; i <= obj.maxFactor; i++) {
if (!arr.includes(j * i)) {
arr.push(j * i);
}
}
j++;
}
arr.forEach((el) => {
if (el === +String(el).split("").reverse().join()) {
arrPalindrome.push(el);
}
});
return arrPalindrome;
}
get smallest() {
return Palindromes.generate(obj);
//I am using the return value of the STATIC method above to do some calc here.
}
get largest() {}
}
const result = Palindromes.generate({ minFactor: 1, maxFactor: 9 });
console.log(result.smallest);
Here is the problem:
Detect palindrome products in a given range.
A palindromic number is a number that remains the same when its digits are reversed. For example, 121 is a palindromic number but 112 is not.
Given a range of numbers, find the largest and smallest palindromes which are products of two numbers within that range.
Your solution should return the largest and smallest palindromes, along with the factors of each within the range. If the largest or smallest palindrome has more than one pair of factors within the range, then return all the pairs.
Given the range [1, 9] (both inclusive)...
And given the list of all possible products within this range: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 15, 21, 24, 27, 20, 28, 32, 36, 25, 30, 35, 40, 45, 42, 48, 54, 49, 56, 63, 64, 72, 81]
The palindrome products are all single digit numbers (in this case): [1, 2, 3, 4, 5, 6, 7, 8, 9]
The smallest palindrome product is 1. Its factors are (1, 1). The largest palindrome product is 9. Its factors are (1, 9) and (3, 3).
The result of ...
Palindromes.generate({ minFactor: 1, maxFactor: 9 });
... as of the OP's current implementation is the following array ...
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Thus, there is no smallest property on this array which is the reason of logging the undefined value when doing ...
console.log(result.smallest);
The OP needs to think ...
either about the provided code
e.g. a refactoring of the smallest/largest getter implementations where at least the former features a forwarding call to the static (class) method while providing an unknown / undefined obj.
and/or how to correctly use it
e.g. by invoking a correctly implemented smallest method at an instance of Palindromes.
class Palindromes {
static generate(obj) {
const arr = [];
const arrPalindrome = [];
let j = obj.minFactor;
while (j <= obj.maxFactor) {
for (let i = obj.minFactor; i <= obj.maxFactor; i++) {
if (!arr.includes(j * i)) {
arr.push(j * i);
}
}
j++;
}
arr.forEach((el) => {
if (el === +String(el).split("").reverse().join()) {
arrPalindrome.push(el);
}
});
return arrPalindrome;
}
smallest(config) {
const arrPalindrome = Palindromes.generate(config);
return arrPalindrome[0];
}
largest(config) {
const arrPalindrome = Palindromes.generate(config);
return arrPalindrome[arrPalindrome.length - 1];
}
}
const instance = new Palindromes;
const generated = Palindromes.generate({ minFactor: 1, maxFactor: 9 });
const smallest = instance.smallest({ minFactor: 1, maxFactor: 9 });
const largest = instance.largest({ minFactor: 1, maxFactor: 9 });
console.log({ generated, smallest, largest });
.as-console-wrapper { min-height: 100%!important; top: 0; }
It seems that there are multiple things that could be improved here, as many different things are combined incorrectly (e.g.: getting .smallest on an instance of Palindromes currently throws a ReferenceError: obj is not defined).
I'm not sure how this is intended to be used syntactically, whether this is meant to be a property accessor or if it's meant to be a method.
Property accessor:
Used on instances of Palindromes:
const p = new Palindromes({ minFactor: 1, maxFactor: 9 })
const result = p.smallest // triggers getter
Used on the Palindromes class:
const result = Palindromes.smallest // trigger getter
Method:
Used on instances of Palindromes:
const p = new Palindromes()
const result = p.smallest({ minFactor: 1, maxFactor: 9 })
Used on the Palindromes class:
const result = Palindromes.smallest({ minFactor: 1, maxFactor: 9 })
So I'll just give the fixes for all.
Property Accessor:
Class usage: WORST SOLUTION
There isn't a way to give arguments to a getter is JS Getter MDN. So this is the worst option, as it requires a getter function that uses environment state (i.e.: environment variables or class static properties) for the arguments to Palindromes.generate():
let environmentVariableObject = {}
class Palindromes {
// use default parameters when obj can't be given to generate()
static generate(obj = environmentVariableObject /* or Palindromes */) {
const arr = []
const arrPalindrome = []
let j = obj.minFactor
while (j <= obj.maxFactor) {
for (let i = obj.minFactor; i <= obj.maxFactor; i++) {
if (!arr.includes(j * i)) {
arr.push(j * i)
}
}
j++
}
arr.forEach(el => {
if (el === +String(el).split("").reverse().join()) {
arrPalindrome.push(el)
}
})
return arrPalindrome
}
// static getter on class
static get smallest() {
return Palindromes.generate()
// or return Palindromes.generate(environmentVariableObject /* or Palindromes */)
// instead of using default parameter to generate()
}
}
environmentVariableObject.minFactor = 1 // or Palindromes.minFactor = 1
environmentVariableObject.maxFactor = 9 // or Palindromes.maxFactor = 9
const result = Palindromes.smallest // trigger getter
console.log(result)
This is BAD! Do not use this solution, it's horrible for readability and will be prone to all kinds of bugs! (e.g.: the properties obj.minFactor and obj.maxFactor can be changed or deleted from obj between different calls to Palindromes.generate() and triggers of Palindromes.prototype.smallest, or even while the function is running.)
Instance usage: SOLUTION (but not improvement)
This is closest to what I think the question's code is trying to do.
class Palindromes {
// question's code was missing a constructor for instance of the class
constructor(obj){
this.minFactor = obj.minFactor
this.maxFactor = obj.maxFactor
}
static generate(obj) {
const arr = []
const arrPalindrome = []
let j = obj.minFactor
while (j <= obj.maxFactor) {
for (let i = obj.minFactor; i <= obj.maxFactor; i++) {
if (!arr.includes(j * i)) {
arr.push(j * i)
}
}
j++
}
arr.forEach(el => {
if (el === +String(el).split("").reverse().join()) {
arrPalindrome.push(el)
}
})
return arrPalindrome
}
// instance getter on prototype of class instead of static getter on class
get smallest() {
return Palindromes.generate(this)
}
}
// create new instance of class
const p = new Palindromes({ minFactor: 1, maxFactor: 9 })
const result = p.smallest // trigger getter
console.log(result)
The problems with the question's code:
Used argument obj which was not defined in getter or the surrounding variable scope.
Didn't create an instance of the Palindromes class.
The getter should use return Palindromes.generate(this) instead of return Palindromes.generate(obj).
While this fixes those problems, it may not be ideal depending on the use case, (I'm still not sure how this is intended to be used). Given the logic the function needs to accomplish in the question, it could be a pure function.
Method:
Using a method makes much more sense given the intended usage in the question allows it to be a method instead of a getter (property accessor). And it should be much better for readability.
Class usage: Good Solution
class Palindromes {
// no constructor needed to create an instance of the class
// because no instances of the class are needed
static generate(obj) {
const arr = []
const arrPalindrome = []
let j = obj.minFactor
while (j <= obj.maxFactor) {
for (let i = obj.minFactor; i <= obj.maxFactor; i++) {
if (!arr.includes(j * i)) {
arr.push(j * i)
}
}
j++
}
arr.forEach(el => {
if (el === +String(el).split("").reverse().join()) {
arrPalindrome.push(el)
}
})
return arrPalindrome
}
// static method of class
static smallest(obj) {
return Palindromes.generate(obj)
}
}
const result = Palindromes.smallest({ minFactor: 1, maxFactor: 9 }) // call method
console.log(result)
This is a very clean solution, as it doesn't need any new instances of the class to be created and its usage is very short and clear.
Instance usage: Best Solution
class Palindromes {
// no constructor needed to create an instance of the class
static generate(obj) {
const arr = []
const arrPalindrome = []
let j = obj.minFactor
while (j <= obj.maxFactor) {
for (let i = obj.minFactor; i <= obj.maxFactor; i++) {
if (!arr.includes(j * i)) {
arr.push(j * i)
}
}
j++
}
arr.forEach(el => {
if (el === +String(el).split("").reverse().join()) {
arrPalindrome.push(el)
}
})
return arrPalindrome
}
// instance method on prototype of class
smallest(obj) {
return Palindromes.generate(obj)
}
}
// create a new instance of class just by using new
const p = new Palindromes()
const result = p.smallest({ minFactor: 1, maxFactor: 9 }) // call method
console.log(result)
This is a lot better. It doesn't need any properties or environment variables, and it can set the needed results of the Palindromes.generate() function (smallest multiple, greatest multiple, set of multiples for each product, etc.) as properties of the class instance: this.
Revision
After reviewing and rewriting your code, it seems the Palindromes class is supposed to have the following usage.
Create palindromes within a given range or from a set of numbers. (NOTE: in it's current implementation it's extremely inefficient!)
Find products in range (or from set) that are palindromes.
Find set of multiples whose product are the smallest. (seems to be unimplemented...)
Find set of multiples whose product are the largest. (also unimplemented...)
Find the largest palindrome. (also unimplemented...)
Find the smallest palindrome. (also unimplemented...)
This sounds a lot like an exam question (which is not the type of questions Stack Overflow can or should be used for), so I won't write the logic (just as a precaution, sorry if I misread things...).
But I am able to point in the general right direction!
It think it would be helpful to reference the Mozilla Developer Network Web Docs and MDN JavaScript Guide
Also, here's why I didn't use semicolons MDN Automatic Semicolon Insertion
Have fun on your JavaSrcipt journey Afzal18!

Use Arrow Function in Class Method to access Class Property

I've put together a simplified example of what I'm trying to do, obviously a bit contrived... I have this class:
export class myClass {
a = 'bar';
b = 0;
save(x: any = null): void {
//save all properties
//...
}
}
In other classes that need to use it, I will define foo = new myClass();
Then it can be used either as:
this.foo.b = 3
this.foo.save();
or, because sometimes I just want it on one line (hence the x: any = null:
this.foo.save(this.foo.b = 3);
I would like to write the single line version more elegantly, and feel something like this should be possible... is it?
//How can I make this possible?
this.foo.save(c => c.b = 3)
if it is possible, what would the add method look like?
Many thanks!
Answer for the original question.
If you want this.calc.add(c => c.b = 3), then you need to handle invoking the function c => c.b = 3 once passed to the add method.
So just check the value is a function, if it is then pass this to the function, which would be c in your function, then the return value you add with this.b
Plain old js.
class Calculator {
constructor() {
this.a = 10
this.b = 0
this.sum = 0
}
add(x) {
this.sum = this.a + (typeof x === 'function' ? x(this) : x)
}
}
const calc = new Calculator()
calc.add(c => c.b = 3)
console.log(calc.sum)
calc.add(1)
console.log(calc.sum)
Implicitly assigning is anti pattern
// Something that you should avoid
this.calc.b = 3
class Calc {
constructor(private a: number = 0, private b: number = 0) {}
setA(a: number) {
this.a = a;
return this;
}
setB(b: number) {
this.b = b;
return this;
}
sum() {
return this.a + this.b;
}
}
const calc = new Calc();
// will return 0
console.log(calc.sum());
// will return 6
console.log(calc.setA(1).setB(5).sum());
const calc1 = new Calc(1,2);
// will return 3
console.log(calc1.sum());

Recursively concatenate passed string

I'm writing a BST in JS and trying to concatenate a string passed into a member function without success. console.log() works but it automatically starts a new line for each entry.
"use strict";
class Node {
constructor(dt) {
this.data = dt;
this.left = null;
this.right = null;
}
}
class BST {
constructor() {
this.root = null;
}
insert(parent, key) {
if (!this.root) return this.root = new Node(key);
else {
if (key < parent.data) {
if (!parent.left) {
parent.left = new Node(key);
} else {
this.insert(parent.left, key);
}
} else if (key > parent.data) {
if (!parent.right) {
parent.right = new Node(key);
} else {
this.insert(parent.right, key);
}
}
}
}
// Doesn't work
printIN(parent, string) {
if (parent) {
this.printIN(parent.left);
console.log(parent.data);
string += " " + parent.data;
// return string += " " + parent.data;
this.printIN(parent.right);
}
return string;
}
// This works.
// printIN(parent) {
// if (parent) {
// this.printIN(parent.left);
// console.log(parent.data);
// this.printIN(parent.right);
// }
// }
}
let treeA = new BST();
let tree = null;
tree = treeA.insert(treeA.root, 5);
tree = treeA.insert(treeA.root, 7);
tree = treeA.insert(treeA.root, 3);
tree = treeA.insert(treeA.root, 14);
let string = [""];
string = treeA.printIN(treeA.root, string);
console.log();
console.log(string);
// treeA.printIN(treeA.root);
I want to print out the numbers on one single line, instead of them starting on a new line each time. I thought using string concatenation is the logical solution, but I can't seem to make it work.
// Doesn't work
printIN(parent, string) {
if (parent) {
this.printIN(parent.left);
console.log(parent.data);
string += " " + parent.data;
// return string += " " + parent.data;
this.printIN(parent.right);
}
return string;
Another answer gave you a solution to your string problem. The snippet below has an alternative. But more importantly, it offers some significant clean-up to your OOP approach.
There are two main reasons this is necessary.
First, you have member functions of your object, which still require the object to be passed to them. That's redundant and confusing. Any OOP function which doesn't make reference to this should be a static function. But if you're going to do OOP (I might argue against it in fact), then you shouldn't use static functions as though they were methods. Thus insert and printIN should not have the parent parameter.
Second, you are dealing with a recursive structure. Nodes of a tree should have the same design as the tree itself. This makes all sorts of things easier. But you have a class Node and one called BST. And BST then has a root node, which should then be recursive. There is simply no reason for this that I can see. A BST by nature is a root node, with a data value (usually, although we can make that optional as you do) and potentially left and right nodes that are themselves BSTs.
Fixing the code to work like this simplifies it substantially. Here is one implementation:
class BST {
constructor(data) {
this.data = data;
}
insert(key) {
if (!this.data) {
this.data = key;
} else {
if (key < this.data) {
if (!this.left) {
this.left = new BST(key);
} else {
this.left.insert(key);
}
} else if (key > this.data) {
if (!this.right) {
this.right = new BST(key);
} else {
this.right.insert(key);
}
}
}
}
printIN() {
return (this.left ? this.left.printIN() + ' ' : '') +
this.data +
(this.right ? ' ' + this.right.printIN() : '');
}
}
let treeA = new BST();
treeA.insert(5);
treeA.insert(7);
treeA.insert(3);
treeA.insert(14);
console.log(treeA.printIN());
There is no Node class here. Everything is done in BST. Moreover, BST objects don't need a root node and the methods are simpler.
I would note, though that printIN is less useful than another function which we could use the same way:
toArray() {
return [
...(this.left ? this.left.toArray() : []),
this.data,
...(this.right ? this.right.toArray() : [])
];
}
which would allow
console.log(treeA.toArray()); //=> [3, 5, 7, 14]
and you could get your string just by joining that array with a space. This function strikes me as much more useful than the printIN one you have.
We could also add a static function:
static fromArray(xs) {
return xs .reduce (function (tree, x) {
tree.insert(x)
return tree
}, new BST())
}
that would allow us to write
tree = BST .fromArray ([8, 6, 7, 5, 3, 0, 9]);
tree .printIN() //=> [0, 3, 5, 6, 7, 8, 9]
I mentioned that I wouldn't even do this with OOP. But I don't do much that way these days, preferring functional approaches. So, if you're curious, here's another take on the problem altogether, using just functions, and trees that aren't modified when you do an insert, but instead return new trees:
const bst = (xs) =>
xs .reduce ((tree, x) => bstInsert (x, tree), {})
const bstInsert = (x, {left, data, right} = {}) =>
data == undefined
? {data: x}
: x < data
? {data, left: bstInsert (x, left), ...(right ? {right} : {})}
: {data, ...(left ? {left} : {}), right: bstInsert (x, right)}
const inorder = ({left, data, right} = {}) => [
...(left ? inorder(left) : []),
data,
...(right ? inorder(right) : [])
]
inorder (bst ([8, 6, 7, 5, 3, 0, 9])) //=> [0, 3, 5, 6, 7, 8, 9]
I'm not recommending that technique, specifically, but FP can be a powerful alternative to OOP.
Try this technique.
printIN(parent, result = []) {
if (parent) {
this.printIN(parent.left, result);
result.push(parent.data)
this.printIN(parent.right, result);
}
return result;
}
Pass, as a second argument, the array of strings that will serve as the resulting list of node values as per the in-order bst traversal.
Use a default value for this argument of [] then, if omitted, one will be created, but when recursing, you can pass the existing array in. When you visit a node, push the value into the array. Call printIN on the root node and do not provide the result argument (a new array will be created and returned).
If you want to log all on one line, use Array.prototype.join to transform the array of strings into a single comma-separated string, and log that.
Working example:
class Node {
constructor(dt) {
this.data = dt;
this.left = null;
this.right = null;
}
}
class BST {
constructor() {
this.root = null;
}
insert(parent, key) {
if (!this.root) return this.root = new Node(key);
else {
if (key < parent.data) {
if (!parent.left) {
parent.left = new Node(key);
} else {
this.insert(parent.left, key);
}
} else if (key > parent.data) {
if (!parent.right) {
parent.right = new Node(key);
} else {
this.insert(parent.right, key);
}
}
}
}
printIN(parent, result = []) {
if (parent) {
this.printIN(parent.left, result);
result.push(parent.data)
this.printIN(parent.right, result);
}
return result;
}
}
let treeA = new BST();
let tree = null;
tree = treeA.insert(treeA.root, 5);
tree = treeA.insert(treeA.root, 7);
tree = treeA.insert(treeA.root, 3);
tree = treeA.insert(treeA.root, 14);
console.log(treeA.printIN(treeA.root).join());

Update array inside another array

I have a JSON array that represents a list of objects (people).
every object (every person) has name attribute, image, and an array of numbers.
Example:
"people":[
{
"name":"nunu",
"image":"image1",
"numbers":{
"total":50,
"vector":[
10,
20,
5,
10,
5
]
}
}
];
My goal is to update all vectors and append some calculation to each vector.
This is what I tried:
this.people = this.people.map((person) => {
return person.numbers.vector.map((num) => {
return Math.round(((num / divider) * 100) / 100);
});
});
The problem is that people is being replaced by the numbers in my vector and I lose the people data.
How I can update the vectors without making nay change to any other data?
Due to .map() specification it creates a new array, to process top-level list use .forEach() instead:
this.people.forEach(person =>
person.numbers.vector = person.numbers.vector.map(num =>
Math.round(((num / divider) * 100) / 100)
);
);
people = people.map((person) => {
person.numbers.vector = person.numbers.vector.map((num) => {
return Math.round(((num / divider) * 100) / 100);
});
return person;
});
You were returning the vector as the value for person, you need to change the value of vector then return person instead.
Try using spread operator to update person object and save all data. For example, calculate total value as sum of nums:
this.people = this.people.map((person) => {
let total = person.numbers.vector.reduce((prevNum, nextNum) => {
return prevNum + nextNum;
});
return {
...person,
numbers: {
...person.numbers,
total
}
}
});
At the same way you can change vector values, for example
If you use the new spread operator and something like Babel this becomes trivial:
const source = {
"people": [{
"name": "nunu",
"image": "image1",
"numbers": {
"total": 50,
"vector": [
10,
20,
5,
10,
5
]
}
}]
};
const newSource = {
...source,
people: source.people.map(person => {
return {
...person,
numbers: {
...person.numbers,
vector: person.numbers.vector.map(n => Math.round(((n / 2) * 100) / 100))
}
}
})
};
Here is more on the spread operator.
As a side note, using the spread operator creates a new object and using map creates a new array. This way you will always have a new object and can't change the old one. Using const with this type of code is also a good practice.

How to map/reduce/filter a Set in JavaScript?

Is there any way to map/reduce/filter/etc a Set in JavaScript or will I have to write my own?
Here's some sensible Set.prototype extensions
Set.prototype.map = function map(f) {
var newSet = new Set();
for (var v of this.values()) newSet.add(f(v));
return newSet;
};
Set.prototype.reduce = function(f,initial) {
var result = initial;
for (var v of this) result = f(result, v);
return result;
};
Set.prototype.filter = function filter(f) {
var newSet = new Set();
for (var v of this) if(f(v)) newSet.add(v);
return newSet;
};
Set.prototype.every = function every(f) {
for (var v of this) if (!f(v)) return false;
return true;
};
Set.prototype.some = function some(f) {
for (var v of this) if (f(v)) return true;
return false;
};
Let's take a little set
let s = new Set([1,2,3,4]);
And some stupid little functions
const times10 = x => x * 10;
const add = (x,y) => x + y;
const even = x => x % 2 === 0;
And see how they work
s.map(times10); //=> Set {10,20,30,40}
s.reduce(add, 0); //=> 10
s.filter(even); //=> Set {2,4}
s.every(even); //=> false
s.some(even); //=> true
Isn't that nice ? Yeah, I think so too. Compare that to the ugly iterator usage
// puke
let newSet = new Set();
for (let v in s) {
newSet.add(times10(v));
}
And
// barf
let sum = 0;
for (let v in s) {
sum = sum + v;
}
Is there any better way to accomplish map and reduce using a Set in JavaScript?
A short-hand way to do it is to convert it to an array via the ES6 spread operator.
Then all the array functions are available to you.
const mySet = new Set([1,2,3,4]);
[...mySet].reduce(...);
To sum up the discussion from comments: while there are no technical reasons for set to not have reduce, it's not currently provided and we can only hope it changes in ES7.
As for map, calling it alone could violate the Set constraint, so its presence here might be debatable.
Consider mapping with a function (a) => 42 - it will change the set's size to 1, and this might or might not be what you wanted.
If you're ok with violating that because e.g. you're going to fold anyway, you can apply the map part on every element just before passing them to reduce, thus accepting that the intermediate collection (which isn't a Set at this point) that's going to be reduced might have duplicated elements. This is essentially equivalent to converting to Array to do processing.
The cause of the lack of map/reduce/filter on Map/Set collections seem to be mainly conceptual concerns. Should each collection type in Javascript actually specify its own iterative methods only to allow this
const mySet = new Set([1,2,3]);
const myMap = new Map([[1,1],[2,2],[3,3]]);
mySet.map(x => x + 1);
myMap.map(([k, x]) => [k, x + 1]);
instead of
new Set(Array.from(mySet.values(), x => x + 1));
new Map(Array.from(myMap.entries(), ([k, x]) => [k, x + 1]));
An alternative were to specify map/reduce/filter as part of the iterable/iterator protocol, since entries/values/keys return Iterators. It is conceivable though that not every iterable is also "mappable". Another alternative were to specify a separate "collection protocol" for this very purpose.
However, I do not know the current discussion on this topic at ES.
const set = new Set([1,2,3,4,5]);
function filterSet(index) {
set.delete([...set][index]);
}
filterSet(3); // Set { 1, 2, 3, 5, [size]: 4 }
thought this was a pretty decent solution for "filtering" the set.

Categories