如何 "pass" 链式映射和过滤器之间的额外数据

How to "pass" extra data between chained maps and filters

这主要是出于学术兴趣,因为我设法以一种完全不同的方式解决了它,但是,短篇小说,我想要的是伪代码:

Foreach object in array1
    Find matching otherObject in array2 // must exist and there's only 1
    Find matching record in array3      // must exist and there's only 1
    If record.status !== otherObject.status
        push { otherObject.id, record.status } onto newArray

在我看来,直觉上似乎应该有一种方法可以用 array1.filter(<some function>).map(<some other function> 做一些事情,但我无法在实践中使用它。

这是一个真实的例子。这有效:

function update(records) {
  const filtered = records.filter((mcr) => {
    const match =  at._people.find((atr) => atr.email.toLowerCase() ===
      mcr.email.toLowerCase());

    return (match.subscriberStatus.toLowerCase() !==
      mc.mailingList.find((listEntry) =>
        listEntry.id === mcr.id).status.toLowerCase()
    );
  });

  const toUpdate = filtered.map((mcr) => {
    const match =  at._people.find((atr) => atr.email.toLowerCase() ===
      mcr.email.toLowerCase());

    return ({ 'id': match.id,
              'fields': {'Mailing List Status': mcr.subscriberStatus }
            }
    );
  });
}

但让我恼火的是重复的 const match =。在我看来,如果 at._people 是一个大数组,这些可能会很昂贵。

我天真地尝试过:

function update(records) {
  let match;

  const toUpdate = records.filter((mcr) => {
    match = at._people.find((atr) => atr.email.toLowerCase() ===
      mcr.email.toLowerCase());

    // return isDifferent?
    return (match.subscriberStatus.toLowerCase() !==
      mc.mailingList.find((listEntry) => listEntry.id === mcr.id).status.toLowerCase());
  }).map((foundMcr) => {
    return ({ 'id': match.id, 'fields': {'Mailing List Status': foundMcr.subscriberStatus } })
  });
}

但是(回想起来有点明显)这是行不通的,因为在 map 中,match 永远不会改变 — 它总是在 [=19= 中的最后一件事].关于如何将在 filter 中找到的 match.id 逐项传递给链式 map 的任何想法?或者,真的,还有其他方法可以完成同样的事情吗?

如果这对您有帮助,我会这样做:

let newArray = array1.filter((item) => {
   let matching1 = array2.filter(matchingFunction)  
   let matching2 = array3.filter(matchingFunction) 
   return matching1?.status == matching2?.status; 
})

如果您 使用 .map.filter() 那么您可以避免稍后在链中进行额外的重新计算,如果您执行以下操作(一般步骤):

  1. .map() 将每个项目放入包含的包装器对象中:

    • 数组中的项目
    • 计算您在后续步骤中需要的额外数据
  2. .filter() 基于计算数据的包装器对象。

  3. .map() 剩余的结果变成你想要的形状,在原始项目和任何计算数据上绘制。


在您的情况下,这可能意味着:

  1. 您执行一次查找逻辑。
  2. 使用找到的项目丢弃一些结果。
  3. 使用剩余的内容生成一个新数组。

下面是提取回调的结果,使 map/filter/map 逻辑更清晰:

//takes a record and enriches it with `match` and `mailingStatus`
const wrapWithLookups = mcr => {
    const match =  at._people.find((atr) => atr.email.toLowerCase() ===
      mcr.email.toLowerCase());
    const mailingListStatus = mc.mailingList.find((listEntry) => listEntry.id === mcr.id).status;

    return { match, mailingListStatus , mcr };
};

//filters based on match and mailingListStatus calculated fields
const isCorrectSubscriberStatus = ({match, mailingListStatus}) => 
    match.subscriberStatus.toLowerCase() !== mailingListStatus .toLowerCase();

//converts to a new item based on mcr and match
const toUpdatedRecord = ({match, mcr}) => ({
    'id': match.id,
    'fields': {'Mailing List Status': mcr.subscriberStatus }
});

function update(records) {
    return records
        .map(wrapWithLookups)
        .filter(isCorrectSubscriberStatus)
        .map(toUpdatedRecord);
}

如果以后需要,这将节省 match and/or mailingStatus 的重新计算。但是,它确实在数组中引入了一个全新的循环来收集它们。这可能是一个性能问题,但是,如果您像 Lodash 提供的那样使用惰性求值链,则很容易补救。使用的代码调整为:

function update(records) {
    return _(records) // wrap in a lazy chain evaluator by Lodash ->-+
        .map(wrap)                         // same as before         |
        .filter(isCorrectSubscriberStatus) // same as before         |
        .map(toUpdatedRecord)              // same as before         |
        .value(); // extract the value <-----------------------------+
}

其他图书馆可能采用非常相似的方法。在任何情况下,延迟评估不会 运行 一次通过数组 .map(),然后另一次 .filter(),然后第三次第二次 .map() 而是仅迭代一次和 运行s 适当的操作。


惰性评估可以通过构建在 reduce() 之上的转换器来表达。有关传感器工作原理的示例,请参见:


这样就可以避免所有的.map().filter()的调用,只需做一个组合函数,直接使用.reduce()。但是,我个人发现,如果需要性能,与通过 .map().filter().map() 链表达逻辑然后使用惰性求值相比,更难推理也更难维护。


值得注意的是 map() -> filter() -> map() 逻辑不需要通过惰性链使用。您可以使用像 FP distribution of Lodash or the vanilla Ramda 这样的库,它为您提供通用的 map()filter() 函数,这些函数可以应用于任何列表并相互组合以避免再次重复多次。使用 Lodash FP 这将是:

import map from 'lodash/fp/map';
import filter from 'lodash/fp/filter';
import flow from 'lodash/fp/flow';

function update(records) {
    const process = flow(
        map(wrapWithLookups),
        filter(isCorrectSubscriberStatus),
        map(toUpdatedRecord),
    );
    
    return process(records);
}

对于 Ramda,实现是相同的——map()filter() 在两个库中的行为相同,唯一的区别是组合函数(Ramda 中的 flow() in Lodash) is called pipe。它的作用与 flow():

相同
pipe(
    map(wrapWithLookups),
    filter(isCorrectSubscriberStatus),
    map(toUpdatedRecord),
)

要更深入地了解为什么您可能希望避免链接和此处的替代方法,请参阅有关 Medium.com 的文章:Why using _.chain is a mistake.