使用函数式编程的方法在map过程中获取前一个元素

4

我有一个数组,我需要对它进行map操作。我需要将当前元素与前一个元素进行比较。我通过比较它们的id检测当前元素是否与上一个元素相同,并根据这个条件执行不同的操作。有没有纯函数式的方法在不进行索引计算的情况下实现这个功能?

items.map((item, index) => {
    if(item.id === items[index - 1 > 0 ? index - 1 : 0].id) {
    // do something
} else {
   // do something else
 }
})

代码可以运行,但我想避免在索引上进行数学运算。有没有办法做到这一点?

2
幸运的是,您在此处没有突变(或重新分配)索引,您只有一个条件表达式。 - CertainPerformance
3
这根本没有改变索引。 - Code Maniac
1
尝试使用 items.reduce((old, new) => {console.log(old === new); return (new)});,但您需要手动将值“映射”到另一个数组(以匹配Array.map()的行为)。 - Ernesto Stifano
变异需要赋值,显式的(例如 index = ...)或隐式的(例如 index++)。 - user5536315
1
它并没有“突变”索引,但触及索引也不是很卫生。为什么这被投票为“不清楚你在问什么”对我来说毫无意义。我投票重新开放这个问题。 - Mulan
显示剩余8条评论
4个回答

1

这不是特别快的实现,但解构赋值使其特别优雅 -

const None =
  Symbol ()

const mapAdjacent = (f, [ a = None, b = None, ...more ] = []) =>
  a === None || b === None
    ? []
    : [ f (a, b), ...mapAdjacent (f, [ b, ...more ]) ]

const pair = (a, b) =>
  [ a, b ]

console.log(mapAdjacent(pair, [ 1, 2, 3 ]))
// [ [ 1, 2 ], [ 2, 3 ] ]

console.log(mapAdjacent(pair, "hello"))
// [ [ h, e ], [ e, l ], [ l, l ], [ l, o ] ]

console.log(mapAdjacent(pair, [ 1 ]))
// []

console.log(mapAdjacent(pair, []))
// []

或者将其编写为生成器 -

const mapAdjacent = function* (f, iter = [])
{ while (iter.length > 1)
  { yield f (...iter.slice(0,2))
    iter = iter.slice(1)
  }
}

const pair = (a, b) =>
  [ a, b ]

console.log(Array.from(mapAdjacent(pair, [ 1, 2, 3 ])))
// [ [ 1, 2 ], [ 2, 3 ] ]

console.log(Array.from(mapAdjacent(pair, "hello")))
// [ [ h, e ], [ e, l ], [ l, l ], [ l, o ] ]

console.log(Array.from(mapAdjacent(pair, [ 1 ])))
// []

console.log(Array.from(mapAdjacent(pair, [])))
// []


1
reduce()函数提供了一个功能,你所需要的:
items.reduce((previousValue, currentValue) => {
  if(currentValue.id === previousValue.id) {
    // do something
  } else {
    // do something else
  }
});

在这个上下文中,“previous value”并不是指“前一个元素”,而是累加器,或者正如文档所说:“由上一次对callbackFn的调用产生的值”。因此,这个答案不仅与问题无关,而且是错误的。 - Augustine Calvino
只需将先前的值设置到累加器中,祝您愉快。我的示例在具体情况下解释了reduce()函数。KISS)) - Jackkobec

0

正如我在评论中提到的那样,我建议使用reduce。这里是一个例子:

const input = [
  {id: 1, value: "Apple Turnover"},
  {id: 1, value: "Apple Turnover"},
  {id: 2, value: "Banana Bread"},
  {id: 3, value: "Chocolate"},
  {id: 3, value: "Chocolate"},
  {id: 3, value: "Chocolate"},
  {id: 1, value: "Apple"},
  {id: 4, value: "Danish"},
];

// Desired output: Array of strings equal to values in the above array,
// but with a prefix string of "New: " or "Repeated: " depending on whether
// the id is repeated or not

const reducer = (accumulator, currentValue) => {
  let previousValue, descriptions, isRepeatedFromPrevious;
  
  if (accumulator) {
    previousValue = accumulator.previousValue;
    descriptions = accumulator.descriptions;
    isRepeatedFromPrevious = previousValue.id === currentValue.id;
  } else {
    descriptions = [];
    isRepeatedFromPrevious = false;
  }
  
  if (isRepeatedFromPrevious) {
    // The following line is not purely functional and performs a mutation,
    // but maybe we do not care because the mutated object did not exist
    // before this reducer ran.
    descriptions.push("Repeated: " + currentValue.value);
  } else {
    // Again, this line is mutative
    descriptions.push("New: " + currentValue.value);
  }
 
  return { previousValue: currentValue, descriptions }
};


const output = input.reduce(reducer, null).descriptions;

document.getElementById('output').innerText = JSON.stringify(output);
<output id=output></output>


0

您确定需要地图吗?这听起来像是一个 XY问题。如果您想要遍历数组中相邻的元素,那么您需要定义自己的函数。

const mapAdjacent = (mapping, array) => {
    const {length} = array, size = length - 1, result = new Array(size);
    for (let i = 0; i < size; i++) result[i] = mapping(array[i], array[i + 1]);
    return result;
};

const items = [1, 2, 3, 4, 5];

const result = mapAdjacent((x, y) => [x, y], items);

console.log(result); // [[1, 2], [2, 3], [3, 4], [4, 5]]

请注意,如果您将空数组作为输入传递给它,这将抛出一个RangeError

const mapAdjacent = (mapping, array) => {
    const {length} = array, size = length - 1, result = new Array(size);
    for (let i = 0; i < size; i++) result[i] = mapping(array[i], array[i + 1]);
    return result;
};

const items = [];

const result = mapAdjacent((x, y) => [x, y], items); // RangeError: Invalid array length

console.log(result);

我认为这是良好的行为,因为您不应该一开始就给mapAdjacent一个空数组。
这是一个使用reduceRight的纯函数实现mapAdjacent的示例。额外的好处是它适用于任何可迭代对象。

const mapAdjacent = (mapping, [head, ...tail]) =>
    tail.reduceRight((recur, item) => prev =>
        [mapping(prev, item), ...recur(item)]
      , _ => [])(head);

const items = "hello";

const result = mapAdjacent((x, y) => [x, y], items);

console.log(result); // [['h', 'e'], ['e', 'l'], ['l', 'l'], ['l', 'o']]

与迭代版本不同,如果您将空数组作为输入,则它返回一个空数组而不是抛出错误。

const mapAdjacent = (mapping, [head, ...tail]) =>
    tail.reduceRight((recur, item) => prev =>
        [mapping(prev, item), ...recur(item)]
      , _ => [])(head);

const items = "";

const result = mapAdjacent((x, y) => [x, y], items);

console.log(result); // []

请注意,这是 JavaScript 中使用剩余元素进行数组解构的意外副作用。等效的 Haskell 版本会引发异常。
mapAdjacent :: (a -> a -> b) -> [a] -> [b]
mapAdjacent f (x:xs) = foldr (\y g x -> f x y : g y) (const []) xs x

main :: IO ()
main = do
    print $ mapAdjacent (,) "hello" -- [('h','e'),('e','l'),('l','l'),('l','o')]
    print $ mapAdjacent (,) "" -- Exception: Non-exhaustive patterns in function mapAdjacent

然而,对于这个函数来说,返回一个空数组可能是可取的。这相当于在Haskell中添加了mapAdjacent f [] = []情况。

感谢您提供 XY 问题的链接。这在实际中是普遍相关的。 - D. Visser

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