我正在寻找一个函数来测试列表元素的谓词,为满足谓词的每个元素创建一个新列表,并仅将函数应用于该元素。
示例:
someFunction :: (a -> Bool) -> (a -> a) -> [a] -> [[a]]
someFunction = ...
let ys = someFunction isOdd (* 2) [1..10]
{- ys == [[2, 2, 3, 4, 5, ...],
[1, 2, 6, 4, 5, ...],
[1, 2, 3, 4, 10, ...],
...] -}
在ys
中,第一个列表等于原始列表,但第一个元素除外,它满足谓词并乘以2
。除了第三个元素之外,第二个列表也等于原始列表,依此类推。
我已经能够通过获取满足谓词的值的索引然后通过索引进行映射来编写这样的函数。然而,这似乎不是很有用,我希望看到一种更惯用的方法。
答案 0 :(得分:9)
您可以从标准或应该是的部分组装此功能。接受的答案有关于拉链的正确线索。我的回答about differentiation and comonads给出了相关操作的一般处理,但让我具体说明一下。
我定义了“带有一个元素孔的列表”的类型,如下所示:
data Bwd x = B0 | Bwd x :< x deriving Show
type HoleyList x = (Bwd x, [x])
严格地说,我不需要引入后向列表来做到这一点,但如果我不得不扭转我的想法,我会很容易混淆。 (恰好HoleyList
是[]
的形式衍生物。)
我现在可以在其上下文中定义什么是列表元素。
type InContext x = (HoleyList x, x)
这个想法是该对的第二个组件属于后向列表和前向列表。我可以定义将列表重新插入的函数(在通用处理中调用upF
。)
plug :: InContext x -> [x]
plug ((B0, xs), y) = y : xs
plug ((xz :< x, xs), y) = plug ((xz, y : xs), x)
我还可以定义一个函数,该函数提供了将列表分开的所有方法(downF
,一般而言)。
selections :: [x] -> [InContext x]
selections = go B0 where
go xz [] = []
go xz (x : xs) = ((xz, xs), x) : go (xz :< x) xs
请注意
map snd (selections xs) = xs
map plug (selections xs) = map (const xs) xs
现在我们很高兴能够关注Bartek的食谱。
selectModify :: (a -> Bool) -> (a -> a) -> [a] -> [[a]]
selectModify p f = map (plug . (id *** f)) . filter (p . snd) . selections
即:通过测试过滤选择,将函数应用于焦点元素,然后重新插入。如果你有拉链设备,它是一个单行,它应该适用于任何可区分的仿函数,而不仅仅是列表!完成工作!
> selectModify ((1 ==) . (`mod` 2)) (2*) [1..10]
[[2,2,3,4,5,6,7,8,9,10]
,[1,2,6,4,5,6,7,8,9,10]
,[1,2,3,4,10,6,7,8,9,10]
,[1,2,3,4,5,6,14,8,9,10]
,[1,2,3,4,5,6,7,8,18,10]]
答案 1 :(得分:5)
怎么样:
从列表开始:
[1,2,3,4]
复制列表n次,n是其大小(:: [[]]
):
[
[1,2,3,4],
[1,2,3,4],
[1,2,3,4],
[1,2,3,4]
]
拆分每个元素的列表(或多或少“对角线”)(:: [([], [])]
):
[
([],[1,2,3,4]),
([1],[2,3,4]),
([1,2],[3,4]),
([1,2,3],[4])
]
过滤掉head . snd
不符合谓词的行
[
([], [1,2,3,4]),
([1,2], [3,4])
]
在剩下的头上应用你的功能
[
([], [2,2,3,4])
([1,2], [6,4]),
]
将这些对连接起来
[
[2,2,3,4],
[1,2,6,4]
]
答案 2 :(得分:4)
你可以用手指(比如zipper:D你用手指移动每个项目:D,就像你读的那样)
someFunction :: (a -> Bool) -> (a -> a) -> [a] -> [[a]]
someFunction check f xs = r [] xs
where r _ [] = []
r ps (y:ys) = let rs = r (ps ++ [y]) ys
in if check y then [ps ++ [f y] ++ ys] ++ rs
else rs
r
函数执行ps
“已处理元素”和(y:ys)
“待处理元素”。
如果您需要线性成本(ps ++ [y]
操作,请使用有效的尾部插入结构。)
使用splitAt
即可编写
someFunction check f xs = map (\(a,(x:b)) -> a ++ [f x] ++ b) $
filter (check.head.snd)
[splitAt n xs | n <- [0..length xs - 1]]
或使用列表理解
someFunction check f xs =
[ a ++ [f x] ++ b | n <- [0..length xs - 1]
, let (a, (x:b)) = splitAt n xs
, check x]
使用@chi建议的zip
解决方案采用线性成本(生成列表,最后是O(n ^ 2))
someFunction check f xs =
[ a ++ [f x] ++ b | (a, (x:b)) <- init $ zip (inits xs) (tails xs)
, check x]
最后(?)@ØrjanJohansen请注意删除init $
(我留下两个版本,我认为这是一个很好的例子)
避免init $
someFunction check f xs =
[ a ++ [f x] ++ b | (a, (x:b)) <- zip (inits xs) (tails xs)
, check x]
列表理解避免了最后(xs, [])
“压缩”元素,@ØrjanJohansen指出here如何翻译
[e | p <- l, Q] = let ok p = [e | Q]
ok _ = []
in concatMap ok l
(thx @WillNess)
答案 3 :(得分:4)
仔细查看这里发布的所有优秀且有些花哨的解决方案(其中包括@ josejuan的最后一个zip
- 基本上是我用来匆忙使用的成语),我不禁感慨list缺少真正的 direct ,但仍然使用显式的,懒惰的递归惯用解决方案 - 如果someFunction
是标准函数,那么您可能会在标准库中看到这种代码。所以这是一个版本(包括go
工人包装你也看到了):
someFunction :: (a -> Bool) -> (a -> a) -> [a] -> [[a]]
someFunction p f xs = go xs where
go [] = []
go (x:xs)
| p x = (f x : xs) : rest
| otherwise = rest
where
rest = map (x :) (go xs)