简体   繁体   中英

Combine two arrays with two different keys with Ramda

I'm new to Ramda and currying but would like to combine two different arrays. Given:

var array1 = [
 {_id: 0, x: 10},
 {_id: 1, x: 30}
];

var array2 = [
  {UUID: 0, y: 20},
  {UUID: 1, y: 60}
];

I would like to join them where _id === UUID to get something like this:

var result = [
  {_id: 0, UUID: 0, x: 10, y: 20},
  {_id: 1, UUID: 1, x: 30, y: 60}
];

I looked at the docs and where , intersection , and merge seemed to be the ones to use. But intersection and merge expect the same key. where seems to be the ticket, but I'm still not sure how to tell it that I want two different keys to have the same value.

So I figure I can do it mirroring the POJS way, using one or more of forEach , find , and equals . Here's what I was thinking (no intersection or where usage so far):

import r from 'ramda';
var result = 
  r.forEach(
    r.find(
      r.equals(
        r.prop('UUID', r._),
        r.prop('_id', r._)
      ), data, metadata);

I'm stuck at just how to check for the separate keys ( UUID and _id ) from the two arrays while keeping the functional notation. I believe I'm going about it all wrong but my mind is rutted and I'll try a plain Javascript approach first.

One could use R.indexBy , R.mergeWith , and R.merge :

//  propMerge :: String -> String -> Array Object -> Array Object -> Array Object
var propMerge = R.curry(function(k1, k2, xs1, xs2) {
  return R.values(R.mergeWith(R.merge,
                              R.indexBy(R.prop(k1), xs1),
                              R.indexBy(R.prop(k2), xs2)));
});

What you want is likely to use generators . Like so:

arr1.sort((a, b) => a._id - b._id);
arr2.sort((a, b) => a.UUID - b.UUID);

let step1 = arr1[Symbol.iterator]();
let step2 = arr2[Symbol.iterator]();

let zipSeq = (first, second) => {
  let done = false, result = [];
  while (!done) {
    let obj = first.next();
    let check = second.next();
    while (check.value.UUID !== obj.value._id && !check.done) {
      check = second.next(); // make sure we account for non-sequential
    }
    result.push(Object.assign({}, obj.value, check.value));
    done = obj.done || check.done;
  }
  return result;
};

let mixed = zipSeq(step1, step2);

Generalizing the zipSeq function to take arbitrary generators and a combining callback is left as an exercize to the reader.

There are certainly more compact ways to solve this than lazy sequences, but its pretty short and almost certainly more performant than repeatedly searching the second array for matches, and its far less imperative than maintaining and incrementing two separate indexing variables to do both in one pass.

Since per your comment there are always matches, you can sort as above and then do this:

let results = R.zip(arr1, arr2, (a, b) => Object.assign({}, a, b));

Since you had in mind to

" try a plain Javascript approach first."

here is a native JavaScript alternative solution using Array.concat , Array.find , Array.indexOf , Array.map , Object.keys and Object.assign functions:

var keys = ['_id', 'UUID'], result = {};

array1.concat(array2).forEach(function(obj){
    var value = obj[Object.keys(obj).find((v) => keys.indexOf(v) !== -1 )],
        props = Object.keys(obj);  // array of property names of the current object

    result[value] = result[value] || obj;
    if (Object.keys(result[value]).indexOf(props[0]) === -1) {
        Object.assign(result[value], obj);  // "merging" two objects
    }
}, result);
result = Object.keys(result).map((k) => result[k]);

console.log(JSON.stringify(result, 0, 4));

The output:

[
    {
        "_id": 0,
        "x": 10,
        "UUID": 0,
        "y": 20
    },
    {
        "_id": 1,
        "x": 30,
        "UUID": 1,
        "y": 60
    }
]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM