如何在使用 Ramda 挑选另一个数组的键后追加到数组

How to append to an array after cherry picking keys of another using Ramda

我有一个现有的对象数组

const oldData = [
  {'one': 1, 'two': 2},
  {'one': 3, 'two': 4}
];

我有一个新的对象数组:

const newData = [
  {'three': 5, 'two': 6, 'one': 7},
  {'five': 8, 'one': 9, 'two': 10}, 
];

我有一个数组,其中包含我要提取的对象道具(这是一个变量)

const columnMeta = ['one', 'two'];

我想使用 Ramda 创建一个可组合、可重用的函数。此函数应仅提取 newArray 的所选 columnMeta 属性并将其附加到 oldArray

中存在的任何内容

以下有效,但似乎有点过于冗长(而且几乎无法重用..)

// simple enough, join two arrays using the spread operator
const appendData = curry((curr, prev) => [...prev, ...curr]);

// get a subset of an object, given an array of keys
const extractByColumnMeta = curry((k, obj) => zipObj(k, props(k)(obj)));

// create a composable function that takes an array of keys
const mapDataByColumns = compose(map, extractByColumnMeta)(columnMeta);

// compose a new function that would take a the newData & oldData
const mergeDataAfterMap = compose(appendData, mapDataByColumns);

// works, but I'm sure I can do better :)
mergeDataAfterMap(newData)(oldData);

// => [{one: 1, two: 2}, {one: 3, two: 4}, {one: 7, two: 6}, {one: 9, two: 10}]

非 ramda 方式

const oldData = [
  {'one': 1, 'two': 2},
  {'one': 3, 'two': 4}
];

const newData = [
  {'three': 5, 'two': 6, 'one': 7},
  {'five': 8, 'one': 9, 'two': 10}, 
];

const columnMeta = ['one', 'two'];


const mapDataToColumns = (fields, oldData, newData) => {
    // a new array. anything in oldData is overwritten
    return [...oldData, ...newData.map(row => {
        // reduce to return only wanted keys
        return fields.reduce((acc, field) => {
          // add to accumulator or return
          return row.hasOwnProperty(field) 
            ? {...acc, [field]:row[field]}
            : acc;
        }, {});
    })];
}

console.log(mapDataToColumns(columnMeta, oldData, newData));

您只需要将 R.pickR.concat 组合起来,但要采用一种有点棘手的方式。您正在尝试使用二元函数 R.concat.

组合一元函数 R.pick(columnMenta)
const compose2 = R.compose(R.compose, R.compose)

const foo = compose2(R.map(R.pick(['one', 'two'])), R.concat)

console.log(foo(oldData, newData))
// => [{"one":1,"two":2},{"one":3,"two":4},{"one":7,"two":6}, {"one":9,"two":10}]

两个输入的任意限制使这有点弱,而且大多数人不喜欢 compose2 诡计。如果您有多个输入数据列表并且想以一种合理的方式组合它们怎么办?输入 R.chain

const foo = R.chain(R.map(R.pick(['one', 'two'])))

console.log(foo([oldData, newData, oldData]))
// => [{"one":1,"two":2},{"one":3,"two":4},{"one":7,"two":6},{"one":9,"two":10},{"one":1,"two":2},{"one":3,"two":4}]

注意 foo 现在需要一个 数组 输入,它可以有效地允许您根据需要组合任意数量的数据集


好的,看来您还希望 columnMeta 作为函数的输入。如果我们采用后一个答案将是最简单的

const foo = R.curry((columns, xxs) =>
  R.chain(R.map(R.pick(columns)), xxs))

console.log(foo(['one'], [oldData, newData, oldData]))
// => [{"one":1},{"one":3},{"one":7},{"one":9},{"one":1},{"one":3}]

console.log(foo(['two'], [oldData, newData, oldData]))
// => [{"two":2},{"two":4},{"two":6},{"two":10},{"two":2},{"two":4}]

console.log(foo(['one', 'two'], [oldData, newData, oldData]))
// => [{"one":1,"two":2},{"one":3,"two":4},{"one":7,"two":6},{"one":9,"two":10},{"one":1,"two":2},{"one":3,"two":4}]

如果你不介意这个API:

mergeAndPick(columnMeta, [oldData, newData]);
//=> [{"one": 1, "two": 2}, {"one": 3, "two": 4}, 
//    {"one": 7, "two": 6}, {"one": 9, "two": 10}]

那你就可以简单的写成这样:

const mergeAndPick = R.useWith(R.project, [R.identity, R.unnest]);

如果你真的想要这个:

mergeDataAfterMap(columnMeta, oldData, newData);
//=> [{"one": 1, "two": 2}, {"one": 3, "two": 4}, 
//    {"one": 7, "two": 6}, {"one": 9, "two": 10}]

然后你可以在上面构建它。

const mergeDataAfterMap = (columnMeta, oldData, newData) => 
    mergeAndPick(columnMeta, [oldData, newData]);

但第一个更灵活,允许您指定任意数量的列表进行组合。

这是建立在一个相当不寻常的 Ramda 函数 useWith 之上的,这使得以 points-free 方式组合函数变得更加容易。在 ES6 时代,它的优势不那么明显,可以改写为

const mergeAndPick = R.curry((columnMeta, lists) => 
    R.project(columnMeta, R.unnest(lists)));

您可以在 Ramda REPL.

上看到所有这些操作

基于@ScottSauyet 的回答和对@naomik 的回答的评论,我想出了这个:

const pickAndMerge = compose(chain, project);

然后就是,

const oldData = [
  {'one': 1, 'two': 2},
  {'one': 3, 'two': 4}
];

const newData = [
  {'three': 5, 'two': 6, 'one': 7},
  {'five': 8, 'one': 9, 'two': 10}, 
];

const columnMeta = ['one', 'two'];

const pickAndMerge = R.compose(R.chain, R.project);

console.log(pickAndMerge(columnMeta)([oldData, newData]));
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.20.0/ramda.min.js"></script>

怎么样:

const mergeSelectAttr = R.curry((oldData, columnMeta, newData) => 
  R.compose(
    R.concat(oldData), 
    R.map(R.pick(columnMeta))
  )(newData) 
)

Ramda fiddle

我想说这会产生比当前答案更灵活的 api,您可以根据自己的方便称呼它:

  • mergeSelectAttr(oldData, columnMeta, newData)
  • mergeSelectAttr(oldData)(columnMeta, newData)
  • mergeSelectAttr(oldData, columnMeta)(newData)

即使您在稍后阶段获得 newData,例如从 api 调用/数据库结果,您使用此解决方案获得的一个主要优势仍然有效,因为柯里化。你当然也可以改变 oldDatacolumnMeta 的顺序,这取决于你先得到什么

例如:

// You have olData and coulmnMeta now, but not newData:
const merger = mergeSelectAttr(oldData, columnMeta)
    .
    .
    .
    .
// A little while later, when you have access to newData:

const finalValue = merger(newData)