【问题标题】:Haskell - indexing a listHaskell - 索引列表
【发布时间】:2018-05-02 09:09:52
【问题描述】:

我有一个包含 3 个元组项的列表,我想根据第一项为列表编制索引,我已经编写了一个在逻辑上听起来很合理的代码,但出现类型错误,这就是我写的

p>
addIndex [] indexed = indexed
addIndex ((a1,b1,c1):xs) [] 
                    = addIndex xs [(a1,b1,c1,0)]
addIndex ((a1,b1,c1):xs) indexedWIP 
                    = addIndexH ((a1,b1,c1):xs) indexedWIP (last indexedWIP)
addIndexH ((a1,b1,c1):xs) indexedWIP (ax,bx,cx,ix)
                    = if (a1 /= ax) 
                        then (addIndex xs (indexedWIP ++ (a1,b1,c1,(ix+1)))) 
                        else (addIndex xs (indexedWIP ++ (a1,b1,c1,(ix))))

我收到以下类型错误

ERROR file:.\lmaogetrektson.hs:109 - Type error in application
*** Expression     : indexedWIP ++ (a1,b1,c1,ix + 1)
*** Term           : (a1,b1,c1,ix + 1)
*** Type           : (b,c,d,e)
*** Does not match : [a]

【问题讨论】:

  • 这不是一个包含三个元素的列表,而是一个 3 元组。
  • 天哪,伙计,你似乎总是因为非常简单的术语来抨击我,或者当你从未对我的问题提供任何意见时要求我提供额外的服务。将其编辑为元组...
  • 完全没有,但元组和列表之间存在根本区别:您可以使用++ 附加列表,元组始终具有固定数量的项目(已知在编译时间),元素可以有不同的元素,等等。所以你的indexedWIP ++ (...) 不会工作,因为你不能追加元组。
  • 你可能想缩进一点:P
  • @WillemVanOnsem 你的意思是“你不能用++附加元组”。 @Omar 列表和元组的行为确实不同,Willem 并不是要抨击你,而是要指出问题。

标签: list haskell types tuples type-mismatch


【解决方案1】:

让我在每一行检查您的addIndex 的类型:

addIndex :: [a] -> b -> b
addIndex [] indexed = indexed

-- Combined with the above, leads to:
addIndex :: (Num n) => [(a,b,c)] -> [(a,b,c,n)] -> [(a,b,c,n)]
addIndex ((a1,b1,c1):xs) [] = addIndex xs [(a1,b1,c1,0)]

-- This call demands addIndexH satisfies:
addIndexH :: (Num n) => [(a,b,c)] -> [(a,b,c,n)] -> (a,b,c,n) -> [(a,b,c,n)]
-- It's also costly, as last needs to traverse the list
addIndex ((a1,b1,c1):xs) indexedWIP = 
    addIndexH ((a1,b1,c1):xs) indexedWIP (last indexedWIP)

-- /= check matches types of a1 and ax, requiring them to be Eq
addIndexH ((a1,b1,c1):xs) indexedWIP (ax,bx,cx,ix) = 
    if (a1 /= ax) then (addIndex xs (indexedWIP ++ (a1,b1,c1,(ix+1)))) 
    else (addIndex xs (indexedWIP ++ (a1,b1,c1,(ix))))

列表和元组的区别实际上是您在这里遇到的问题。

Prelude> :t (++)
(++) :: [a] -> [a] -> [a]

++ 的两个操作数必须是相同类型的列表。所以我们需要这样的东西:

addIndexH ((a1,b1,c1):xs) indexedWIP (ax,bx,cx,ix) = 
    if (a1 /= ax) then (addIndex xs (indexedWIP ++ [(a1,b1,c1,(ix+1))])) 
    else (addIndex xs (indexedWIP ++ [(a1,b1,c1,(ix))]))

最终结果应该是一个函数,它接受一个 3 元组列表和另一个枚举 4 元组列表,但方式相当迂回。考虑它是如何扩展的:

addIndex [(a,b,c), (x,y,z)] []
addIndex [(x,y,z)] [(a,b,c,0)]
addIndexH [(x,y,z)] [(a,b,c,0)] (a,b,c,0)
addIndex [] ([(a,b,c,0)] ++ [(x,y,z,(0+1))])
([(a,b,c,0)] ++ [(x,y,z,(0+1))])

这是一个相当复杂的过程,而且列表越长越糟糕(我们甚至还没有研究过重复的字段)。

