How to simulate let expressions in JavaScript? - javascript

Consider the following implementation of take:
const take = (n, [x, ...xs]) =>
n === 0 || x === undefined ?
[] : [x, ...take(n - 1, xs)];
console.log(take(7, [1, 2, 3, 4, 5])); // [1, 2, 3, 4, 5]
console.log(take(3, [1, 2, 3, 4, 5])); // [1, 2, 3]
console.log(take(1, [undefined, 1])); // []
As you can see, it doesn't work for arrays with undefined because x === undefined is not the best way to test whether an array is empty. The following code fixes this problem:
const take = (n, xs) =>
n === 0 || xs.length === 0 ?
[] : [xs[0], ...take(n - 1, xs.slice(1))];
console.log(take(7, [1, 2, 3, 4, 5])); // [1, 2, 3, 4, 5]
console.log(take(3, [1, 2, 3, 4, 5])); // [1, 2, 3]
console.log(take(1, [undefined, 1])); // [undefined]
However, writing xs[0] and xs.slice(1) isn't as elegant. In addition, it's problematic if you need to use them multiple times. Either you'll have to duplicate code and do unnecessary extra work, or you'll have to create a block scope, define constants and use the return keyword.
The best solution would be to use a let expression. Unfortunately, JavaScript doesn't have them. So, how to simulate let expressions in JavaScript?

In Lisp, a let expression is just syntactic sugar for a left-left lambda (i.e. an immediately-invoked function expression). For example, consider:
(let ([x 1]
[y 2])
(+ x y))
; This is syntactic sugar for:
((lambda (x y)
(+ x y))
1 2)
In ES6, we can use arrow functions and default parameters to create an IIFE that looks like a let expression as follows:
const z = ((x = 1, y = 2) => x + y)();
console.log(z);
Using this hack, we can define take as follows:
const take = (n, xxs) =>
n === 0 || xxs.length === 0 ?
[] : (([x, ...xs] = xxs) => [x, ...take(n - 1, xs)])();
console.log(take(7, [1, 2, 3, 4, 5])); // [1, 2, 3, 4, 5]
console.log(take(3, [1, 2, 3, 4, 5])); // [1, 2, 3]
console.log(take(1, [undefined, 1])); // [undefined]
Hope that helps.

Instead of using an IIFE just use a normal function with a proper name to make things more explicit:
const _let = f =>
f();
const collateBy = f => xs =>
xs.reduce((m, x) =>
_let((r = f(x), ys = m.get(r) || []) =>
m.set(r, (ys.push(x), ys))), new Map());
const includes = t => s =>
s.includes(t);
xs = ["Dev", "Jeff", "Kalib", "Amy", "Gemma"];
const collation = collateBy(includes("e")) (xs);
console.log(collation.get(true));
console.log(collation.get(false));

Related

Currying a flipped function using lodash - Are there limitations?

I'm new to lodash and just playing around with it to become familiar.
I'm trying to curry a flipped function and I'm getting a TypeError.
Currying the same 'unflipped' function works as expected.
const curriedMap = _.curry(_.map);
const squares1 = curriedMap([ 1, 2, 3, 4 ]);
console.log(squares1(x => x * x)); // [ 1, 4, 9, 16 ]
const flippedMap = _.flip(_.map);
console.log(flippedMap(x => x * x, [1, 2, 3, 4])); // [ 1, 4, 9, 16 ]
const curriedFlippedMap = _.curry(flippedMap);
const makeSquares = curriedFlippedMap(x => x * x);
console.log(makeSquares([1, 2, 3, 4])); // TypeError: makeSquares is not a function
I'm expecting the last line to produce [ 1, 4, 9, 16 ], but instead I get 'TypeError'. What am I doing wrong?
_.map has a length property (number of parameters) that _.curry can use to curry it automatically, but _.flip(_.map) can’t easily produce a new function with the same length as its input (it reverses the entire argument list, it’s not just f => (a, b) => f(b, a)).
> _.map.length
2
> _.flip(_.map).length
0
_.curry lets you specify the number of parameters to work around that:
const curriedFlippedMap = _.curry(flippedMap, 2);

How to sort even numbers ascending and odd numbers descending in an array?

