如何使用 Ramda 将数组中的空字符串替换为另一个值?

How can I replace empty strings in arrays with another value with Ramda?

空字符串''随机放在多个数组中。 我想用 data.X.

中的值替换 ''
const data = {
  a: ['a', '', 'a', 'a', ''],
  b: ['b', 'b', ''],
  c: ['', '', 'c', 'c'],
  X: ['X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8']
}

我希望它能像下面这样变形。请注意,X1、X2 ... 位于 a b c.

的数组中
const dataTransformed = {
  a: ['a', 'X1', 'a', 'a', 'X2'],
  b: ['b', 'b', 'X3'],
  c: ['X4', 'X5', 'c', 'c']
}

你可以假设有足够的length of X来满足空字符串的数量。 我一直在测试 R.lensProp,但它似乎无法使用多个密钥。

const replaceEmptyString = X => R.map(R.replace(/''/g, X)) 
const setX = R.map(R.over(R.lensProp('a', 'b', 'c'), replaceEmptyString)) 

const replaceStringsLens = R.lens(replaceEmptyString, setX)
console.log (replaceStringsLens(data)) //ERROR

感谢您的指导。 REPL

由于您需要保留 X 中的当前位置,或者事件改变数组,我认为使用 Ramda 这样做没有任何优势。

使用解构将 X 与对象的其余部分分开,并使用扩展对其进行克隆,因此我们不会改变原始对象。

从对象的其余部分,使用 Object.entries()(或 R.toPairs)获取 [key, value] 对的数组,映射这些对,并映射每个对的数组 (v).如果项目为空,使用 Array.shift() 从克隆的 X.

中获取值

使用 Object.fromEntries() 转换回对象。

const fn = ({ X, ...rest }) => {
  const x = [...X] // clone X to avoid mutating the original
  
  return Object.fromEntries( // convert back to object
    Object.entries(rest) // convert to pairs of [key, value]
      .map(([k, v]) => // map the pairs
        // map the values and replace empty values from the cloned X array
        [k, v.map(item => item || x.shift())]
      )
  )
}

const data = {
  a: ['a', '', 'a', 'a', ''],
  b: ['b', 'b', ''],
  c: ['', '', 'c', 'c'],
  X: ['X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8']
}

const result = fn(data)

console.log(result)

正如@ScottSauyet 所指出的,使用 R.map 可以减少冗长:

const fn = ({ X, ...rest }) => {
  const x = [...X] // clone X to avoid mutating the original
  // map the values and replace empty values from the cloned X array
  return R.map(v => v.map(item => item || x.shift()), rest)
}

const data = {
  a: ['a', '', 'a', 'a', ''],
  b: ['b', 'b', ''],
  c: ['', '', 'c', 'c'],
  X: ['X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8']
}

const result = fn(data)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous"></script>

一种有趣的方法是将状态保存在(默认)参数中:

const fillWithX = ({X, ...rest}, i = 0, next = () => X [i++] || '') => 
  R.map (R.map (v => v || next ()), rest)

const data = {
  a: ['a', '', 'a', 'a', ''],
  b: ['b', 'b', ''],
  c: ['', '', 'c', 'c'],
  X: ['X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8']
}

console .log (fillWithX (data))
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

(请注意,|| '' 处理没有足够 X 的情况。您可能需要不同的默认值,或者完全需要一些其他处理。)

Ramda 的 map 在这里大放异彩,尤其是在处理对象方面。我们可以跳过 Ramda,用 Object.entries -> Array.prototype.map -> Object.fromEntries 的舞蹈替换外部的 map,用 Array.prototype.map 的舞蹈替换内部的 map,像这样:

const fillWithX = ({X, ...rest}, i = 0, next = () => X [i++] || '') => 
  Object .fromEntries (
    Object .entries (rest) 
      .map (([k, vs]) => [k, vs .map (a => a || next ())])
  )

但这显然不如 Ramda 方法优雅。


这里有两个问题。首先,默认参数可能存在问题。如果您有三个不同的 data 版本,您将无法成功调用

[data1, data2, data3] .map (fillWithX)

因为 Array.prototype.map 提供给它的回调的 index 参数会覆盖 i 参数到 fillWithX 并且它的 array 参数会覆盖 nextmap 并不是唯一可能发生这种情况的地方。如果我知道我的函数将如何被调用,我通常会选择忽略它。当我需要处理它时,标准技术是编写一个私有助手和一个仅使用所需参数调用它的 public 函数。但是 Ramda 提供 call,有时可以使它更干净。我们可以这样修复它:

const fillWithX = (data) => R.call (
  ({X, ...rest}, i = 0, next = () => X [i++] || '') => 
    R.map (R.map (v => v || next ()), rest),
  data
)

这里我们创建一个新函数,它接受我们的 data 参数并使用上面的函数和 data 参数调用 R.call。这是一个相当普遍的技术。

第二个问题更为根本。我不确定这个功能是否真的有意义。这不是实施;这是基本的想法。问题是出于所有合理的目的,这两个对象是等价的:

const data = {
  a: ['a', '', 'a', 'a', ''],
  b: ['b', 'b', ''],
  c: ['', '', 'c', 'c'],
  X: ['X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8']
}

const data = {
  b: ['b', 'b', ''],
  a: ['a', '', 'a', 'a', ''],
  c: ['', '', 'c', 'c'],
  X: ['X1', 'X2', 'X3', 'X4', 'X5', 'X6', 'X7', 'X8']
}

但是这个函数会为这两个函数产生不同的值,因为键的顺序不同。

如果目标只是用来自 X 的不同值填充所有空洞,那么这不是问题。但是,如果出现在给定孔中的特定 X 值很重要,那么就有一个根本问题。我看到的唯一实用的解决方案是对键进行排序——一个丑陋且有点不直观的过程。

因此,我认为您在使用此想法或该想法的任何其他实现之前可能需要考虑一下这个问题。

使用迭代器

from Thankyou 指出了使用迭代器处理该状态的更简单方法。任何数组都可以通过调用 .values() 变成一个迭代器。这种 built-in 方法绝对更干净:

const fillWithX = ({X, ...rest}, it = X .values()) => 
  R .map (R .map (v => v || it .next () .value || ''), rest)

这仍然是默认参数的问题,如果需要可以用同样的方法解决。当然,它不会改变上述基本问题。

但它确实更干净。