Avoid double concat in Ramda - javascript

My question is simple. I started with Ramda recently, and I like it, for it is pure functional. I have a little issue with concat function, as it only accepts two lists as arguments. So, if I need to concat three lists or more, I have to chain functions like this: concat(list1, concat(list2, concat(list3, list4))) (for a four lists concatenation). Is there any better way to do this, that I don't know? Thanks.

If you want to concatenate a list of lists, you can reduce the list using R.concat with an empty list as the initial value.
const concatAll = R.reduce(R.concat, []);
concatAll([[1, 2], [3, 4], [5, 6]]);
Or just pass the lists directly to R.reduce.
R.reduce(R.concat, [], [[1, 2], [3, 4], [5, 6]]);
If you want to create a new variadic function that takes each list as a separate argument, then you can wrap the function with R.unapply.
const concatAll_ = R.unapply(R.reduce(R.concat, []));
concatAll_([1, 2], [3, 4], [5, 6]);
Ramda also has an R.unnest function, which when given a list of lists will concatenate them together.
R.unnest([[1, 2], [3, 4], [5, 6]]);

I haven't used the Ramda library, but you appears to be using this in node.js from the documentation I've read in the link you posted. In that case, you can use the arguments variable in a function in node.js to write your own concat function that takes n lists as input. The arguments variable is essentially an array of the arguments inputted into the function.
function myConcat () {
for (var i = 1; i < arguments.length; i++) {
concat(arguments[0], arguments[i]);
}
return arguments[0];
};
In this case however, you would probably have to call it like:
list1 = myConcat(list1, list2, list3, list4);

Depending on exactly what you are concatenating and your environment (ES2015 required), you could do:
const newList = [...list1, ...list2, ...list3];
Otherwise, you are stuck with multiple calls to concat, though you could make it a little cleaner with compose:
const newList = compose(concat(list1), concat(list2), concat(list4))(list3);
really though you want to map or better, reduce:
const newList = reduce((acc, x) => concat(acc, x), [list3], [list4, list2, list1]);
or that inner reduce function could look like:
(acc, x) => [...acc, ...x]

Using R.unnest with R.unapply allows you to call your function as a variadic:
const unnestAll = R.unapply(R.unnest)
unnestAll([1, 2], [3, 4], [5, 6, 7]) //=> [1, 2, 3, 4, 5, 6, 7]
A slight variation on Scott Cristopher's answer
See Example in repl

You can use flatten method this way:
R.flatten([list1, list2, list3, list4, ])
Technically you pass a single array, which contains all your lists. Then you just flatten them all into a single array.
Do NOT use this method if some of your lists might contain list themselves - they'll be flattened as well.

Related

how does this concat command work with this array?

I came across this JS problem but I can't figure out the syntax of how it's working, could someone please help to explain? I don't understand the empty square bracket syntax at the start and then how the concat is being applied with another empty square bracket? it's just quite confusing for me.
Appreciate any help to step through this.
let arr = [[1, 2],[3, 4],[5, 6, 7, 8, 9],[10, 11, 12]];
let flattened = [].concat.apply([], arr);
// [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]
let arr = [[1, 2],[3, 4],[5, 6, 7, 8, 9],[10, 11, 12]];
let flattened = [].concat.apply([], arr);
*first array*---| |---*second array*
The first array has one goal:
to invoke concat() method with this. Here this is the second array. Then the first array is thrown away
Then apply() method flattens the arr array to one level.
You can check it:
let foo = [0].concat([1,2,3], [[1,2,3]] );
console.log(foo)
UPDATE:
In addition, the first array can be removed:
const arr = [[1, 2],[3, 4],[5, 6, 7, 8, 9],[10, 11, 12]];
const flattened = Array.prototype.concat.apply([], arr);
console.log(flattened)
Some info how apply() works.
In addition, it would be really useful to know how apply() flattens array items to one level or how apply() takes an array of arguments and treats each element of that array as a single argument.
Let me show a simple example. We have the following object and method:
const showFullName = (arg_1, arg_2) => {
console.log(`arg_1 is ${arg_1}`);
console.log(`arg_2 is ${arg_2}`);
}
let foo = {};
showFullName.apply(foo, ['firstName', 'surname']);
The [] is an empty array. The concat function concatenate two or more arrays: [].concat(arr[0], arr[1], arr[2], arr[3])
The catch here is to know how apply works. The second parameter of apply is an array of parameters. It's like doing this (if you're familiar with the concept of desctructuring):
[].concat(...arr)
You can read more about apply here

