Ramda:如何通过currying,过滤和映射来最小化计算资源?

问题描述 投票:3回答:3

我正在使用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和联系人列表,映射在contacts`键上,选择相关的并缩小它们然后返回已过滤的联系人:

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));

正如你所看到的,这段代码很混乱(甚至认为它比命令式更漂亮)。我多次咖喱value =>,感觉不够理想。我也在质疑,这段代码是否只对联系人进行一次迭代,是否有效。

你如何过滤和映射(只选择相关的键+ lowerCase)一个大的联系人列表,而不是迭代两次或更多?有没有办法避免我的currying并写下这个清洁工?

javascript arrays functional-programming currying ramda.js
3个回答
3
投票

这里有几件事要回应。

  • 即使评论有点讽刺,@zerkms也是对的。尝试性能优化没有多大意义,除非您知道代码实际上具有较差的性能,特别是如果它使代码更难编写或维护。
  • 你不要多次咖喱value =>。它只是预先计算,每次过滤列表时,您的值的部分应用会发生一次。
  • 您只能一次迭代您的联系人。但是每个人都在你的字段列表中调用any。如果它找到一个匹配,这个会提前返回,所以计算调用次数并不容易,但它可能是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)也适用于此。


2
投票

你如何过滤和映射(只选择相关的键+ lowerCase)一个大的联系人列表,而不是迭代两次或更多?有没有办法避免我的currying并写下这个清洁工?

如果您需要一次性过滤和转换数据,我不知道如何使用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. b财产,添加10
  3. 然后选择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链只需要返回一个字符串。 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>

1
投票

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>
© www.soinside.com 2019 - 2024. All rights reserved.