如何使用镜头遍历并分配给Map中的某些(但不是全部)元素

时间:2015-04-28 15:58:05

标签: haskell lenses

我一直在尝试使用镜头和容器并获得一些成功,但我已经达到了理解使用Data.Map的过滤遍历的理解限制 - 我可以更改地图中的单个实例或遍历所有实例但我无法弄清楚如何对某些可识别的分区(即范围内的键)采取行动。

基本上我正在尝试用类似于Gabriel Gonzalez优秀镜片教程的地图做类似的事情[1]

这是我的代码的工作框架,其中包含traverseSome函数,我不知道如何编写注释掉的函数。任何帮助都感激不尽!

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric   #-}
{-# LANGUAGE RankNTypes      #-}

import Control.Lens
import Control.Monad.State
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set

type CharSet = Set.Set Char
type MapOfSets = Map.Map Int CharSet

data DB = DB { _mos  :: MapOfSets } deriving (Show, Eq)

makeLenses ''DB

initDB :: DB
initDB =  DB { _mos  = Map.fromList (zip [1..5] (repeat Set.empty)) }

add2Map :: Int -> CharSet -> State DB ()
add2Map i cs = mos.ix i %= (Set.union cs)

traverseAll :: Traversal' DB CharSet
traverseAll = mos.traversed

add2MapsAll :: CharSet -> State DB ()
add2MapsAll cs = traverseAll %= (Set.union cs)

--        <problematic part>          
{-
traverseSome :: [Int] -> Int -> Traversal' DB MapOfSets
traverseSome ids i = _

add2MapsSome :: [Int] -> CharSet -> State DB ()
add2MapsSome ids cs = mos.(traverseSome ids 2) %= (Set.union cs)
-}         
--        </problematic part>

main :: IO ()
main = do
  let db = initDB
  let bar = Set.fromList ['a'..'g'] :: CharSet
  let baz = Set.fromList ['f'..'m'] :: CharSet
  let quux = Set.fromList ['n'..'z'] :: CharSet

  let db2 = execState (add2Map 5 bar) db
  let db3 = execState (add2MapsAll baz) db
  -- let db4 = execState (add2MapsSome [1,3] quux) db

  print db2
  print db3
  -- print db4

[1] http://www.haskellforall.com/2013/05/program-imperatively-using-haskell.html

2 个答案:

答案 0 :(得分:1)

我假设你的意思

traverseSome :: [Int] -> Traversal' DB CharSet

这是一个更通用的版本

keys :: Ord k => [k] -> IndexedTraversal' k (Map.Map k a) a
keys ks f m = go ks <&> \m' -> foldr (uncurry M.insert) m m'
  where
    go []     = pure []
    go (i:is) = case Map.lookup i m of
                  Just a  -> (:) . (,) i <$> indexed f i a <*> go is
                  Nothing -> go is

ordinals的{​​{1}}非常相似(我的版本没有重复,所以请确保列表没有重复)。 Data.Vector.Lens遍历索引列表并在地图中查找它们,随时添加索引。 go位遍历已编辑元素的列表,并foldr将它们返回到原始地图。

您可以将自己的内容写成

inserts

如果你确实想要

traverseSome :: [Int] -> IndexedTraversal' Int DB CharSet
traverseSome is = mos . keys is

add2MapsSome :: [Int] -> CharSet -> State DB ()
add2MapsSome is cs = traverseSome is %= Set.union cs

这可以写成(注意你不应该向traverseSome :: [Int] -> Lens' DB MapOfSets 添加新密钥,否则你将违反镜头规定)

Map

可用于编写submap :: Ord k => [k] -> Lens' (Map.Map k a) (Map.Map k a) submap ks f m = f (Map.fromList as) <&> (<> m) where as = Maybe.mapMaybe (\i -> (,) i <$> Map.lookup i m) ks (但效率较低,因为您创建了中间keys):

Map

编辑:没有中间列表的版本:

keys :: Ord k => [k] -> IndexedTraversal' k (Map k a) a
keys ks = submap ks . itraversed

答案 1 :(得分:1)

地图是instance of TraversableWithIndex,因此您可以使用traverseSome :: [Int] -> Traversal' DB CharSet traverseSome ids = mos . itraversed . indices (`Set.member` idSet) where idSet = Set.fromList ids 来遍历密钥。 indices可用于缩小键的范围。

itraversed

请注意,此处traversedtraversed不同。 itraversed始终由元素的序号位置编制索引,而For Each dr As DataRow In dt.Rows ' here i want the row number (row_number) of the row being processed ' so that i can update some values in the previous row of datatable dt ' something like dt(row_number-1)(0) = 50 Next 可以根据数据结构由各种键类型编制索引。