How would one add items to the beginning of an array with Lodash?

I've been searching for a while to add items to the beginning of an array with lodash. Unfortunately I can't seem to find anything other than lodash concat (to the end of the array). The docs don't seem to say anything about it either.
I got the following code:
const [collection, setCollection] = useState({
foo: [1, 2, 3]
});
const addToCollection = (key, items) => {
setCollection(prevCollection => ({
...prevCollection,
[key]: _.concat(prevCollection[key] || [], items)
}));
};
But this concats all the items to the end. I don't want to sort them every time because that uses unnessecary processing power, I would much rather just add them to the beginning because the API always pushes the items already sorted
How would I accomplish this:
addToCollection('foo', [4, 5, 6]);
console.log(collection['foo']) // [4, 5, 6, 1, 2, 3];
Instead of what is happening now:
addToCollection('foo', [4, 5, 6]);
console.log(collection['foo']) // [1, 2, 3, 4, 5, 6];
Try swapping the arguments:
_.concat(items, prevCollection[key] || [])
Or vanilla JS is pretty easy too:
Collection.unshift('addMe', var, 'otherString' )
https://www.w3schools.com/jsref/jsref_unshift.asp#:~:text=The%20unshift()%20method%20adds,use%20the%20push()%20method.
I know you asked for lodash but I figured this is a good thing to be aware of too :)
EDIT:
To clarify, this works the same whether you're pushing defined vars, string, arrays, objects or whatever:
let yourArray = [1,2,3];
let pushArray = [1,2,3,4];
let anotherArray = [7,8,9];
yourArray.unshift(pushArray, anotherArray);
will push "pushArray" and "anotherArray" to the begining of "yourArray" so it's values will look like this:
[[1,2,3,4], [7,8,9], 1,2,3]
Happy Coding!

Javascript recursion: pull all unique combinations of single items from N arrays of M length

I am looking to create a recursive function (or use loops), to select all possible combinations of single items of N arrays, each having M length.
I want to pull out each combination and find the product of the items from the arrays and store the result. So in other words, the order of the items pulled out doesn't matter (for the example, 1, 1, 4 pulled from the first index starting with array1 would be considered the same as 4, 1, 1 pulled from the first index starting with array3 and working backwards).
//Example:
//in this case, N = 3 and M = 5, 3, 4 for array1, array2, and array3, respectively
array1 = [1, 3, 5, 6, 7];
array2 = [1, 5, 3];
array3 = [4, 3, 7, 9];
//Example output using arrays above:
[1, 1, 4]
[3, 1, 4]
[5, 1, 4]
[6, 1, 4]
[7, 1, 4]
[1, 5, 4]
[1, 3, 4]
[3, 5, 4]
//etc...
I expect the output of each recursive call to be one item from each Array, resulting in a unique combination of items.
For example, each call would output an array of N length, with one value from each array. The function should run until all unique combinations have been looked at.
UPDATE: to clarify, the final end solution that I am trying to get at is to find the Minimum product, by selecting one item from each array and multiplying them together. But I also need to evaluate, manipulate and store each combination, before determining this minimum.
Cou could get the cartesian product first and then get unique products from this values.
The raw array contains possible duplicates.
const multiply = (a, b) => a * b;
var array1 = [1, 3, 5, 6, 7],
array2 = [1, 5, 3],
array3 = [4, 3, 7, 9],
data = [array1, array2, array3],
raw = data.reduce((a, b) => a.reduce((r, v) => r.concat(b.map(w => [].concat(v, w))), [])),
unique = Array.from(new Set(raw.map(a => a.reduce(multiply))));
console.log(unique);
console.log(raw.map(a => a.join(' ')));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I prefer to write this sort of thing as a composition of functions. I'm a big fan of Ramda (disclaimer: I'm a Ramda author) and with Ramda, this is a one-liner 1:
const uniqueProduct = pipe (xprod, map (product), unique)
Of course you wouldn't want to pull in an external library for a single simple problem, but it's also easy to write our own versions of the functions used here:
// Utility functions
const pipe = (...fns) => (args) =>
fns .reduce ((a, f) => f (a), args)
const map = (fn) => (xs) =>
xs.map(fn)
const product = (xs) =>
xs .reduce ((a, x) => a * x, 1)
const crossproduct = (xss) =>
xss.reduce(
(ps, xs) => ps.reduce((r, p) => [...r, ...(xs.map((x) => [...p, x]))], []),
[[]]
)
const unique = (xs) =>
Array .from (new Set (xs))
// Main function
const uniqueProduct = pipe (crossproduct, map (product), unique)
// Demonstration
const data = [[1, 3, 5, 6, 7], [1, 5, 3], [4, 3, 7, 9]]
console .log (
uniqueProduct (data)
)
Every function here, except for uniqueProduct is common enough that there's already a version available in Ramda. But these versions are also potentially useful across a great deal of your code. And outside crossproduct, they are trivial-to-write functions.
Note that there is no difference here in algorithm from Nina Scholz's answer; it's only structured differently. I like the fact that, by storing the common functions in my own library or by using a public one, this code can be written so simply.
1 This is actually a bit of an exaggeration. Ramda's xprod function only works on two arrays; I happen to keep handy one that works on an array of arrays in my own personal utility library built atop Ramda.

