使用 ramda 修改对象数组

Jey*_*ani 1 ramda.js

我有一个像下面这样的对象数组,

[
{      
  "myValues": []
},
{
 "myValues": [],
 "values": [
    {"a": "x", "b": "1"},
    {"a": "y", "b": "2"}
  ],
  "availableValues": [],      
  "selectedValues": []
}
]
Run Code Online (Sandbox Code Playgroud)

此外,如果我迭代对象,对象中存在的“值”键,它会将其转换为如下所示,

[
{      
  "myValues": []
},
{
  "myValues": [],
  "values": [
    {"a": "x", "b": "1"},
    {"a": "y", "b": "2"}
  ],
  "availableValues": [
    {"a": "x", "b": "1"},
    {"a": "y", "b": "2"}
  ],      
  "selectedValues": ["x", "y"]
}
]
Run Code Online (Sandbox Code Playgroud)

我尝试过 ramda 函数式编程,但没有结果,

let findValues = x => {
  if(R.has('values')(x)){
  let availableValues = R.prop('values')(x);
  let selectedValues = R.pluck('a')(availableValues);
  R.map(R.evolve({
    availableValues: availableValues,
    selectedValues: selectedValues
  }))
 }
}
R.map(findValues, arrObj);
Run Code Online (Sandbox Code Playgroud)

Sco*_*yet 6

不变性

首先,你需要小心你的动词。Ramda 不会修改您的数据结构。不变性是 Ramda 以及一般函数式编程的核心。如果您真的想改变您的数据,Ramda 将不是您的工具包。但是,Ramda 会转换它,并且它可以以相对内存友好的方式进行转换,重用数据结构中未转换的那些部分。

修正你的方法

我们先来看看清理你函数中的几个问题:

let findValues = x => {
  if (R.has('values')(x)) {
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    R.map(R.evolve({
      availableValues: availableValues,
      selectedValues: selectedValues
    }))
  }
}
Run Code Online (Sandbox Code Playgroud)

第一个也是最明显的问题是这个函数不返回任何东西。如上所述,Ramda 不会改变您的数据。因此,当提供给 Ramda 的map. 我们可以通过返回map(evolve)调用结果并在if条件失败时返回原始值来解决此问题:

let findValues = x => {
  if (R.has('values')(x)) {
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    return R.map(R.evolve({
      availableValues: availableValues,
      selectedValues: selectedValues
    }))
  }
  return x;
}
Run Code Online (Sandbox Code Playgroud)

接下来,map调用毫无意义。 evolve已经迭代了对象的属性。所以我们可以删除它。但我们还需要应用evolve到您的输入值:

let findValues = x => {
  if (R.has('values')(x)){
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    return R.evolve({
      availableValues: availableValues,
      selectedValues: selectedValues
    }, x)
  }
  return x;
}
Run Code Online (Sandbox Code Playgroud)

还有一个问题。 Evolve期望包含转换值的函数的对象。例如,

evolve({
  a: n => n * 10,
  b: n => n + 5
})({a: 1, b: 2, c: 3}) //=> {a: 10, b: 7, c: 3}
Run Code Online (Sandbox Code Playgroud)

请注意,提供给对象的属性值evolve是函数。此代码正在提供值。我们可以换用这些值R.always,通过availableValues: R.always(availableValues),但我认为它更简单的使用lambda用的“参数_”,这意味着该参数是不重要的:availableValues: _ => availableValues。你也可以写availableValues: () => available values,但下划线表明通常的值被忽略的事实。

解决这个问题得到一个有效的功能:

let findValues = x => {
  if (R.has('values')(x)) {
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    R.map(R.evolve({
      availableValues: availableValues,
      selectedValues: selectedValues
    }))
  }
}
Run Code Online (Sandbox Code Playgroud)
let findValues = x => {
  if (R.has('values')(x)) {
    let availableValues = R.prop('values')(x);
    let selectedValues = R.pluck('a')(availableValues);
    return R.map(R.evolve({
      availableValues: availableValues,
      selectedValues: selectedValues
    }))
  }
  return x;
}
Run Code Online (Sandbox Code Playgroud)

如果您在此处运行此代码段,您还会看到一个有趣的点,即结果availableValues属性是对原始属性的引用values,而不是它的单独副本。这有助于表明 Ramda 正在重用原始数据。你也可以通过注意到这一点来看到这一点R.map(findValues, arrObj)[0] === arrObj[0] //=> true。

其他方法

我写了我自己的版本,而不是从你的版本开始。这是一个工作功能:

