映射函数应用于列表的特定元素
Map function applied to specific elements of the list
我有一个功能:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal f is xs = helper 0 is xs
where
helper _ [] xs = xs
helper c (i:is) (x:xs)
| c < i = x : helper (c+1) (i:is) xs
| otherwise = f x : helper (c+1) is xs
它是这样工作的:
mapAtOriginal (*2) [0,3] [1,2,3,4] -- == [2,2,3,8]
所以我想重写它,但是使用 map
函数。我知道 map
适用于列表的每个元素,但是,我需要它只适用于特定索引。
我该怎么做?
map
不知道它在“列表中的什么位置”。因此,您首先需要将该信息编码到元素本身中。这可以用 zip [0..]
来完成,它基本上用每个元素出现的位置来注释它。
然后,在你map
的函数中,你只需要对注释元组进行模式匹配,并使用if
来决定是否将操纵器函数应用于其他元组元素。
请注意,zip
和 map
的组合始终等同于单次传递 zipWith
,因此您应该优先使用它。
如果您坚持使用 map
来解决这个问题,一个可能的途径是使用类型为 [(a, Bool)]
的辅助列表,其中只有索引元素与 True
布尔值。
生成这个辅助列表的函数可以这样声明:
markIndexedElements :: [Int] -> [a] -> [(a, Bool)]
如果我们有这样一个函数,剩下的问题就变得简单了:
λ> auxList = markIndexedElements [0,3] [1,2,3,4]
λ> auxList
[(1,True),(2,False),(3,False),(4,True)]
λ>
λ> map (\(x, b) -> if b then (2*x) else x) auxList
[2,2,3,8]
λ>
markIndexedElements
函数从第一个列表中生成第二个列表,同时保留一些 state 信息。所以如果我们更喜欢一个现成的递归方案,这似乎是scanl :: (b -> a -> b) -> b -> [a] -> [b]函数的工作。
要维护的状态主要由原始列表中的当前位置加上到目前为止未使用的索引列表组成。如果当前位置等于下一个索引,我们将删除该索引并输出一个 (x, True) 对。这给出了以下代码:
-- only indexed elements to get paired with a True value
markIndexedElements :: [Int] -> [a] -> [(a, Bool)]
markIndexedElements indices xs =
let sfn ((_,p),(pos,ind)) y = -- stepping function for scanl
if (null ind)
then ((y, False), (pos+1,[]))
else ((y, pos==head ind),
(pos+1, if (pos==head ind) then tail ind else ind))
scanRes = scanl sfn ((undefined,False), (0,indices)) xs
in map fst $ drop 1 scanRes
剩下的代码不难写:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let pairList = markIndexedElements indices xs -- auxiliary list
pfn (x,b) = if b then (fn x) else x -- auxiliary function
in map pfn pairList
main = do
let xs = [1,2,3,4]
indices = [0,3]
result = mapAtOriginal (*2) indices xs
putStrLn $ show (markIndexedElements indices xs)
putStrLn $ show result
程序输出:
[(1,True),(2,False),(3,False),(4,True)]
[2,2,3,8]
类似的解决方案:
如果我们尝试对这个问题再应用一个笛卡尔还原论步骤,我们可以注意到参数 xs
在 markIndexedElements
函数中只起次要作用。一种可能性是完全消除它,取而代之的是 returns 只是一个无限的布尔值列表的函数:
booleansFromIndices :: [Int] -> [Bool]
booleansFromIndices indices =
let sfn (pos,ind) = Just $ -- stepping function for unfoldr
if (null ind)
then ( False, (pos+1, []) )
else ( pos==head ind,
(pos+1, if (pos==head ind) then tail ind else ind))
in unfoldr sfn (0,indices)
结果列表在最后一个索引之后以 False
个值的无限序列结束:
λ> take 10 $ booleansFromIndices [0,3]
[True,False,False,True,False,False,False,False,False,False]
λ>
然后可以使用 booleansFromIndices
:
重写目标 mapAtOriginal
函数
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let pairList = zip xs $ booleansFromIndices indices
pfn (x, b) = if b then (fn x) else x
in map pfn pairList
最后但同样重要的是,正如其他 answerers/commenters 已经指出的,map+zip 方案通常可以替换为基于 zipWith 功能的方案。
像这样,在我们的例子中:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let booleans = booleansFromIndices indices
in zipWith (\x b -> if b then (fn x) else x) xs booleans
从jpmarinier's 的想法出发,填补索引列表中的空白;使用包 data-ordlist
、
{-# LANGUAGE TupleSections #-}
import qualified Data.List.Ordered as O
import Data.Ord (comparing)
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal f is xs =
zipWith (flip ($)) -- apply zippily
xs
(map fst $ -- recover the functions, `f` or `id`
O.unionBy (comparing snd) -- `is` is assumed subset of [0..]
(map (f ,) is ) -- tag with
(map (id ,) [0..]) ) -- indices
这就像 getZipList $ (f `_at` is) `_or` (id `_at` [0..]) <*> ZipList xs
对 _at
和 _or
的一些临时定义。
这利用了 data-ordlist
的 union
是左偏的事实,即它在碰撞时从第一个参数列表中选择元素而不是第二个参数列表中的元素。
我有一个功能:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal f is xs = helper 0 is xs
where
helper _ [] xs = xs
helper c (i:is) (x:xs)
| c < i = x : helper (c+1) (i:is) xs
| otherwise = f x : helper (c+1) is xs
它是这样工作的:
mapAtOriginal (*2) [0,3] [1,2,3,4] -- == [2,2,3,8]
所以我想重写它,但是使用 map
函数。我知道 map
适用于列表的每个元素,但是,我需要它只适用于特定索引。
我该怎么做?
map
不知道它在“列表中的什么位置”。因此,您首先需要将该信息编码到元素本身中。这可以用 zip [0..]
来完成,它基本上用每个元素出现的位置来注释它。
然后,在你map
的函数中,你只需要对注释元组进行模式匹配,并使用if
来决定是否将操纵器函数应用于其他元组元素。
请注意,zip
和 map
的组合始终等同于单次传递 zipWith
,因此您应该优先使用它。
如果您坚持使用 map
来解决这个问题,一个可能的途径是使用类型为 [(a, Bool)]
的辅助列表,其中只有索引元素与 True
布尔值。
生成这个辅助列表的函数可以这样声明:
markIndexedElements :: [Int] -> [a] -> [(a, Bool)]
如果我们有这样一个函数,剩下的问题就变得简单了:
λ> auxList = markIndexedElements [0,3] [1,2,3,4]
λ> auxList
[(1,True),(2,False),(3,False),(4,True)]
λ>
λ> map (\(x, b) -> if b then (2*x) else x) auxList
[2,2,3,8]
λ>
markIndexedElements
函数从第一个列表中生成第二个列表,同时保留一些 state 信息。所以如果我们更喜欢一个现成的递归方案,这似乎是scanl :: (b -> a -> b) -> b -> [a] -> [b]函数的工作。
要维护的状态主要由原始列表中的当前位置加上到目前为止未使用的索引列表组成。如果当前位置等于下一个索引,我们将删除该索引并输出一个 (x, True) 对。这给出了以下代码:
-- only indexed elements to get paired with a True value
markIndexedElements :: [Int] -> [a] -> [(a, Bool)]
markIndexedElements indices xs =
let sfn ((_,p),(pos,ind)) y = -- stepping function for scanl
if (null ind)
then ((y, False), (pos+1,[]))
else ((y, pos==head ind),
(pos+1, if (pos==head ind) then tail ind else ind))
scanRes = scanl sfn ((undefined,False), (0,indices)) xs
in map fst $ drop 1 scanRes
剩下的代码不难写:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let pairList = markIndexedElements indices xs -- auxiliary list
pfn (x,b) = if b then (fn x) else x -- auxiliary function
in map pfn pairList
main = do
let xs = [1,2,3,4]
indices = [0,3]
result = mapAtOriginal (*2) indices xs
putStrLn $ show (markIndexedElements indices xs)
putStrLn $ show result
程序输出:
[(1,True),(2,False),(3,False),(4,True)]
[2,2,3,8]
类似的解决方案:
如果我们尝试对这个问题再应用一个笛卡尔还原论步骤,我们可以注意到参数 xs
在 markIndexedElements
函数中只起次要作用。一种可能性是完全消除它,取而代之的是 returns 只是一个无限的布尔值列表的函数:
booleansFromIndices :: [Int] -> [Bool]
booleansFromIndices indices =
let sfn (pos,ind) = Just $ -- stepping function for unfoldr
if (null ind)
then ( False, (pos+1, []) )
else ( pos==head ind,
(pos+1, if (pos==head ind) then tail ind else ind))
in unfoldr sfn (0,indices)
结果列表在最后一个索引之后以 False
个值的无限序列结束:
λ> take 10 $ booleansFromIndices [0,3]
[True,False,False,True,False,False,False,False,False,False]
λ>
然后可以使用 booleansFromIndices
:
mapAtOriginal
函数
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let pairList = zip xs $ booleansFromIndices indices
pfn (x, b) = if b then (fn x) else x
in map pfn pairList
最后但同样重要的是,正如其他 answerers/commenters 已经指出的,map+zip 方案通常可以替换为基于 zipWith 功能的方案。 像这样,在我们的例子中:
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal fn indices xs =
let booleans = booleansFromIndices indices
in zipWith (\x b -> if b then (fn x) else x) xs booleans
从jpmarinier's data-ordlist
、
{-# LANGUAGE TupleSections #-}
import qualified Data.List.Ordered as O
import Data.Ord (comparing)
mapAtOriginal :: (a -> a) -> [Int] -> [a] -> [a]
mapAtOriginal f is xs =
zipWith (flip ($)) -- apply zippily
xs
(map fst $ -- recover the functions, `f` or `id`
O.unionBy (comparing snd) -- `is` is assumed subset of [0..]
(map (f ,) is ) -- tag with
(map (id ,) [0..]) ) -- indices
这就像 getZipList $ (f `_at` is) `_or` (id `_at` [0..]) <*> ZipList xs
对 _at
和 _or
的一些临时定义。
这利用了 data-ordlist
的 union
是左偏的事实,即它在碰撞时从第一个参数列表中选择元素而不是第二个参数列表中的元素。