I do have a working code like the following, but I am wondering if there is a way with Ramda to turn this whole expression into a curried function where I can specify the input data argument. Perhaps even compose a whole thing differently.
const data = [
{ val: ['A', 'B'] },
{ val: ['C', 'D'] },
{ val: ['A', 'C', 'E'] },
]
R.uniq(R.flatten(R.map(R.prop('val'), data)))
I tried using R.__, but that's probably working differently, not for such nested calls.
Here's a simple transformation of your function, using compose:
const {compose, uniq, flatten, map, prop} = R;
const data = [
{ val: ['A', 'B'] },
{ val: ['C', 'D'] },
{ val: ['A', 'C', 'E'] },
]
const extract = compose(uniq, flatten, map(prop('val')))
console.log(extract(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
This could also be written with Ramda's order-reversed twin of compose, pipe:
const {pipe, uniq, flatten, map, prop} = R;
const data = [
{ val: ['A', 'B'] },
{ val: ['C', 'D'] },
{ val: ['A', 'C', 'E'] },
]
const extract = pipe(
map(prop('val')),
flatten,
uniq
)
console.log(extract(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
I personally choose compose for one-liners, pipe for anything longer.
The notion of function composition expressed in these two functions is quite central to Ramda. (Disclaimer: I'm a Ramda author.)
Related
I have three arrays that I need to link to each other in this way:
arr1 = ['A', 'A', 'B', 'B', 'C', 'C' 'A', 'C']
arr2 = ['a', 'aa', 'b', 'bb', 'c', 'cc', 'aaa', 'ccc']
arr3 = [1, 2, 3, 4, 5, 6, 7, 8]
I want these arrays to be linked like this: [['A', ['a', 1], ['aa',2], ['aaa',7]], ['B', ['b', 3], ['bb',4]], ['C', ['c', 5], ['cc',6], ['ccc', 8]]
I could create a 2d array like this but I feel it won't be an efficient data structure. Moreover, I will be using HTML to display content from this data structure and the 2d array could get complicated. I was thinking of using a Map but not sure whether it supports arrays as values to the keys. Any ideas/suggestions how I can achieve this?
Update 2
With this version requiring only shared indices in the three arrays, plus some grouping of the results, it's a bit easier:
const link = (a1, a2, a3) =>
[... new Set (a1)] .map ((a) => [
a,
...Object.keys(a1) .filter (k => a1[k] == a) .map (k => [a2 [k], a3 [k]])
])
const arr1 = ['A', 'A', 'B', 'B', 'C', 'C', 'A', 'C']
const arr2 = ['a', 'aa', 'b', 'bb', 'c', 'cc', 'aaa', 'ccc']
const arr3 = [1, 2, 3, 4, 5, 6, 7, 8]
console .log (link (arr1, arr2, arr3))
.as-console-wrapper {max-height: 100% !important; top: 0}
I would still strongly recommend that you rethink your data structures. Shared array indices are a pretty nasty way to deal with data.
I would find this much, much cleaner:
[
{foo: 'A', bars: [{baz: 'a', qux: 1}, {baz: 'aa', qux: 2}, {baz: 'aaa', qux: 7}]},
{foo: 'B', bars: [{baz: 'b', qux: 3}, {baz: 'bb', qux: 4}]},
{foo: 'C', bars: [{baz: 'c', qux: 5}, {baz: 'cc', qux: 6}, {baz: 'ccc', qux: 8}]}
]
Explanation
A comment asked for an explanation of this code. Here's an attempt.
const link = (a1, a2, a3) =>
[... new Set (a1)] .map ((a) => [
a,
...Object.keys(a1) .filter (k => a1[k] == a) .map (k => [a2 [k], a3 [k]])
])
We start by defining link as a function of three parameters, a1, a2, and a3. With realistic data you would likely give these more meaningful names. Our goal will be to take the three arrays and, using shared indices, pair up the values in each by indices, and then group the results based on the first value, each of which may be duplicated multiple times in the first array.
We could have chosen to use a crossproduct implementation to create an intermediate result like [['A', 'a', '1'], ['A', 'aa', 2], ['B', 'b', 3], ...] then do the grouping on those values.
Instead we choose to go a little more directly at this. Our arrow function contains only one expression, so there is no need for { - } or a return statement. We first create an array of unique members of a1, using what's probably the most common implementation of uniqueness: [... new Set (a1)]. Here new Set (a1) creates a set, an container without duplicates. When we prepend it with ..., we get an iterator for the set. And wrapping that in [ - ] makes it into an array. That will give us ['A', 'B', 'C'].
We want an output array containing one output array for each of these, so we can use map, which by applying a function to each element turns one array into another of the same size. The function we pass to map takes a value from ['A', 'B', 'C'] and returns an array. The first element is that value, and the others are found by calling Object.keys on our array. (Logically, Array.keys makes more sense here, as it will give us numeric keys. But that would take one additional step as we'd have to turn its iterator result into an array; and in this case the string keys will work just as well.) We filter these keys/indices to find those where the values in our original array match the current value. For A, this would be ['0', '1', '6'].
We map again, converting these indices into a pair of same-index values from the other two arrays. This is .map (k => [a2 [k], a3 [k]]). Finally, we apply a ... to spread the results into our array.
I hope that helps.
Updated Version
We have more information now. If I understand correctly, we will have some way to match a given element from the second array with one from the first array. Let's encode that in a function, which we can then pass to our generic solution.
Here I demonstrate with the sample data assuming a match is simply a letter match.
const link = (match) => (a1, a2, a3) =>
a1 .map (x => [
x,
...Object.keys(a2) .filter (k => match (x, a2[k]))
.map (k => [a2[k], a3[k]])
])
const arr1 = ['A', 'B', 'C']
const arr2 = ['a', 'aa', 'b', 'bb', 'c', 'cc', 'aaa', 'ccc']
const arr3 = [1, 2, 3, 4, 5, 6, 7, 8]
const matchingLetter = (a1Val, a2Val) =>
a2Val .startsWith (a1Val.toLowerCase())
console .log (link (matchingLetter) (arr1, arr2, arr3))
.as-console-wrapper {max-height: 100% !important; top: 0}
Original Version
As discussed in the comments, this strikes me as a bad idea, if your data sources don't force it upon you.
But if all you need to do is to takes one element from the first array and pair it with a linked set of two pairs from the next two arrays, then you could write something like this:
const link = (a1, a2, a3) =>
a1 .map ((a, i) => [a, [a2[2 * i], a3[2 * i]], [a2[2 * i + 1], a3[2 * i + 1]]])
const arr1 = ['A', 'B', 'C']
const arr2 = ['a', 'aa', 'b', 'bb', 'c', 'cc']
const arr3 = [1, 2, 3, 4, 5, 6]
console .log (link (arr1, arr2, arr3))
.as-console-wrapper {max-height: 100% !important; top: 0}
If there is some other relationship between the linked sets than this little two-for-one mapping, then I think we need more information.
Although it is a common problem but I couldn't find any lead to get the desired result. So here is the problem. I have the following array:
[
[ 'a' ]
[ 'a', 'b' ]
[ 'a', 'c' ]
[ 'a', 'c', 'd' ]
[ 'a', 'c', 'd', 'e' ]
]
And what I want as an end result is an object like this:
{
a: {
b: {},
c: { d: { e: {} } }
}
}
I don't understand which approach would be better to get this result and how to achieve it.
You need a double reduce, one for the outer array and one for the keys and the nesting objects.
var data = [['a'], ['a', 'b'], ['a', 'c'], ['a', 'c', 'd'], ['a', 'c', 'd', 'e']],
result = data.reduce((r, keys) => {
keys.reduce((o, k) => o[k] = o[k] || {}, r);
return r;
}, {});
console.log(result);
I have and array of arrays aa = [['a'], ['b'], ['c']] and i have an array a = ['a', 'b', 'c']
I need to get the item in aa for each element in a i.e i want to list elements in a with their respective arrays in aa the result should be like
a: ['a'] b: ['b'] c: ['c']
I tried this code but it does return the first element i aa for each element in a
I wonder what's wrong here
const aa = [
['a'],
['b'],
['c']
]
const a = ['a', 'b', 'c']
let b = []
a.forEach((el) => {
b.push(
aa.filter((element) => {
return element.includes(el)
})
)
})
console.log(b)
Try this
const aa = [
['a'],
['b'],
['c']
];
const a = ['a', 'b', 'c'];
let b = {};
a.forEach( // loop "a"
aEl => b[aEl] = aa.filter( // filter "aa"
aaEl => aaEl.includes(aEl) // on array that includes the item from 'a'
).flat() // we need to flatten the resulting array before returning it
);
console.log(JSON.stringify(b)); // using stringify to make it readable
Since you want your output to be a key-value list (a: ['a']), variable b should be a map. Let's also rename b to out for readability.
out = {}
To get a better view of if our code is working, let's use some unique test data, and let's rename a to keys and aa to values.
const keys = ['A', 'B', 'C']
const values = [
['A', 'A2', 'a3'],
['B1', 'B', 'b3'],
['C1', 'C2', 'C']
]
For every key in keys, we want to set search for all arrays in values that contain the key. To set the search result to out we use brackets like so:
keys.forEach((key) => {
out[key] = values.filter(valueArr => valueArr.includes(key))
})
This outputs:
{
"A": [["A", "A2", "a3"]],
"B": [["B1", "B", "b3"]],
"C": [["C1", "C2", "C"]]
}
Now there are two arrays around each value. This is because values.filter can return multiple arrays. To combine these into a single array you can use the flat() function. The whole code looks like:
const keys = ['A', 'B', 'C']
const values = [
['A', 'A2', 'a3'],
['B1', 'B', 'b3'],
['C1', 'C2', 'C']
]
out = {}
keys.forEach((key) => {
out[key] = values.filter(valueArr => valueArr.includes(key)).flat()
})
console.log(out)
What is the best way to add List to List in Immutable.js?
concat method is working, but another way is not working.
const a = fromJS([
{
comment: 'aaaa',
who: 'a1',
buttonInfo: ['a', 'b', 'c'],
},
{
comment: 'bb',
who: 'a2',
buttonInfo: ['a', 'b', 'c'],
},
]);
const b = fromJS([
{
comment: 'ccc',
who: 'c1',
buttonInfo: ['a', 'b'],
},
{
comment: 'ddd',
who: 'd2',
buttonInfo: ['a''],
},
]);
This is working:
a.concat(b)
But this is not working:
[...a ,...b]
// or
b.map(v => {
a.push(v);
})
you can use concat method as it said in doc:
const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);
const array = [ 7, 8, 9 ];
const list3 = list1.concat(list2, array);
// List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
An ImmutableJS list has a method named concat whose behavior is the same as a normal javascript array. However, you cannot use spread syntax for an Immutable array.
Also the syntax for push is different from the normal array, push like concat with Immutable List returns a new list, your map method will look like
b.map(v => {
a = a.push(v);
})
P.S. Using the above method though will mutate your array a. You must create a new List and then push both the array contents into it if you want to use push. However concat is the best way for your case
For add List to List in Immutable.js, you can use merge method.
Example:
const a = fromJS(
[
{
comment: 'aaaa',
who: 'a1',
buttonInfo: ['a', 'b', 'c'],
},
{
comment: 'bb',
who: 'a2',
buttonInfo: ['a', 'b', 'c'],
},
]
);
const b = fromJS(
[
{
comment: 'ccc',
who: 'c1',
buttonInfo: ['a', 'b'],
},
{
comment: 'ddd',
who: 'd2',
buttonInfo: ['a''],
},
]
);
a.merge(b);
for example I have an object that that has objects and arrays in itself:
const object =
{
a: {
b: [
0: 'something',
1: {
c: 'the thing that I need',
},
],
},
};
and an array that has the keys as values:
const array =
[
'a', 'b', '1', 'c',
];
How can I use this array to navigate in the object and give me the value?
Maybe there is a way to do this with ramda? or just in general to make it look human readable.
You can reduce the array defining the path through the object.
You do have an error in the array. The path should be: [ 'a', 'b', '1', 'c' ], because the thing you need is inside the second element of the b array, not the first.
const object = {
a: {
b: [
'something',
{ c: 'the thing that I need' }
],
},
};
const path = [ 'a', 'b', '1', 'c' ];
const result = path.reduce(( source, next ) => source[ next ], object );
console.log( result );
Ian Hoffman-Hicks' superb crocks library has a function that does exactly that
import propPathOr from 'crocks/helpers/propPathOr'
const getC = propPathOr(null, ['a', 'b', '0', 'c'])
getC({ a: { b: [{ c: 'gotcha!' }] } }) === 'gotcha!' // true
This function is called path in Ramda.