I don't know how to sort the odd numbers in descending order on the right of the even ones. I'm kinda stuck here, I know I am missing something.
My output is like this: [2, 4, 6, 8, 10, 1, 3, 5, 7, 9]
It should be like this: [2, 4, 6, 8, 10, 9, 7, 5, 3, 1]
var n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
n.sort(function(a, b) {
return a % 2 - b % 2 || b % 2 - a % 2;
});
console.log(n);
A trivial and very expensive solution would be to build two arrays, sort them then concatenate them, but you can combine both tests and sort in place like this:
arr.sort((a,b)=> (a%2-b%2) || (a%2 ? b-a : a-b))
As you can see, the pattern for hierarchical sort is just
arr.sort(compareA || compareB)
which you can generalize for more conditions.
let arr = Array.from({length:10}, (_,i)=>i+1)
arr.sort((a,b)=> (a%2-b%2) || (a%2 ? b-a : a-b))
console.log(arr)
You can filter the odds and evens and, sort them and finally concatenate both arrays.
let n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let odds = n.filter((a) => a % 2 !== 0).sort((a, b) => b - a);
let even = n.filter((a) => a % 2 === 0).sort((a, b) => a - b);
let sorted = even.concat(odds);
console.log(sorted);
.as-console-wrapper { min-height: 100%; }
You can use filter() to get different arrays of even and odd numbers and then sort them and join then using Spread Operator
var n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let odd = n.filter(x => x % 2).sort((a,b) => b-a);
let even = n.filter(x => !(x % 2) ).sort((a,b) => a-b);
let res = [...even,...odd]
console.log(res);
Did you try splitting the array into two arrays (one for even and one for odd) then sort each one as you like and join them back using the spread operator [...even, ...odd]
var n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
var even = n.filter(a => a % 2 == 0).sort();
var odd = n.filter(a => a % 2 != 0).sort().reverse();
var numbers = [...even, ...odd];
You can separate two array and merge by spread operator.
let n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let odd = n.filter (v => v % 2);
odd.sort((a,b) => b-a);
//console.log(odd)
let even = n.filter (v => !(v % 2));
even.sort((a,b) => a-b);
//console.log(even);
console.log([...even,...odd]);
var n = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
odd = n.filter (v => v % 2);
odd.sort((a,b) => b-a);
//console.log(odd)
even = n.filter (v => !(v % 2));
even.sort((a,b) => a-b);
//console.log(even);
console.log([...even,...odd]);

How to remove first array element using the spread syntax

So I have an array, ex. const arr = [1, 2, 3, 4];. I'd like to use the spread syntax ... to remove the first element.
ie. [1, 2, 3, 4] ==> [2, 3, 4]
Can this be done with the spread syntax?
Edit: Simplified the question for a more general use case.
Sure you can.
const xs = [1,2,3,4];
const tail = ([x, ...xs]) => xs;
console.log(tail(xs));
Is that what you're looking for?
You originally wanted to remove the second element which is simple enough:
const xs = [1,0,2,3,4];
const remove2nd = ([x, y, ...xs]) => [x, ...xs];
console.log(remove2nd(xs));
Hope that helps.
Destructuring assignment
var a = [1, 2, 3, 4];
[, ...a] = a
console.log( a )
Is this what you're looking for?
const input = [1, 0, 2, 3, 4];
const output = [input[0], ...input.slice(2)];
After the question was updated:
const input = [1, 2, 3, 4];
const output = [...input.slice(1)];
But this is silly, because you can just do:
const input = [1, 2, 3, 4];
const output = input.slice(1);
You could use the rest operator (...arrOutput) with the spread operator(...arr).
const arr = [1, 2, 3, 4];
const [itemRemoved, ...arrOutput] = [...arr];
console.log(arrOutput);

Sequentially Pairing Items in an Array

