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)列表。但我只需要考虑键 firstName
、lastName
、city
、company
和 position
。有一个包含这些的数组:
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);
注意 props
比 pick(...) -> 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
或换能器。
这是一个使用换能器的解决方案。在这个例子中:
- 仅适用于
a
属性 等于 1
. 的对象
- 在
b
属性 上,添加 10
- 然后选择
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>
我正在使用 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)列表。但我只需要考虑键 firstName
、lastName
、city
、company
和 position
。有一个包含这些的数组:
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);
注意 props
比 pick(...) -> 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
或换能器。
这是一个使用换能器的解决方案。在这个例子中:
- 仅适用于
a
属性 等于1
. 的对象
- 在
b
属性 上,添加10
- 然后选择
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>