Ramda建议如何从稍微嵌套的数组中删除重复项

9
我们正在尝试利用Ramda避免一些蛮力编程。我们有一个对象数组,可能会像这样:
[
{id: "001", failedReason: [1000]},
{id: "001", failedReason: [1001]},
{id: "001", failedReason: [1002]},
{id: "001", failedReason: [1000]},
{id: "001", failedReason: [1000, 1003]},
{id: "002", failedReason: [1000]}
]

我们希望将其改造成如下形式:

[
{id: "001", failedReason: [1000, 1001, 1002, 1003]},
{id: "002", failedReason: [1000]}
]

本质上,它会根据ID缩小数组大小,并累积一个子“failedReason”数组,其中包含该ID的所有“failedReasons”。我们希望一些Ramda魔法可以做到这一点,但到目前为止我们尚未找到一个好的方法。如果您有任何想法,将不胜感激。

3个回答

8
我无法轻松地在我的手机上进行测试,但是类似这样的内容应该可以工作:
pipe(
  groupBy(prop('id')), 
  map(pluck('failedReason')),
  map(flatten),
  map(uniq)
)

更新

我刚刚在电脑上查看了这个问题,并注意到输出结果并不完全符合您的要求。添加两个步骤将解决此问题:

pipe(
  groupBy(prop('id')), 
  map(pluck('failedReason')),
  map(flatten),
  map(uniq),
  toPairs,
  map(zipObj(['id', 'failedReason']))
)

您可以在Ramda REPL中看到此操作的实现。

Scott - 你的答案非常完美。它具有我们一直努力(但不成功)想要实现的干净优雅。感谢你的帮助 - 你是Ramda大师。特别感谢其他为此问题贡献答案的每个人。我感激大家的时间。 - Gatmando
谢谢。你可能还需要仔细看一下David Chambers的非常不同的答案。他的Thingy包装器可能只有在其他方面也有帮助的情况下才值得做,但如果是这样的话,它可能比这个一次性的解决方案更好。(顺便说一句,他和我都是Ramda的主要作者之一。) - Scott Sauyet

2
你可以定义一个包装类型,满足 Monoid 的要求。然后你只需使用 R.concat 来组合该类型的值即可:
//  Thing :: { id :: String, failedReason :: Array String } -> Thing
function Thing(record) {
  if (!(this instanceof Thing)) return new Thing(record);
  this.value = {id: record.id, failedReason: R.uniq(record.failedReason)};
}

//  Thing.id :: Thing -> String
Thing.id = function(thing) {
  return thing.value.id;
};

//  Thing.failedReason :: Thing -> Array String
Thing.failedReason = function(thing) {
  return thing.value.failedReason;
};

//  Thing.empty :: () -> Thing
Thing.empty = function() {
  return Thing({id: '', failedReason: []});
};

//  Thing#concat :: Thing ~> Thing -> Thing
Thing.prototype.concat = function(other) {
  return Thing({
    id: Thing.id(this) || Thing.id(other),
    failedReason: R.concat(Thing.failedReason(this), Thing.failedReason(other))
  });
};

//  f :: Array { id :: String, failedReason :: Array String }
//    -> Array { id :: String, failedReason :: Array String }
var f =
R.pipe(R.map(Thing),
       R.groupBy(Thing.id),
       R.map(R.reduce(R.concat, Thing.empty())),
       R.map(R.prop('value')),
       R.values);

f([
  {id: '001', failedReason: [1000]},
  {id: '001', failedReason: [1001]},
  {id: '001', failedReason: [1002]},
  {id: '001', failedReason: [1000]},
  {id: '001', failedReason: [1000, 1003]},
  {id: '002', failedReason: [1000]}
]);
// => [{"id": "001", "failedReason": [1000, 1001, 1002, 1003]},
//     {"id": "002", "failedReason": [1000]}]

我相信你可以给这个类型起一个比Thing更好的名字。 ;)

1
为了好玩,主要是探索Ramda的优势,我尝试想出一个“一行代码”来完成相同的数据转换...我现在完全理解Scott's answer的简单易懂:D
我想分享我的结果,因为它很好地说明了一个清晰的API在可读性方面所能做到的。管道连接的map、flatten和uniq比较容易理解...
我使用Map进行分组,使用Set过滤重复的failedReason。

const data = [ {id: "001", failedReason: [1000]}, {id: "001", failedReason: [1001]},  {id: "001", failedReason: [1002]}, {id: "001", failedReason: [1000]}, {id: "001", failedReason: [1000, 1003]}, {id: "002", failedReason: [1000]} ];

const converted = Array.from(data
  .reduce((map, d) => map.set(
      d.id, (map.get(d.id) || []).concat(d.failedReason)
    ), new Map())
  .entries())
  .map(e => ({ id: e[0], failedReason: Array.from(new Set(e[1])) }));
  
console.log(converted);

如果MapIteratorSetIterator.map甚至是.toArray方法,代码会更加清晰。

另一方面,使这段代码难以阅读和理解的唯一因素是 (map.get(d.id) || [])e[0]/e[1]。但是,使用 .map.toArray 可以大大简化代码。做得好! - Scott Sauyet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接