Given an array, [1, 2, 3, 4, 5], what is the most efficient method for pairing up each of the items sequentially, like so: [[1,2], [2,3], [3,4], [4,5]]?
I've been trying to use the reduce method but to no avail and want something elegant.
Use simple for loop
var data = [1, 2, 3, 4, 5];
var res = [];
for (var i = 0; i < data.length-1; i++) {
res.push(data.slice(i, i+2));
}
console.log(res);
With Array#reduce method
console.log(
[1, 2, 3, 4, 5].reduce(function(a, b, i) {
if (i == 1) return [[a, b]];
a.push([a[a.length - 1][1], b]);
return a
})
)
With Array#reduce method with initial value as empty array
console.log(
[1, 2, 3, 4, 5].reduce(function(a, b, i, arr) {
arr[i + 1] !== undefined && a.push([b, arr[i + 1]])
return a
}, [])
)
To answer the "elegant" bit... ;)
let pairwise = function *(it) {
var
a = it[Symbol.iterator](),
b = it[Symbol.iterator]();
b.next();
for (var x of b) {
yield [a.next().value, x]
}
};
console.log(Array.from(pairwise([1,2,3,4,5])))
Using lodash for given array:
var result = _.chunk( _.sortBy(array.concat(_.slice(array, 1, array.length - 1))), 2);
Check jsfiddle
So if array = [1,2,3,4,5] we have steps:
_.slice(array, 1, array.length - 1)
// = [2,3,4]
array.concat(_.slice(array, 1, array.length - 1)
// = [1,2,3,4,5].concat([2,3,4]) = [1,2,3,4,5,2,3,4]
_.sortBy(array.concat(_.slice(array, 1, array.length - 1))
// _sortBy([1,2,3,4,5,2,3,4]) = [1,2,2,3,3,4,4,5]
_.chunk( _.sortBy(array.concat(_.slice(array, 1, array.length - 1))), 2)
// _chunk([1,2,2,3,3,4,4,5],2) = [[1,2],[2,3],[3,4],[4,5]]
Another short solution using Array.forEach and Array.push functions:
var arr = [1, 2, 3, 4, 5], pairs = [];
arr.forEach((v, k, arr) => arr[k+1] && pairs.push([v, arr[k+1]]));
console.log(JSON.stringify(pairs)); // [[1,2],[2,3],[3,4],[4,5]]
Using reduce:
const res = [1, 2, 3, 4, 5].reduce(
([b, acc], a) => [a, acc.concat([[b, a]])]
, [null, []])[1].slice(1)
console.log(res)
The seed of reduce is a tuple of two items: [null, []]. null represents the current element in the array and [] is the result.
In the first iteration of reduce:
([b, acc], a) => ... b = null and acc = []
The function produces a new tuple, the first item in the tuple is the current element of the array and the second item is the result. In the second iteration:
([b, acc], a) => ..., b = 1 and acc = [[null, 1]]
The second iteration will add (concat) [1, 2] to the result (acc).
In the third iteration:
([b, acc], a) => ..., b = 2 and acc = [[null, 1], [1, 2]]
And so on so forth:
const trace = (x, y) => {
console.log(x);
return y;
}
const json = x => JSON.stringify(x)
const res = [1, 2, 3, 4, 5].reduce(
([b, acc], a) => trace(
`a = ${a}, b = ${b} acc = ${json(acc)} ++ ${json([[b, a]])}`
, [a, acc.concat([[b, a]])]
)
, [null, []]) // seed
// the result is a tuple `[currentElement, finalResult], we extract finalResult here
[1]
// -1 element of the original array was null (check the seed), let's remove it from the result
.slice(1)
console.log(res)
We can think about the problem another way: we are kind of joining the elements of the same array with each other into tuples. Using Ramda zip function is elegant but has a performance tradeoff because we go thru the list twice:
const arr = [1, 2, 3, 4, 5]
const res = R.zip(arr, R.drop(1, arr))
console.log(res)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.21.0/ramda.min.js"></script>
Reduce is most elegant way to do that.
[1,2,3,4,5].reduce((a,b,c) => {
a.push([c,b]);
return a;
}, [])

Iterate an array as a pair (current, next) in JavaScript

In the question Iterate a list as pair (current, next) in Python, the OP is interested in iterating a Python list as a series of current, next pairs. I have the same problem, but I'd like to do it in JavaScript in the cleanest way possible, perhaps using lodash.
It is easy to do this with a simple for loop, but it doesn't feel very elegant.
for (var i = 0; i < arr.length - 1; i++) {
var currentElement = arr[i];
var nextElement = arr[i + 1];
}
Lodash almost can do this:
_.forEach(_.zip(arr, _.rest(arr)), function(tuple) {
var currentElement = tuple[0];
var nextElement = tuple[1];
})
The subtle problem with this that on the last iteration, nextElement will be undefined.
Of course the ideal solution would simply be a pairwise lodash function that only looped as far as necessary.
_.pairwise(arr, function(current, next) {
// do stuff
});
Are there any existing libraries that do this already? Or is there another nice way to do pairwise iteration in JavaScript that I haven't tried?
Clarification: If arr = [1, 2, 3, 4], then my pairwise function would iterate as follows: [1, 2], [2, 3], [3, 4], not [1, 2], [3, 4]. This is what the OP was asking about in the original question for Python.
Just make the "ugly" part into a function and then it looks nice:
arr = [1, 2, 3, 4];
function pairwise(arr, func){
for(var i=0; i < arr.length - 1; i++){
func(arr[i], arr[i + 1])
}
}
pairwise(arr, function(current, next){
console.log(current, next)
})
You can even slightly modify it to be able to make iterate all i, i+n pairs, not just the next one:
function pairwise(arr, func, skips){
skips = skips || 1;
for(var i=0; i < arr.length - skips; i++){
func(arr[i], arr[i + skips])
}
}
pairwise([1, 2, 3, 4, 5, 6, 7], function(current,next){
console.log(current, next) // displays (1, 3), (2, 4), (3, 5) , (4, 6), (5, 7)
}, 2)
In Ruby, this is called each_cons (each consecutive):
(1..5).each_cons(2).to_a # => [[1, 2], [2, 3], [3, 4], [4, 5]]
It was proposed for Lodash, but rejected; however, there's an each-cons module on npm:
const eachCons = require('each-cons')
eachCons([1, 2, 3, 4, 5], 2) // [[1, 2], [2, 3], [3, 4], [4, 5]]
There's also an aperture function in Ramda which does the same thing:
const R = require('ramda')
R.aperture(2, [1, 2, 3, 4, 5]) // [[1, 2], [2, 3], [3, 4], [4, 5]]
Another solution using iterables and generator functions:
function* pairwise(iterable) {
const iterator = iterable[Symbol.iterator]();
let a = iterator.next();
if (a.done) return;
let b = iterator.next();
while (!b.done) {
yield [a.value, b.value];
a = b;
b = iterator.next();
}
}
console.log("array (0):", ...pairwise([]));
console.log("array (1):", ...pairwise(["apple"]));
console.log("array (4):", ...pairwise(["apple", "orange", "kiwi", "banana"]));
console.log("set (4):", ...pairwise(new Set(["apple", "orange", "kiwi", "banana"])));
Advantages:
Works on all iterables, not only arrays (eg. Sets).
Does not create any intermediate or temporary array.
Lazy evaluated, works efficiently on very large iterables.
Typescript version (playground):
function* pairwise<T>(iterable: Iterable<T>): Generator<[T, T], void> {
const iterator = iterable[Symbol.iterator]();
let a = iterator.next();
if (a.done) return;
let b = iterator.next();
while (!b.done) {
yield [a.value, b.value];
a = b;
b = iterator.next();
}
}
This answer is inspired by an answer I saw to a similar question but in Haskell: https://stackoverflow.com/a/4506000/5932012
We can use helpers from Lodash to write the following:
const zipAdjacent = function<T> (ts: T[]): [T, T][] {
return zip(dropRight(ts, 1), tail(ts));
};
zipAdjacent([1,2,3,4]); // => [[1,2], [2,3], [3,4]]
(Unlike the Haskell equivalent, we need dropRight because Lodash's zip behaves differently to Haskell's`: it will use the length of the longest array instead of the shortest.)
The same in Ramda:
const zipAdjacent = function<T> (ts: T[]): [T, T][] {
return R.zip(ts, R.tail(ts));
};
zipAdjacent([1,2,3,4]); // => [[1,2], [2,3], [3,4]]
Although Ramda already has a function that covers this called aperture. This is slightly more generic because it allows you to define how many consecutive elements you want, instead of defaulting to 2:
R.aperture(2, [1,2,3,4]); // => [[1,2], [2,3], [3,4]]
R.aperture(3, [1,2,3,4]); // => [[1,2,3],[2,3,4]]
d3.js provides a built-in version of what is called in certain languages a sliding:
console.log(d3.pairs([1, 2, 3, 4])); // [[1, 2], [2, 3], [3, 4]]
<script src="http://d3js.org/d3.v5.min.js"></script>
# d3.pairs(array[, reducer]) <>
For each adjacent pair of elements in the specified array, in order, invokes the specified reducer function passing the element i and element i - 1. If a reducer is not specified, it defaults to a function which creates a two-element array for each pair.
Here's a generic functional solution without any dependencies:
const nWise = (n, array) => {
iterators = Array(n).fill()
.map(() => array[Symbol.iterator]());
iterators
.forEach((it, index) => Array(index).fill()
.forEach(() => it.next()));
return Array(array.length - n + 1).fill()
.map(() => (iterators
.map(it => it.next().value);
};
const pairWise = (array) => nWise(2, array);
I know doesn't look nice at all but by introducing some generic utility functions we can make it look a lot nicer:
const sizedArray = (n) => Array(n).fill();
I could use sizedArray combined with forEach for times implementation, but that'd be an inefficient implementation. IMHO it's ok to use imperative code for such a self-explanatory function:
const times = (n, cb) => {
while (0 < n--) {
cb();
}
}
If you're interested in more hardcore solutions, please check this answer.
Unfortunately Array.fill only accepts a single value, not a callback. So Array(n).fill(array[Symbol.iterator]()) would put the same value in every position. We can get around this the following way:
const fillWithCb = (n, cb) => sizedArray(n).map(cb);
The final implementation:
const nWise = (n, array) => {
iterators = fillWithCb(n, () => array[Symbol.iterator]());
iterators.forEach((it, index) => times(index, () => it.next()));
return fillWithCb(
array.length - n + 1,
() => (iterators.map(it => it.next().value),
);
};
By changing the parameter style to currying, the definition of pairwise would look a lot nicer:
const nWise = n => array => {
iterators = fillWithCb(n, () => array[Symbol.iterator]());
iterators.forEach((it, index) => times(index, () => it.next()));
return fillWithCb(
array.length - n + 1,
() => iterators.map(it => it.next().value),
);
};
const pairWise = nWise(2);
And if you run this you get:
> pairWise([1, 2, 3, 4, 5]);
// [ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ] ]
We can wrap Array.reduce a little to do this, and keep everything clean.
Loop indices / loops / external libraries are not required.
If the result is required, just create an array to collect it.
function pairwiseEach(arr, callback) {
arr.reduce((prev, current) => {
callback(prev, current)
return current
})
}
function pairwise(arr, callback) {
const result = []
arr.reduce((prev, current) => {
result.push(callback(prev, current))
return current
})
return result
}
const arr = [1, 2, 3, 4]
pairwiseEach(arr, (a, b) => console.log(a, b))
const result = pairwise(arr, (a, b) => [a, b])
const output = document.createElement('pre')
output.textContent = JSON.stringify(result)
document.body.appendChild(output)
Here's a simple one-liner:
[1,2,3,4].reduce((acc, v, i, a) => { if (i < a.length - 1) { acc.push([a[i], a[i+1]]) } return acc; }, []).forEach(pair => console.log(pair[0], pair[1]))
Or formatted:
[1, 2, 3, 4].
reduce((acc, v, i, a) => {
if (i < a.length - 1) {
acc.push([a[i], a[i + 1]]);
}
return acc;
}, []).
forEach(pair => console.log(pair[0], pair[1]));
which logs:
1 2
2 3
3 4
Here's my approach, using Array.prototype.shift:
Array.prototype.pairwise = function (callback) {
const copy = [].concat(this);
let next, current;
while (copy.length) {
current = next ? next : copy.shift();
next = copy.shift();
callback(current, next);
}
};
This can be invoked as follows:
// output:
1 2
2 3
3 4
4 5
5 6
[1, 2, 3, 4, 5, 6].pairwise(function (current, next) {
console.log(current, next);
});
So to break it down:
while (this.length) {
Array.prototype.shift directly mutates the array, so when no elements are left, length will obviously resolve to 0. This is a "falsy" value in JavaScript, so the loop will break.
current = next ? next : this.shift();
If next has been set previously, use this as the value of current. This allows for one iteration per item so that all elements can be compared against their adjacent successor.
The rest is straightforward.
My two cents. Basic slicing, generator version.
function* generate_windows(array, window_size) {
const max_base_index = array.length - window_size;
for(let base_index = 0; base_index <= max_base_index; ++base_index) {
yield array.slice(base_index, base_index + window_size);
}
}
const windows = generate_windows([1, 2, 3, 4, 5, 6, 7, 8, 9], 3);
for(const window of windows) {
console.log(window);
}
Simply use forEach with all its parameters for this:
yourArray.forEach((current, idx, self) => {
if (let next = self[idx + 1]) {
//your code here
}
})
Hope it helps someone! (and likes)
arr = [1, 2, 3, 4];
output = [];
arr.forEach((val, index) => {
if (index < (arr.length - 1) && (index % 2) === 0) {
output.push([val, arr[index + 1]])
}
})
console.log(output);
A modifed zip:
const pairWise = a => a.slice(1).map((k,i) => [a[i], k]);
console.log(pairWise([1,2,3,4,5,6]));
Output:
[ [ 1, 2 ], [ 2, 3 ], [ 3, 4 ], [ 4, 5 ], [ 5, 6 ] ]
A generic version would be:
const nWise = n => a => a.slice(n).map((_,i) => a.slice(i, n+i));
console.log(nWise(3)([1,2,3,4,5,6,7,8]));
You can do this with filter and map:
let a = [1, 2, 3, 4]
console.log(a.filter((_,i)=>i<a.length-1).map((el,i)=>[el,a[i+1]]))
You can omit the filter part if you are ok with the last element being [4, undefined]:
let a = [1, 2, 3, 4]
console.log(a.map((el,i)=>[el,a[i+1]]))
Lodash does have a method that allows you to do this: https://lodash.com/docs#chunk
_.chunk(array, 2).forEach(function(pair) {
var first = pair[0];
var next = pair[1];
console.log(first, next)
})

Categories