I'm translating some code from Python to JavaScript and I need to rewrite the following nested for-loop list comprehension (where solution is a dictionary of string keys and list values).
events = [e for key in solution.keys() for c in solution[key] for e in c.events]
Without giving it much thought, I'd translate it to something like this unwieldy, ugly nested for-loop.
const events = []
for (const key of solution.keys()) {
for (const c of solution[key]) {
for (const e of c.events) {
events.push(e)
}
}
}
But maybe there's a nicer way. How can I rewrite the above nested for-loop in short, idiomatic, modern (ES2015+) JavaScript?
There’s not a much nicer way. The original Python should be using values:
events = [e for foo in solution.values() for c in foo for e in c.events]
which you can reflect in JavaScript:
const events = [];
for (const foo of solution.values()) {
for (const c of foo) {
for (const e of c.events) {
events.push(e);
}
}
}
This is pretty short and easy to read (or it will be when foo is replaced with an appropriate name). You can use concat if you like creating lots of intermediate lists:
const events = [];
for (const foo of solution.values()) {
for (const c of foo) {
events = events.concat(c.events);
}
}
and reduce if you like function calls that don’t really save space or readability, assuming the values of solution are arrays:
const events = [];
for (const foo of solution.values()) {
events = foo.reduce(
(m, n) => m.concat(n.events),
events
);
}
and Array.from and reduce if you really like intermediate lists and don’t really like readability:
const events =
Array.from(solution.values()).reduce(
(events, foo) => events.concat(
foo.reduce(
(m, n) => m.concat(n.events),
events
)
),
[]
);
Defining more functions dulls the pain but doesn’t change the fact that ES6 is not so great
const concat = arrays =>
arrays.reduce((m, n) => m.concat(n), []);
const concatMap = (fn, arrays) =>
concat(arrays.map(fn));
const events = concatMap(
foo => concatMap(foo, c => c.events),
Array.from(solution.values())
);
Maybe the standard library is missing some iterator functions
function* map(fn, iterable) {
for (const x of iterable) {
yield fn(x);
}
}
function* concat(iterables) {
for (const iterable of iterables) {
yield* iterable;
}
}
const concatMap = (fn, iterables) =>
concat(map(fn, iterables));
const events = Array.from(
concatMap(
([key, foo]) => concatMap(c => c.events, foo),
solution
)
);
Stick with the for loops, honestly.
Related
I'm doing array manipulation in Javascript, and I want to be able to chain operations with multiple calls to map, concat, etc.
const someAmazingArrayOperation = (list) =>
list
.map(transformStuff)
.sort(myAwesomeSortAlgorithm)
.concat([someSuffixElement])
.precat([newFirstElement])
.filter(unique)
But the problem I've run into is that Array.precat doesn't exist. (Think of Array.concat, but the reverse.)
I don't want to modify Array.prototype in my own code, for reasons. (https://flaviocopes.com/javascript-why-not-modify-object-prototype/)
I could totally use Array.concat and concatenate my array to the end of the prefix array and carry on. But that doesn't chain with the other stuff, and it makes my code look clunky.
It's kind of a minor issue because I can easily write code to get the output I want. But it's kind of a big deal because I want my code to look clean and this seems like a missing piece of the Array prototype.
Is there a way to get what I want without modifying the prototype of a built-in type?
For more about the hypothetical Array.precat, see also:
concat, but prepend instead of append
You could use Array#reduce with a function which takes the initialValue as array for prepending data.
const
precat = (a, b) => [...a, b],
result = [1, 2, 3]
.reduce(precat, [9, 8, 7]);
console.log(result)
If you don't want to modify Array.prototype, you can consider extends:
class AmazingArray extends Array {
precat(...args) {
return new AmazingArray().concat(...args, this);
}
}
const transformStuff = x => 2*x;
const myAwesomeSortAlgorithm = (a, b) => a - b;
const someSuffixElement = 19;
const newFirstElement = -1;
const unique = (x, i, arr) => arr.indexOf(x) === i;
const someAmazingArrayOperation = (list) =>
new AmazingArray()
.concat(list)
.map(transformStuff)
.sort(myAwesomeSortAlgorithm)
.concat([someSuffixElement])
.precat([newFirstElement])
.filter(unique);
console.log(someAmazingArrayOperation([9, 2, 2, 3]));
I don't want to modify Array.prototype in my own code, for reasons.
These reasons are good, but you can sidestep them by using a collision-safe property - key it with a symbol, not a name:
const precat = Symbol('precatenate')
Array.prototype[precat] = function(...args) {
return [].concat(...args, this);
};
const someAmazingArrayOperation = (list) =>
list
.map(transformStuff)
.sort(myAwesomeCompareFunction)
.concat([someSuffixElement])
[precat]([newFirstElement])
.filter(unique);
So I have been working on some extra credit for my classes. I am very new to programming and have already sought help for this same assignment, I started rolling through and now am absolutely lost.
I need to define the two functions groupBy() and arrayToObect() as asked in the below test.
I'm not necessarily looking for the answer but if someone could help point me in the right direction that would be awesome.
What I have deduced is as follows:
I need to be using the spread operator ...
I need to create a newObj = {}
a. and somehow push the element derived from the array into the obj
I need to take the individual values of the array and assign them as keys, with the variances as the properties of the key.
Bracket notation
I have been racking my brain for hours on this now and could really use some guidance!
describe('groupBy', function () {
const input = [4.2, 6.1, 6.3]
const result = groupBy(input, (el) => Math.floor(el))
it('returns an object', function () {
expect(result).to.be.an('object')
})
it('group array items together based on the callback return value', function () {
expect(result).to.be.eql({
4: [4.2],
6: [6.1, 6.3],
})
})
})
describe('arrayToObject', function () {
const input = ['cat', 'dog', 'bird']
const result = arrayToObject(input, (word) => word + 's')
it('returns an object', function () {
expect(result).to.be.an('object')
})
it('object has original array elements as keys and the result of the callback as values', function () {
expect(result).to.be.eql({
cat: 'cats',
dog: 'dogs',
bird: 'birds',
})
})
})
})
groupBy
Write a function called groupBy which takes an array and a callback. The function should return an object. Each return value of the callback should be a key of the object and the values should be the input element with which the callback was called.
arrayToObject
Write a function called arrayToObject which takes an array and a callback. The function should return an object. Each element of the input array should be a key of the returned object and the output from the callback with an element passed in as the corresponding value.
These questions have been answered a million times on stackoverflow. Essentially what you want to be doing here is using the common js array functions map, filter, reduce, flatten, ..., and think about how your problem can be expressed in terms of those.
A lot of real world code is transforming data like this, so it's good to be comfortable doing it.
Also realize that spread syntax copies the entire object which can be pretty inefficient. JavaScript doesn't have persistent data structures! It's usually better to just mutate — as long as your code is what "owns" the object.
const groupBy = (elts, keyfn) =>
elts.reduce((m, elt) => {
const key = keyfn(elt);
m[key] = m[key] || [];
m[key].push(elt);
return m;
}, {});
const arrayToObject = (elts, fn) =>
elts.reduce(
(obj, elt) => Object.assign(obj, { [elt]: fn(elt) }),
{},
);
I figured it out using a for loop!!
function groupBy(arr, callback) {
const newObj = {}
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
const key = callback(arr[i])
newObj[key] = newObj[key] || []
newObj[key].push(arr[i])
}
}
return newObj
}
function arrayToObject(arr, callback) {
const obj = {}
for (let i = 0; i < arr.length; i++) {
if (callback(arr[i])) {
const key = callback(arr[i])
obj[arr[i]] = obj[key] || callback(arr[i])
}
}
return obj
}
I would like to perform some updates to an array in an object, and then calculate another parameter based on this update. This is what I tried:
import * as R from 'ramda'
const obj = {
arr: [
2,
3
],
result: {
sumOfDoubled: 0
}
};
const double = a => {
return a*2;
}
const arrLens = R.lensProp('arr');
const res0sumOfDblLens = R.lensPath(['result','sumOfDoubled']);
const calc = R.pipe(
R.over(arrLens,R.map(double)),
R.view(arrLens),
R.sum,
R.set(res0sumOfDblLens)
);
const updatedObjA = calc(obj);
const updatedObjB = R.set(res0sumOfDblLens,R.sum(R.view(arrLens,R.over(arrLens,R.map(double),obj))),obj);
// what I want: {"arr":[4,6],"result":{"sumOfDoubled":10}}
console.log(JSON.stringify(obj)); //{"arr":[2,3],"result":{"sumOfDoubled":0}}, as expected
console.log(JSON.stringify(updatedObjA)); //undefined
console.log(JSON.stringify(updatedObjB)); //{"arr":[2,3],"result":{"sumOfDoubled":10}}, correct result but the array did not update
I realise that neither approaches will work; approach A boils down to R.set(res0sumOfDblLens,10), which makes no sense as it doesn't have a target object for the operation. Approach B, on the other hand, manipulates the base object twice rather than passing the result of the first manipulation as an input for the second.
How can I achieve this using only one function composition; i.e. apply the double() function to one part of the object, and then passing that updated object as input for calculating sumOfDoubled?
As well as OriDrori's converge solution, you could also use either of two other Ramda functions. I always prefer lift to converge when it works; it feels more like standard FP, where converge is very much a Ramda artifact. It doesn't always do the job because of some of the variadic features of converge. But it does here, and you could write:
const calc = pipe (
over (arrLens, map (multiply (2))),
lift (set (res0sumOfDblLens) ) (
pipe (view (arrLens), sum),
identity
)
)
But that identity in either of these solutions makes me wonder if there's something better. And there is. Ramda's chain when applied to functions is what's sometimes known as the starling combinator, :: (a -> b -> c) -> (a -> b) -> a -> c. Or said a different way, chain (f, g) //~> (x) => f (g (x)) (x). And that's just what we want to apply here. So with chain, this is simplified further:
const arrLens = lensProp('arr')
const res0sumOfDblLens = lensPath(['result', 'sumOfDoubled'])
const calc = pipe (
over (arrLens, map (multiply (2))),
chain (
set (res0sumOfDblLens),
pipe (view (arrLens), sum)
)
)
const obj = { arr: [2, 3], result: { sumOfDoubled: 0 }}
console .log (calc (obj))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {lensProp, lensPath, pipe, over, map, multiply, chain, set, view, sum} = R </script>
To get the updated value, and the object, so you can set the new sum, you can use R.converge():
const arrLens = R.lensProp('arr');
const res0sumOfDblLens = R.lensPath(['result', 'sumOfDoubled']);
const calc = R.pipe(
R.over(arrLens, R.map(R.multiply(2))),
R.converge(R.set(res0sumOfDblLens), [
R.pipe(R.view(arrLens), R.sum),
R.identity
])
);
const obj = { arr: [2, 3], result: { sumOfDoubled: 0 }};
const result = calc(obj);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Maybe a variant without a lense would be a better fit for your case?
const doubleArr = pipe(
path(['arr']),
map(x => x*2)
)
const newData = applySpec({
arr: doubleArr,
result: {
sumOfDoubled: pipe(
doubleArr,
sum
)
}
})
I'm learning functional programming in JS and I'm trying to write my own pluck.
const curry = (f, arr = []) => (...args) =>
(a => (a.length === f.length ? f(...a) : curry(f, a)))([
...arr,
...args,
]);
const map = curry((fn, arr) => arr.map(fn));
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);
const prop = curry((key, obj) => obj[key]);
const pluck = pipe(prop, map);
But for some reason, pluck doesn't work. As far as I thought, this pluck would:
Call prop with the key I invoke pluck with.
So, prop with a curried key gets put as the function into map, which is returned from pipe.
Then if I pass it an array, it should map over the array, applying prop with the key.
But,
pluck('foo')([{ foo: 'bar'}]);
[ƒ]
What am I doing wrong?
Because the built-in .map() function passes 3 arguments to the callback, your code is getting confused. It's easy to fix:
const map = curry((fn, arr) => arr.map(v => fn(v)));
This question already has an answer here:
Why do generators not support map()?
(1 answer)
Closed 9 months ago.
The question is pretty simple: is there any way to call Array methods like filter, find, map, etc. not only on arrays, but on any iterable?
filter, find, map etc make sense not only on an array, but generally on a sequence. And iterable is a sequence that can be oterated over, so it makes sense to filter a sequence, find (the first element in a sequence), map elements of a sequence... whatever the sequence is.
Imagine such case: an infinite generator (such as fibonacci sequence, generator returns one item at a time). I want to find the first element that satisfies a given condition. Using spread like this:
[...fib()].find(conditionFunction)
will first make fib sequence to be dumped, which results in crashing the browser because of memory consumption (infinite sequence). What I could do is to manually call for loop and use conditionFunction inside.
Is there any way to call filter, find, map, etc lazily on (non-array) iterables?
Unfortunately, iterator methods like find are implemented using sequence protocol (.length + Get), not with iterator protocol. You can try fooling them with a proxy that will make iterables impersonate sequences, e.g.
let asArray = iterable => new Proxy(iterable, {
get(target, prop, receiver) {
if(prop === 'length')
return Number.MAX_SAFE_INTEGER;
return target.next().value;
}
});
function *fib() {
let [a, b] = [1, 1];
while (1) {
yield b;
[a, b] = [b, a + b];
}
}
found = [].find.call(
asArray(fib()),
x => x > 500);
console.log(found);
Requires some more work, but you get the idea.
Another (and IMO much cleaner way) would be to reimplement iterator methods to support iterables (and to be generators themselves). Luckily, this is pretty trivial:
function *lazyMap(iter, fn) {
for (let x of iter)
yield fn(x);
}
for (let x of lazyMap(fib(), x => x + ' hey'))...
And here's how one can make a lazy iterator object with chainable methods:
let iter = function (it) {
return new _iter(it);
};
let _iter = function(it) {
this.it = it;
};
_iter.prototype[Symbol.iterator] = function *() {
for (let x of this.it) {
yield x;
}
};
_iter.prototype.map = function (fn) {
let _it = this.it;
return iter((function *() {
for (let x of _it) {
yield fn(x)
}
})())
};
_iter.prototype.take = function (n) {
let _it = this.it;
return iter((function *() {
for (let x of _it) {
yield x;
if (!--n)
break;
}
})())
};
// #TODO: filter, find, takeWhile, dropWhile etc
// example:
// endless fibonacci generator
function *fib() {
let [a, b] = [1, 1];
while (1) {
yield b;
[a, b] = [b, a + b];
}
}
// get first 10 fibs, multiplied by 11
a = iter(fib())
.map(x => x * 11)
.take(10)
console.log([...a])
Using iter-ops library (I'm the author):
import {pipe, first} from 'iter-ops';
const i = pipe(
fib(),
first((value, index) => {
// return a truthy value here when found what you need
})
); // IterableExt<number>
console.log('found:', i.first);