const findValues = when(
  has('values'),
  x => evolve({
    availableValues: _ => prop('values', x),
    selectedValues: _ => pluck('a', prop('values', x))
  }, x)
)
Run Code Online (Sandbox Code Playgroud)

请注意 的使用when,它捕获了“当此谓词对您的值成立时,使用以下转换;否则使用您的原始值”的概念。我们传递 predicate has('values'),本质上和上面一样。我们的转换与您的类似,使用evolve,但它跳过临时变量,以重复prop调用为代价。

关于这个版本,我仍然有些困扰。使用带有“ _”的那些 lambdas实际上是对evolve. 我认为这更清洁:

const findValues = when(
  has('values'),
  x => merge(x, {
    availableValues: prop('values', x),
    selectedValues: pluck('a', prop('values', x))
  })
)
Run Code Online (Sandbox Code Playgroud)

这里我们使用merge,它是 的纯函数版本Object.assign,将第二个对象的参数添加到第一个对象的参数,当它们冲突时替换。

这是我会选择的。

花药液

自从我开始写这个答案以来,斯科特克里斯托弗发布了另一个,基本上

const findValues = R.map(R.when(R.has('values'), R.applySpec({
  myValues:        R.prop('myValues'),
  values:          R.prop('values'),
  availableValues: R.prop('values'),
  selectedValues:  R.o(R.pluck('a'), R.prop('values'))
})))
Run Code Online (Sandbox Code Playgroud)

这是一个很好的解决方案。它也是很好的无点。如果您的输入对象固定为这些属性,那么我会选择这个而不是我的merge版本。如果您的实际对象包含更多属性,或者只有在输入中才应包含输出中的动态属性,那么我会选择我的merge解决方案。关键是 Scott 的解决方案对于这个特定结构更清晰,但我的解决方案可以更好地扩展到更复杂的对象。

一步步

这不是一个非常复杂的转换,但它是不平凡的。构建这样的东西的一种简单方法是在非常小的阶段构建它。我经常在Ramda REPL 中这样做,这样我就可以看到结果。

我的过程看起来像这样:

第1步

确保我只正确转换了正确的值:

const findValues = when(
  has('values'), 
  always('foo')
)
map(findValues, arrObj) //=> [{myValues: []}, "foo"]
Run Code Online (Sandbox Code Playgroud)

第2步

确保我的变压器功能具有正确的值。

const findValues = when(
  has('values'), 
  keys
)
map(findValues, arrObj) 
//=> [{myValues: []}, ["myValues", "values", "availableValues", "selectedValues"]]
Run Code Online (Sandbox Code Playgroud)

这里identity和keys. 任何证明传递了正确对象的东西都可以。

第 3 步

测试merge这里是否能正常工作。

const findValues = when(
  has('values'), 
  x => merge(x, {foo: 'bar'})
)
map(findValues, arrObj) 
//=> [
//     {myValues: []}, 
//     {
//       myValues: [], 
//       values: [{a: "x", b: "1"}, {a: "y", b: "2"}],
//       availableValues: [], 
//       selectedValues: [], 
//       foo: "bar"
//     }
//   ]
Run Code Online (Sandbox Code Playgroud)

所以我可以正确合并两个对象。请注意,这会保留我原始对象的所有现有属性。

第四步

现在,实际转换我想要的第一个值:

const findValues = when(
  has('values'),
  x => merge(x, {
    availableValues: prop('values', x)
  })
)
map(findValues, arrObj) 
//=> [
//     {myValues: []}, 
//     {
//       myValues: [], 
//       values: [{a: "x", b: "1"}, {a: "y", b: "2"}],
//       availableValues: [{a: "x", b: "1"}, {a: "y", b: "2"}],
//       selectedValues: []
//     }
//   ]
Run Code Online (Sandbox Code Playgroud)

第 5 步

这就是我想要的。所以现在添加另一个属性:

const findValues = when(
  has('values'),
  x => merge(x, {
    availableValues: prop('values', x),
    selectedValues: pluck('a', prop('values', x))
  })
)
map(findValues, arrObj) 
//=> [
//     {myValues: []}, 
//     {
//       myValues: [], 
//       values: [{a: "x", b: "1"}, a: "y", b: "2"}],
//       availableValues: [{a: "x", b" "1"}, {a: "y", b: "2"}], 
//       selectedValues: ["x", "y"]
//     }
//   ]
Run Code Online (Sandbox Code Playgroud)

这最终给出了我想要的结果。

这是一个快速的过程。我可以在 REPL 中非常快速地运行和测试,迭代我的解决方案,直到我得到我想要的东西。