使用镜头将地图视为键值对?

3

遍历(traverse)镜头(是它一个镜头吗?)允许以逐个值的方式查看映射的键值。例如:

import Data.Map
import Control.Lens

simpleMap :: Map Int Char
simpleMap = fromList [(1, 'a'), (2, 'b')]

nextCharAll :: Map Int Char -> Map Int Char
nextCharAll = traverse %~ succ

下一个字符是fromList [(1, 'b'), (2, 'c')]的simpleMap。
我可以使用什么样的遍历/镜头来做类似以下的操作?
nextIntNextChar :: Map Int Char -> Map Int Char
nextIntNextChar = 
    asKeyValuePairs 
    . (_key %~ (+1)) 
    . (_value %~ succ)

1
这是一个镜头吗? -- traverse是一种遍历。每个镜头都是一种遍历,但并非每种遍历都是镜头。遍历可以达到零个或多个目标,而镜头总是只能达到一个目标。如果你想要一个包括遍历、镜头(以及其他东西)的通用术语,你可以使用"视光器"。 - duplode
3
为什么你要这么麻烦呢?你已经有了Map模块中的mapKeysWith函数,可以解决你的问题。nextIntNextChar = mapKeysWith succ succ - freestyle
3
你为什么要这么麻烦呢?你已经有Map模块中的mapKeysWith函数可以解决你的问题了。nextIntNextChar = mapKeysWith succ succ - undefined
2
此外,在您的情况下,以下方式会更好(就性能而言):nextIntNextChar = fmap succ . mapKeysMonotonic succ - freestyle
2
此外,在您的情况下,这样做会更好(就性能而言):nextIntNextChar = fmap succ . mapKeysMonotonic succ - undefined
显示剩余3条评论
2个回答

2
我不确定你是否喜欢,但其中一种方法是使用Iso来回转换为列表。
nextIntNextChar :: Map Int Char -> Map Int Char
nextIntNextChar = iso toList fromList . traverse %~ bimap (+1) succ

1
这确实有效,尽管值得意识到这不是一个合法的同构,因为 toList . fromList $ [(1, '1'), (1, '2')] 不等于 [(1, '1'), (1, '2')] - Joe
1
这确实有效,尽管值得意识到这不是一个合法的同构,因为 toList . fromList $ [(1, '1'), (1, '2')] 不等于 [(1, '1'), (1, '2')] - undefined
这是一个很好的观点。谢谢你指出来。 - snak

2
lens包提供了处理键值数据结构的机制,适用于需要对键进行读取操作而不是修改键的操作。因此,它对于编写nextIntNextChar(见下文)没有太大帮助,但对于遍历键值对的其他操作可能会有用。
这个功能由Data.Lens.Indexed中的索引光学提供。请注意,这与Data.Lens.At中的atix操作是不同的。后者允许您通过索引关注键值数据结构的元素,而前者允许您将“索引”与特定的遍历或其他光学相关联,并通过镜头操作传递或组合其他索引。
例如,如果您想要一个镜头操作来对偶数Int键索引的Char进行succ操作,您可以编写:
succEvenChars :: Map Int Char -> Map Int Char
succEvenChars m = m & itraversed.indices even %~ succ

在这里,itraversed使用Int键作为索引构建了一个Map的索引遍历。 indices光学选择其索引与特定谓词匹配的元素。 生成的索引遍历可以像通常一样与修改操作%~一起使用。
(警告:使用traversed替代itraversed进行类型检查,但会产生不同的答案。 traversed光学也创建了一个索引遍历,但索引不是来自地图的Int键;它是从零开始按遍历顺序计算元素的Int索引。 对于此示例,第一个元素具有偶数索引0,但具有奇值键1,应用了succ的元素��唯一一个。)
提供了修改索引的操作,但那只是键的副本,因此修改它不会重新对原始数据结构进行键排序。因此,使用索引遍历实现的nextIntNextChar并不是非常令人兴奋。类似于以下内容的东西:
nextIntNextChar :: Map Int Char -> Map Int Char
nextIntNextChar m = fromList (m ^.. reindexed succ itraversed.to succ.withIndex)

这使用`itraversed`构建一个带有键作为索引的索引遍历,将`reindexed succ`应用于此索引遍历以转换索引,使用`to succ`光学来转换值,并使用`withIndex`光学生成键值对。查看(`^..`)结果光学会产生一系列新的键值对,然后可以使用`fromList`将其加载到新的映射中。这是一个`lens`解决方案,没错,但与其他答案和评论中提供的解决方案相比,它在可读性和实用性上并没有明显优势。
请注意,索引光学可以从键中“加载”索引,但索引本身可以保存几乎任何内容,不需要是键,特别是不需要唯一地指定一个值。例如,如果您有来自`Data.Tree`的`Tree`,可以使用`itraversed`使用“默认键”生成一个带索引的遍历。该键是给出树中“路径”的整数索引列表,因此其长度是节点的深度。
使用“重新索引长度”来按深度重新索引遍历,然后可以对同一深度的所有节点执行遍历操作。
import Data.Tree
import Data.Tree.Lens
import Control.Lens

t1 :: Tree Char
t1 = Node 'a' [Node 'b' [Node 'c' [],Node 'd' []], Node 'e' [Node 'f' []], Node 'g' []]

main = do
  let nodesAt depth = reindexed length itraversed . index depth
      printTree = putStrLn . drawTree . fmap (:[])

  print $ t1 ^.. nodesAt 2           -- print nodes at depth 2
  printTree $ t1 & nodesAt 1 .~ 'x'  -- set nodes at depth 1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接