Return spreaded array in arrow function

Let's assume i have this type of array:
[ [1, 2], [3, 4] ]
What i need to do is to get nested elements on the higher layer, to make it look like:
[1, 2, 3, 4]
I am trying to reach that in functional way, so the code look's like this:
const arr = [ [1, 2], [3, 4] ]
const f = Array.from(arr, x => ...x)
But that comes up with Unexpected token ... error. So what's the way to do it right?
You can use the flat method of Array:
const inp = [ [1, 2], [3, 4] ];
console.log(inp.flat());
In your case, the spread syntax is not an operator that you can use in that way, that's why the error.
As #MarkMeyer correctly pointed out in the comments, the flat is not supported yet by Edge and Internet Explorer. In this case you could go for a solution with reduce:
const inp = [[1,2], [3,4]];
console.log(inp.reduce((acc, val) => acc.concat(...val), []));
Array.from will produce an item for every item in the array passed in. It looks at the length of the passed in iterable and iterates over the indexes starting at 0. So no matter what you do in the callback (assuming it's valid), you're going to get an array of length 2 output if you pass in a two-element array.
reduce() is probably a better option here:
let arr = [ [1, 2], [3, 4] ]
let flat = arr.reduce((arr, item) => [...arr, ...item])
console.log(flat)
You could create an iterator for the array and spread the array by using another generator for nested arrays.
function* flat() {
for (var item of this.slice()) {
if (Array.isArray(item)) {
item[Symbol.iterator] = flat;
yield* item
} else {
yield item;
}
}
}
var array = [[1, 2], [3, 4, [5, 6]]];
array[Symbol.iterator] = flat;
console.log([...array]);

How does using specifically "[]" as a parameter work?

I was doing some javascript exercises online (codewars.com). One problem asked the user to take an array of array objects and remove one level from the entirety of the array.
[] /* becomes */ []
[[1, 2, 3], ["a", "b", "c"], [1, 2, 3]] /* becomes */ [1, 2, 3, "a", "b", "c", 1, 2, 3]
[[3, 4, 5], [[9, 9, 9]], ["a,b,c"]] /* becomes */ [3, 4, 5, [9, 9, 9], "a,b,c"]
I ended up learning about the concat method, but the most popular solution used this statement...
function (arr){
return [].concat.apply([],arr);
}
Can someone please explain the usage of [] here? I can't understand how this produces the correct results (and it doesn't give explanation on the page). I know that there are plenty of other times in which empty brackets are used as parameters and labeling arrays so understanding the usage here may help me be able to use it myself in the future.
Lets split this across multiple lines so it is easier to describe. The description of line D specifically is what answers your question
[] // A
.concat // B
.apply( // C
[], // D
arr // E
);
A Is an Array, but here it is just being using as a shortcut to Array.prototype so we can access..
B The concat method from Array.prototype
C which we then invoke (using apply) with a
D this argument of a new Array, to be the base Object and
E a list of arguments, which was our previous arr
So, you could re-write this using Array.prototype and call as
var new_base_arr = [];
Array.prototype.concat.call(
new_base_arr,
arr[0],
arr[1],
...
arr[n]
);
Which might look more familiar to you written as
new_base_arr.concat(arr[0], arr[1], ..., arr[n]);
The problem being solved here is invoking a function with an undetermined number of arguments.

Categories

Resources