在 Ramda 中有条件地针对另一个 array/string 过滤一个数组
Filter an array against another array/string conditionally in Ramda
我正在练习Ramda,尝试构造一个函数如下:
该函数有两个参数:
userInput: {
query: Array[String] || String,
target: Array[String]
}
目标:
- 过滤目标数组和return满足特定条件的新数组
- 作为新数组的条件应该只包含以给定查询开头的字符串。
例如:
如果目标是:
["pen", "pencil", "paper", "", undefined, True, "books", "paperback"]
查询为:
["pen", "paper"]
那么过滤后的结果应该是:
["pen", "pencil", "paper", "paperback"]
我以 normal/vanilla(?) js 的方式实现了目标。但这不一定是 FP,也不是利用 Ramda。
到目前为止我的实验是这样的:
- 迭代查询数组(如果它是数组,或者只是普通字符串),并针对每个查询检查目标(使用 Ramda 中的
startsWith
);
- return true/false 目标字符串是否满足任何条件(使用 Ramda 中的
any
或 anyPass
);
- 根据上述步骤中的 T/F 值过滤整个数组;
在代码方面,我正在考虑使用 map
或 apply
将 startsWith
函数应用于目标数组的每个元素。到目前为止我只做了这个:
const textStartsWith = curry((query, target) =>
pipe(toString, startsWith(query))(target)
);
但是,我被柯里化函数的组合困在这里。
如有任何帮助,我们将不胜感激!
如果 query
不是数组,将其转换为数组(参见 convertToArray
)。映射 query
,并使用 R.startsWith
创建一组测试。过滤 target
,并使用 R.anyPass
作为谓词:
const { curry, unless, is, of, filter, anyPass, map, startsWith } = R;
const convertToArray = unless(is(Array), of);
const textStartsWith = curry((query, target) =>
filter(anyPass(map(startsWith, convertToArray(query))))(target)
);
const query = ["pen", "paper"];
const target = ["pen", "pencil", "paper", "", "books", "paperback"];
const result = textStartsWith(query, target);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
如果您需要处理非字符串值,我会 return false
用于每个不是字符串的值。请注意,使用 R.toString
转换字符串会转换字符串 - R.toString('abc'); //=> '"abc"'
(请参阅 docs)
const { curry, unless, is, of, filter, ifElse, anyPass, map, startsWith, always } = R;
const convertToArray = unless(is(Array), of);
const textStartsWith = curry((query, target) =>
filter(ifElse(
is(String),
anyPass(map(startsWith, convertToArray(query))),
always(false)
))(target)
);
const query = ["pen", "paper"];
const target = ["pen", "pencil", "paper", "", undefined, true, "books", "paperback"];
const result = textStartsWith(query, target);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
我会像这样组合 startsWith
和 anyPass
:
const textStartsWith = pipe (
map (startsWith),
anyPass,
flip (o) (String),
filter
)
console .log (
textStartsWith
(['pen', 'paper'])
(['pen', 'pencil', 'paper', '', undefined, true, 'books', 'paperback'])
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {pipe, map, startsWith, anyPass, flip, o, filter} = R </script>
如果你想一次性传递参数,你可以把它包起来 uncurry
:
const textStartsWith = uncurryN (2) (pipe (
map (startsWith),
anyPass,
flip (o) (String),
filter
))
textStartsWith (query, target)
我认为这确实指出了 Ramda 中缺少的功能。 Ramda 具有可变 compose
和 pipe
函数,以及柯里化的二进制组合 o
。但是没有等效的咖喱二进制 pipe
.
如果你阅读Haskell
实现这种实现的一种可能方法是创建一个完全柯里化的函数,然后将 Haskell 等价物粘贴到 http://pointfree.io.
所以如果我们从这个函数开始:
const f1 = (query) => (target) => filter (pipe (
String,
anyPass (map( startsWith) (query))
)) (target)
我们可以制作这样的 Haskell 版本:
\query -> \target -> filter ((anyPass ((map startsWith) query)) . string) target
然后 return 是这样的:
filter . (. string) . anyPass . map startsWith
我们可以像上面的第一个答案一样将其转换回 JS,注意 foo . bar
是 foo
和 bar
的组合,而 (. foo)
相当于flip (o) (foo)
或 o (__, foo)
我们可以得到类似上面第一个片段的结果。
更新
用户 Kuncheria 询问了 flip (o) (String)
。浏览一下签名也许会有所帮助。我们将四个函数传递给 pipe.
map (startsWith)
具有签名 [String] -> [(String -> Boolean)]
。它需要一个字符串列表和 return 一个从字符串到布尔的函数列表。
anyPass
具有签名 [(a -> Boolean)] -> (a -> Boolean)
。它需要一些任意类型的函数列表,a
到 Boolean
和 returns 一个从 a
到 Boolean
的函数(这将是 true
恰好当这些函数中的至少一个 return 对于所提供的 a
为真。)
现在我们可以将 map (startsWith)
的输出([(String -> Boolean)]
与 anyPass
的输入结合起来,方法是用 String
代替 a
,如此 pipe (map (startsWith), anyPass))
具有签名 [String] -> (String -> Boolean)
.
flip (o) (String)
是这里最复杂的一个函数,我们下面会进行说明。在那里我们会发现它的类型是 (String -> c) -> (a -> c)
.
现在用 Boolean
代替 c
,我们结合上面的内容可以看到 pipe (map (startsWith), anyPass, flip (o) (String))
具有签名 [String] -> (a -> Boolean)
.
filter
只是具有签名 (a -> Boolean) -> [a] -> [a]
。它接受一个函数,该函数将 a
类型的值转换为布尔值,returns 是一个接受 a
类型值列表和 returns 过滤列表的函数函数 returns true
.
所以结合上面的内容,我们可以注意到我们的主要功能 - pipe (map (startsWith), anyPass, flip (o) (String), filter)
- 具有签名 [String] -> [a] -> [a]
我们可以像这样更紧凑地写上面的讨论:
const textStartsWith = pipe (
map (startsWith), // [String] -> [(String -> Boolean)]
anyPass, // [(a -> Boolean)] -> (a -> Boolean)
// a = String => [String] -> (String -> Boolean)
flip (o) (String), // (String -> c) -> (a -> c)
// c = Boolean => [String] -> (a -> Boolean)
filter // (a -> Boolean) -> [a] -> [a]
// => [String] -> [a] -> [a]
)
但我们还需要讨论flip (o) (String)
。
o
是柯里化二进制 compose
函数,其签名是
o :: (b -> c) -> (a -> b) -> (a -> c)
我们可以flip
它,得到:
flip (o) :: (a -> b) -> (b -> c) -> (a -> c)
现在我们运行进入一个符号问题。我们一直使用 String
来表示 String 类型。但是在 JS 中,String
也是一个函数:从任何值构造一个 String。我们可以认为它是从某种类型 a
到 String 的函数,即类型 a -> String
。所以,因为
flip (o) :: (a -> b) -> (b -> c) -> (a -> c)
我们可以看到:
flip (o) (String)
; ^----------------- Constructor function
flip (o) (a -> String)
; ^------------ Data type
flip (o) (String) :: (String -> c) -> (a -> c)
; ^ ^----- Data type
; +----------------- Constructor function
我们可以将 flip (o) (String)
视为一个函数,它接受一个将字符串转换为类型 c
的函数,而 return 是一个将类型转换为 [=40= 的函数] 变成 c
类型的东西。一个例子是 length
,该函数获取字符串的长度:
const strLength = flip (o) (String) (length)
strLength ('abc') //=> 3 because String ('abc') = 'abc'
strLength (42) //=> 2 because String (42) = '42'
strLength (void 0) //=> 9 because String (void 0) = 'undefined'
strLength ({}) //=> 15 because String ({}) = 'object [Object]'
我正在练习Ramda,尝试构造一个函数如下:
该函数有两个参数:
userInput: {
query: Array[String] || String,
target: Array[String]
}
目标:
- 过滤目标数组和return满足特定条件的新数组
- 作为新数组的条件应该只包含以给定查询开头的字符串。
例如: 如果目标是:
["pen", "pencil", "paper", "", undefined, True, "books", "paperback"]
查询为:
["pen", "paper"]
那么过滤后的结果应该是:
["pen", "pencil", "paper", "paperback"]
我以 normal/vanilla(?) js 的方式实现了目标。但这不一定是 FP,也不是利用 Ramda。
到目前为止我的实验是这样的:
- 迭代查询数组(如果它是数组,或者只是普通字符串),并针对每个查询检查目标(使用 Ramda 中的
startsWith
); - return true/false 目标字符串是否满足任何条件(使用 Ramda 中的
any
或anyPass
); - 根据上述步骤中的 T/F 值过滤整个数组;
在代码方面,我正在考虑使用 map
或 apply
将 startsWith
函数应用于目标数组的每个元素。到目前为止我只做了这个:
const textStartsWith = curry((query, target) =>
pipe(toString, startsWith(query))(target)
);
但是,我被柯里化函数的组合困在这里。
如有任何帮助,我们将不胜感激!
如果 query
不是数组,将其转换为数组(参见 convertToArray
)。映射 query
,并使用 R.startsWith
创建一组测试。过滤 target
,并使用 R.anyPass
作为谓词:
const { curry, unless, is, of, filter, anyPass, map, startsWith } = R;
const convertToArray = unless(is(Array), of);
const textStartsWith = curry((query, target) =>
filter(anyPass(map(startsWith, convertToArray(query))))(target)
);
const query = ["pen", "paper"];
const target = ["pen", "pencil", "paper", "", "books", "paperback"];
const result = textStartsWith(query, target);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
如果您需要处理非字符串值,我会 return false
用于每个不是字符串的值。请注意,使用 R.toString
转换字符串会转换字符串 - R.toString('abc'); //=> '"abc"'
(请参阅 docs)
const { curry, unless, is, of, filter, ifElse, anyPass, map, startsWith, always } = R;
const convertToArray = unless(is(Array), of);
const textStartsWith = curry((query, target) =>
filter(ifElse(
is(String),
anyPass(map(startsWith, convertToArray(query))),
always(false)
))(target)
);
const query = ["pen", "paper"];
const target = ["pen", "pencil", "paper", "", undefined, true, "books", "paperback"];
const result = textStartsWith(query, target);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
我会像这样组合 startsWith
和 anyPass
:
const textStartsWith = pipe (
map (startsWith),
anyPass,
flip (o) (String),
filter
)
console .log (
textStartsWith
(['pen', 'paper'])
(['pen', 'pencil', 'paper', '', undefined, true, 'books', 'paperback'])
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {pipe, map, startsWith, anyPass, flip, o, filter} = R </script>
如果你想一次性传递参数,你可以把它包起来 uncurry
:
const textStartsWith = uncurryN (2) (pipe (
map (startsWith),
anyPass,
flip (o) (String),
filter
))
textStartsWith (query, target)
我认为这确实指出了 Ramda 中缺少的功能。 Ramda 具有可变 compose
和 pipe
函数,以及柯里化的二进制组合 o
。但是没有等效的咖喱二进制 pipe
.
如果你阅读Haskell
实现这种实现的一种可能方法是创建一个完全柯里化的函数,然后将 Haskell 等价物粘贴到 http://pointfree.io.
所以如果我们从这个函数开始:
const f1 = (query) => (target) => filter (pipe (
String,
anyPass (map( startsWith) (query))
)) (target)
我们可以制作这样的 Haskell 版本:
\query -> \target -> filter ((anyPass ((map startsWith) query)) . string) target
然后 return 是这样的:
filter . (. string) . anyPass . map startsWith
我们可以像上面的第一个答案一样将其转换回 JS,注意 foo . bar
是 foo
和 bar
的组合,而 (. foo)
相当于flip (o) (foo)
或 o (__, foo)
我们可以得到类似上面第一个片段的结果。
更新
用户 Kuncheria 询问了 flip (o) (String)
。浏览一下签名也许会有所帮助。我们将四个函数传递给 pipe.
map (startsWith)
具有签名 [String] -> [(String -> Boolean)]
。它需要一个字符串列表和 return 一个从字符串到布尔的函数列表。
anyPass
具有签名 [(a -> Boolean)] -> (a -> Boolean)
。它需要一些任意类型的函数列表,a
到 Boolean
和 returns 一个从 a
到 Boolean
的函数(这将是 true
恰好当这些函数中的至少一个 return 对于所提供的 a
为真。)
现在我们可以将 map (startsWith)
的输出([(String -> Boolean)]
与 anyPass
的输入结合起来,方法是用 String
代替 a
,如此 pipe (map (startsWith), anyPass))
具有签名 [String] -> (String -> Boolean)
.
flip (o) (String)
是这里最复杂的一个函数,我们下面会进行说明。在那里我们会发现它的类型是 (String -> c) -> (a -> c)
.
现在用 Boolean
代替 c
,我们结合上面的内容可以看到 pipe (map (startsWith), anyPass, flip (o) (String))
具有签名 [String] -> (a -> Boolean)
.
filter
只是具有签名 (a -> Boolean) -> [a] -> [a]
。它接受一个函数,该函数将 a
类型的值转换为布尔值,returns 是一个接受 a
类型值列表和 returns 过滤列表的函数函数 returns true
.
所以结合上面的内容,我们可以注意到我们的主要功能 - pipe (map (startsWith), anyPass, flip (o) (String), filter)
- 具有签名 [String] -> [a] -> [a]
我们可以像这样更紧凑地写上面的讨论:
const textStartsWith = pipe (
map (startsWith), // [String] -> [(String -> Boolean)]
anyPass, // [(a -> Boolean)] -> (a -> Boolean)
// a = String => [String] -> (String -> Boolean)
flip (o) (String), // (String -> c) -> (a -> c)
// c = Boolean => [String] -> (a -> Boolean)
filter // (a -> Boolean) -> [a] -> [a]
// => [String] -> [a] -> [a]
)
但我们还需要讨论flip (o) (String)
。
o
是柯里化二进制 compose
函数,其签名是
o :: (b -> c) -> (a -> b) -> (a -> c)
我们可以flip
它,得到:
flip (o) :: (a -> b) -> (b -> c) -> (a -> c)
现在我们运行进入一个符号问题。我们一直使用 String
来表示 String 类型。但是在 JS 中,String
也是一个函数:从任何值构造一个 String。我们可以认为它是从某种类型 a
到 String 的函数,即类型 a -> String
。所以,因为
flip (o) :: (a -> b) -> (b -> c) -> (a -> c)
我们可以看到:
flip (o) (String)
; ^----------------- Constructor function
flip (o) (a -> String)
; ^------------ Data type
flip (o) (String) :: (String -> c) -> (a -> c)
; ^ ^----- Data type
; +----------------- Constructor function
我们可以将 flip (o) (String)
视为一个函数,它接受一个将字符串转换为类型 c
的函数,而 return 是一个将类型转换为 [=40= 的函数] 变成 c
类型的东西。一个例子是 length
,该函数获取字符串的长度:
const strLength = flip (o) (String) (length)
strLength ('abc') //=> 3 because String ('abc') = 'abc'
strLength (42) //=> 2 because String (42) = '42'
strLength (void 0) //=> 9 because String (void 0) = 'undefined'
strLength ({}) //=> 15 because String ({}) = 'object [Object]'