我正在使用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
和联系人列表,映射在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并写下这个清洁工?
这里有几件事要回应。
value =>
。它只是预先计算,每次过滤列表时,您的值的部分应用会发生一次。any
。如果它找到一个匹配,这个会提前返回,所以计算调用次数并不容易,但它可能是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)
也适用于此。
你如何过滤和映射(只选择相关的键+ 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
或换能器。
这是使用传感器的解决方案。在这个例子中:
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
链只需要返回一个字符串。 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>