【问题标题】:How to restructurize a List in Haskell?如何在 Haskell 中重组列表?
【发布时间】:2010-09-15 14:41:30
【问题描述】:

我有一个这样的列表:(伪符号)

(X,...) -> (X,...) -> (X,...) -> ...
   |          |          |
   V          V          V
(Y,...)    (Y,...)    (Y,...)
   |          |          |
   V          V          V
(Z,...)    (Z,...)    (Z,...)

类型是(Enum a, Bounded a) => [[(a,x)]]。但我需要这样的东西:

(X, ... -> ... -> ... -> ...
   |
   V
(Y, ... -> ... -> ... -> ...
   |
   V
(Z, ... -> ... -> ... -> ...

类型如(Enum a, Bounded a) => [(a,[x])]

x 有任意数量的元素。可以假设,x 的每个成员都是第一个列表的每个子列表中的一个键。

如何将这种转换作为惰性 Haskell 算法进行(不需要对 List 进行完全评估以返回(部分)结果)?

测试数据

见上文,类似这样:

--Input
[[(Foo,1),(Bar,1),(Baz,1)],[(Foo,2),(Bar,2),(Baz,2)],...]

--Output
[(Foo,[1,2,3,...]),(Bar,[1,2,3,...),(Baz,[1,2,3,...])]

我想对数据做什么

我想在这样的函数中使用它:

myFunc :: [(MyEnum,[Int])]
myFunc x@((_,(_:[])):_) = x
myFunc x            = foldTheListRecursively

该函数必须处理大量数据(每个枚举约 10,000 个条目),该列表应该由运行时系统进行垃圾回收(该列表是由程序的另一部分临时构建的)

我的(丑陋的)实现

是这样的,我实现了,但是显然不符合要求,因为列表被遍历了多次:

restructList :: [[(a,x)]] -> [(a,[x])]
resturctList list = (\x -> (x,listFor x)) <$> keys where
  keys = fst <$> head list
  listFor x = snd <$> any ((==x).fst) <$> list

我不在家所以无法测试,所以可能有错误。

【问题讨论】:

  • 你的第一件事应该是 [[(x,a)]] 吗?这有点令人困惑。是枚举和有界的 x 吗?

标签: list haskell transformation


【解决方案1】:

一些示例数据会让您的问题更容易理解。我假设给定的列表如下:

input = [[("foo", 1), ("foo", 2)], [("bar", 3), ("bar", 4)]]

你想得到

output = [("foo",[1,2]), ("bar",[3,4])]

如果是这样,首先想到的是 Data.Map.insertWith。这就像创建一个从键到值的映射,除非值已经存在,您指定的函数将应用于当前值和新值,并插入 result

例如,如果我们写:

import qualified Data.Map as M
step0 = M.insertWith (++) "key" ["value"] M.empty

那么 step0 只是一个将键映射到值的映射。但如果我们再次调用它:

step1 = M.insertWith (++) "key" ["OH HAI"] step0

现在我们有一个从键到["value","OH HAI"] 的映射。这几乎正​​是您想要的,但您想要的是一些 Enum/Boundeds 的列表,而不是字符串列表。

因此,第一步是获取数据的一个“行”,并将其添加到地图中:

import qualified Data.List as L
toMap1 :: M.Map a b -> [(a,b)] -> M.Map a b
toMap1 = L.foldr (λ(k,v) m → M.insertWith (++) k [v] m)

给定input 的第一个元素,从最顶部开始,您会得到:

toMap M.empty (head input)
    ==> [("foo",[1,2])]

现在我们只需要为每一行累积到这张地图中,而不仅仅是第一行。这只是另一个折叠:

toMap2 :: [[(a,b)]] -> Map a b
toMap2 = L.foldr (flip toMap1) M.empty

现在你可以写了:

toMap2 input

然后得到:

fromList [("bar",[3,4]),("foo",[1,2])]

一个简单的M.toList 将其转回一个常规列表,从而产生output

【讨论】:

  • 很好,我没有意识到你可以在这样的haskell代码中使用λ
  • Emacs 为我做这件事;我认为它使所有内容都更易于阅读。
  • 问题是:这个解决方案是否符合要求。
  • 如果您关心效率,最好使用带有 cons 的 insertWith 而不是 append。这留给读者作为练习。
  • @FUZ: That pragma 不会将λ 替换为\
【解决方案2】:

我不是 100% 确定,但从源代码看来 Data.List.transpose 是懒惰的。 http://www.haskell.org/ghc/docs/6.12.2/html/libraries/base-4.2.0.1/src/Data-List.html#transpose 是我的来源。 我认为转置可以帮助您重组指针:

transpose [[1,2,3],[4,5,6],[7,8,9]]
-- results in [[1,4,7],[2,5,8],[3,6,9]]

所以我会想到类似的东西

foo :: [[(a, b)]] -> [(a, [b])]
foo = map (\x -> (fst (head x), map snd x)) . transpose

【讨论】:

  • 我做到了,有效。但我为我的解决方案找到了另一种方法(使用像 foldl (zipWith parseOneEnum) init list 这样的东西折叠整个结构
  • +1:让我的无意义的一面希望像 twoply :: (a-&gt;b, a-&gt;c) -&gt; a -&gt; (b,c) 这样的东西是标准库函数。
  • @rampion:看过箭头吗?
【解决方案3】:

所以我假设您从 q 列表开始,然后将它们映射 (q -> [(k,v)]) 以提取属性值对以给出 [[(k,v)]] 和你想把它变成一个包含属性和所有存在的值的对列表。此外,属性键是有界枚举,因此您可以枚举所有键。

然后你要做的是遍历所有键并选择值

f :: (Enum k, Bounded k) => [[(k,v)]] -> [(k,[v])]
f kvss = map (\k -> (k, map snd $ filter ((eqenum k).fst) $ kvs)) $ enumFromTo minBound maxBound 
  where kvs = concat kvss
        eqenum e1 e2 = (fromEnum e1) == (fromEnum e2)

这是懒惰的;你可以测试一下

data Foo = Foo1 | Foo2
  deriving (Enum, Bounded, Show, Eq)

infd = map (\x -> [(Foo1, 2*x), (Foo2, x*x)]) [1..]

take 5 $ snd $ (f infd) !! 0
take 5 $ snd $ (f infd) !! 1

【讨论】:

    猜你喜欢
    • 2015-12-22
    • 1970-01-01
    • 2019-02-26
    • 2011-01-03
    • 2018-10-13
    • 1970-01-01
    • 1970-01-01
    • 2011-08-22
    • 2013-03-11
    相关资源
    最近更新 更多