Ramda:如何通过柯里化、过滤和映射来最小化计算资源?

Ramda: How to minimize computing resources by currying, filtering and mapping?

我正在使用 Ramda 构建一个 React 应用程序。我还是函数式编程的新手(大约两个月)。

我有这样的联系人列表:

const contacts = [
  {
    id: 1,
    firstName: 'Sven',
    lastName: 'Hillstedt',
    city: 'Aachen',
    company: '',
    position: 'Student',
    group: 'friends',
    tendency: 'maintain'
  },
  {
    id: 2,
    firstName: 'David',
  // ...
];

给定一个字符串,我需要过滤这个(很长,10.000-100.000)列表。但我只需要考虑键 firstNamelastNamecitycompanyposition。有一个包含这些的数组:

const FIRST_NAME = 'firstName';
const LAST_NAME = 'lastName';
const CITY = 'city';
const COMPANY = 'company';
const POSITION = 'position';

export const stringFields = [FIRST_NAME, LAST_NAME, CITY, COMPANY, POSITION];

现在,我使用 Ramda 编写了以下函数,它接受一个 string 和一个联系人列表,映射到联系人键上,选择相关键并将它们小写,然后 returns 过滤后的联系人:

import { any, filter, includes, map, pick, pipe, toLower, values } from 'ramda';

const contactIncludesValue = value =>
  pipe(
    pick(stringFields),
    map(toLower),
    values,
    any(includes(value))
  );

const filterContactsByValue = value => filter(contactIncludesValue(value));

如您所见,此代码很乱(甚至认为它比命令式执行更漂亮)。我 curry value => 很多次,感觉不太理想。我也在质疑,这段代码是否只对联系人进行一次迭代,是否有效。

您将如何过滤和映射(仅选择相关键 + lowerCase)大量联系人列表而不对其进行迭代两次或更多次?有没有办法避免我的柯里化并编写这个清洁器?

这里有几件事要回复。

  • 即使评论有些刻薄,@zerkms 也是对的。除非您知道代码实际上 性能不佳,否则尝试性能优化毫无意义,尤其是当它使代码更难编写或维护时。

  • 您不会多次咖喱 value =>。它仅在前面进行柯里化,每次过滤列表时都会部分应用您的值。

  • 您只迭代联系人一次。但在每个字段中,都会对您的字段列表调用 any。如果找到匹配项,它会提前 return,因此计算调用次数并非易事,但可能是 O(m * n),其中 m 是字段数,n联系人数量

这个版本的代码稍微更简洁一些。您可能会或可能不会发现它更具可读性:

const contactIncludesValue = value =>
  pipe(
    props(stringFields),
    map(toLower),
    any(includes(value))
  );

const filterContactsByValue = pipe(contactIncludesValue, filter);

注意 propspick(...) -> values 更方便,中间 map(toLower) 之后也一样。

How would you filter and map (pick only the relevant keys + lowerCase) a large list of contacts without iterating over it twice or more? Is there a way to avoid my currying and write this cleaner?

如果您需要一次性过滤 AND 转换您的数据,我不明白您如何单独使用 filter 来完成此操作。

例如,这不会保留a并转换它:

const list = [
  {a: 'foo'},
  {b: 'bar'}
];

console.log(

  filter(pipe(map(toUpper), has('a')), list)

);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {filter, pipe, map, toUpper, has} = R;</script>

为此,您需要使用 reduce 或换能器。

这是一个使用换能器的解决方案。在这个例子中:

  1. 仅适用于 a 属性 等于 1.
  2. 的对象
  3. b 属性 上,添加 10
  4. 然后选择b

const list = [
  {a: 1, b: 2},
  {a: 2, b: 20},
  {a: 1, b: 3},
  {a: 2, b: 30},
  {a: 1, b: 4},
  {a: 2, b: 40},
];

console.log(

into([],
  compose(
    filter(propEq('a', 1)),
    map(over(lensProp('b'), add(10))),
    map(pick(['b']))
  ),
  list)
  
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {into, compose, filter, propEq, map, pick, over, lensProp, add} = R;</script>

使用转换器的好处在于,它将生成结果(数组)的逻辑与转换数据的逻辑分离开来。

into([]) 告诉 Ramda 你正在生成一个数组,因此 compose 链中的任何内容都需要附加到它。

into('') 告诉 Ramda 你正在生成一个字符串。您的 compose 链只需要 return 一个字符串。 into 将负责将其连接到最终结果:

const list = [
  {a: 1, b: 2},
  {a: 2, b: 20},
  {a: 1, b: 3},
  {a: 2, b: 30},
  {a: 1, b: 4},
  {a: 2, b: 40},
];

console.log(

into('',
  compose(
    filter(propEq('a', 1)),
    map(over(lensProp('b'), add(10))),
    map(prop('b'))
  ),
  list)
  
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {into, compose, filter, propEq, map, over, lensProp, add, prop} = R;</script>

R.innerJoin 肯定是最简洁的写法,但我不确定它的时间复杂度。

const filter = value => R.innerJoin(
  // you may lowercase, etc... here
  (record, prop) => R.propEq(prop, value, record),
  R.__,
  ['firstName', 'lastName', 'city', 'company', 'position'],
);

const onlySven = filter('Sven');
const onlyGiuseppe = filter('Giuseppe');

const data = [
  {
    id: 1,
    firstName: 'Sven',
    lastName: 'Hillstedt',
    city: 'Aachen',
    company: '',
    position: 'Student',
    group: 'friends',
    tendency: 'maintain'
  },
  // ...
];

console.log('Giuseppe', onlyGiuseppe(data));
console.log('Sven', onlySven(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>