当你遇到重复的字段时,你仍然追加它,只保留新的索引值。这意味着,因为我们只检查了最后一项,所以我们有两个匹配的a 和彼此相邻的索引。该函数可以通过多种方式重写,特别是无需重建每个中间长度的列表并遍历每个元素的增长列表。

【讨论】:

    【解决方案2】:

    我认为你让它变得比必要的复杂。如果我理解正确,您将输入一个三元组列表(a, b, c),并且您想返回一个四元组列表(a, b, c, i),其中i 指定迄今为止不同a 的数量-我们观察到的值。

    因此,我们使用累加器执行某种映射。虽然我们可以在这里使用高阶构造,但让我们在这里的目标是使用递归并添加一个累加器。我们可以先定义一个带签名的辅助函数:

    addIndex' :: (Num n, Eq a) => a -> n -> [(a, b, c)] -> [(a, b, c, n)]
    

    因此,第一个参数是 previous 元素的a-value(我们在这里假设我们已经处理了一个元素)。第二个参数是我们到目前为止观察到的元素数量,第三个元素是我们仍然需要处理的元素列表,结果是 4 元组列表。

    如果列表耗尽,那么我们可以返回空列表,而不管其他变量:

    addIndex' _ _ [] = []
    

    在另一种情况下,我们应该将前一个键ap与当前键a进行比较,如果两者相等,我们返回索引为i的元组作为最后一个元素,然后我们递归具有 same 索引;否则我们增加索引(到i1 = i + 1)。我们每次都在列表的尾部递归:

    addIndex' ap i ((a, b, c): xs) | a == ap = (a, b, c, i) : addIndex' a i xs
                                   | otherwise = (a, b, c, i1) : addIndex' a i1 xs
        where i1 = i + 1
    

    所以我们得到函数:

    addIndex' :: (Num n, Eq a) => a -> n -> [(a, b, c)] -> [(a, b, c, n)]
    addIndex' _ _ [] = []
    addIndex' ap i ((a, b, c): xs) | a == ap = (a, b, c, i) : addIndex' a i xs
                                   | otherwise = (a, b, c, i1) : addIndex' a i1 xs
        where i1 = i + 1
    

    但是现在我们仍然要处理第一个元素。我们知道如果列表为空,我们返回空列表:

    addIndex [] = []
    

    否则,我们将给定列表中索引为0 的第一个元组作为第一个元组返回,然后调用addIndex',将剩余元组和第一个键作为累加器:

    addIndex ((a, b, c): xs) = (a, b, c, 0) : addIndex' a 0 xs
    

    所以我们得到完整的解决方案:

    addIndex :: (Num n, Eq a) => [(a, b, c)] -> [(a, b, c, n)]
    addIndex [] = []
    addIndex ((a, b, c): xs) = (a, b, c, 0) : addIndex' a 0 xs
    
    addIndex' :: (Num n, Eq a) => a -> n -> [(a, b, c)] -> [(a, b, c, n)]
    addIndex' _ _ [] = []
    addIndex' ap i ((a, b, c): xs) | a == ap = (a, b, c, i) : addIndex' a i xs
                                   | otherwise = (a, b, c, i1) : addIndex' a i1 xs
        where i1 = i + 1
    

    然后我们生成例如:

    Prelude> addIndex [('a', 1, 4), ('a', 2, 5), ('b', 1, 3), ('b', 0, 2), ('c', 1, 2)]
    [('a',1,4,0),('a',2,5,0),('b',1,3,1),('b',0,2,1),('c',1,2,2)]
    

    但请注意,我们只查看前一个元素,因此例如,如果 'a' 键出现在 'c' 之后,我们将再次增加计数器:

    Prelude> addIndex [('a', 1, 4), ('a', 2, 5), ('b', 1, 3), ('b', 0, 2), ('c', 1, 2), ('a', 3, 4)]
    [('a',1,4,0),('a',2,5,0),('b',1,3,1),('b',0,2,1),('c',1,2,2),('a',3,4,3)]
    

    此函数将在 linear 时间 O(n) 内运行,而您编写的函数将在 quadratic 时间 O( n2) 因为追加是在线性时间内完成的(以及last 等)。

    【讨论】:

      猜你喜欢
      • 2013-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-21
      • 2012-09-14
      • 2010-12-02
      • 1970-01-01
      • 2022-10-09
      相关资源
      最近更新 更多