【问题标题】:How to compare contents in a list in haskell, to see if they are partially same?如何比较haskell中列表中的内容,看看它们是否部分相同?
【发布时间】:2014-05-31 08:01:59
【问题描述】:

这对我来说非常棘手。

给定一个很长的列表,

[[100,11,1,0,40,1],[100,12,3,1,22,23],[101,11,1,0,45,1],[101,12 ,3,1,28,30],[102,11,1,0,50,1],[102,12,3,1,50,50],[103,11,1,0,50,1 ],[103,12,3,1,50,50],[104,11,1,0,50,25],[104,12,3,1,50,50],[105,11,1 ,0,50,49],[105,12,3,0,30,30],[106,11,1,0,50,49],[106,12,3,0,25,26], [107,11,1,1,33,20],[107,12,3,0,25,26],[108,11,1,1,2,1],[108,12,3,1 ,20,24],[109,11,1,1,2,1],[109,12,3,1,28,31],[110,11,1,0,40,1],[110 ,12,3,1,22,23]..]

现在忽略每个列表的第一个数字,如果两个列表尽管第一个数字相同,例如

[101,11,1,0,50,1]alike[102,11,1,0,50,1]

我们保留后一个列表,直到整个列表都被检查完。理想情况下,结果应该是这样的:

[[102,11,1,0,50,1],[103,12,3,1,50,50]..]

我的想法是用map把第一个数字去掉,用nub和\\去掉所有重复的结果,变成like

[[11,1,0,50,1],[12,3,1,50,50],[12,3,1,50,50],[11,1,0,50,49 ],[12,3,0,25,26],[11,1,1,2,1],[11,1,0,40,1],[12,3,1,22,23], [11,1,0,45,1],[12,3,1,28,30]..]

并使用 Set.isSubsetOf 过滤掉原始列表。

但是,这个想法太复杂且难以实施。由于我是 Haskell 的初学者,有没有更好的方法来排序?或者我可以使用递归函数(尽管仍然需要努力)?

【问题讨论】:

    标签: list haskell set


    【解决方案1】:

    根据您的描述,我认为您想要获取最后列表的列表以拥有每个独特的尾巴。你可以这样做:

    lastUniqueByTail :: Eq a => [[a]] -> [[a]] 
    lastUniqueByTail = reverse . nubBy ((==) `on` tail) . reverse
    

    注意这仅适用于非空列表的有限列表

    您可以在Data.Function 模块中找到on,您可以在Data.List 中找到nubBy

    所以这里是对每个部分的解释,我们将从内到外:

    • ((==) `on` tail)此函数执行两个列表之间的比较,如果它们的尾部相等则确定它们相等(即,它执行相等检查而忽略第一个元素)。

      on 函数在这里发挥了最大的魔力。它具有以下类型签名:

      on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
      

      它本质上定义为(f `on` g) x y = f (g x) (g y),所以替换我们提供的函数,我们得到((==) `on` tail) x y = (tail x) == (tail y)

    • 那么,nubBy 就像 nub 一样,只是您可以为它提供一个比较器来测试相等性。我们给它上面定义的函数,以便它丢弃具有相同尾部的元素。

    • 但是,与nub 一样,nubBy 在它找到的每个等价类中保留first 元素。 (即,如果它在列表中找到两个相等的元素,它总是选择第一个)。我们想要最后一个这样的元素,这就是为什么我们必须先反转(以便最后一个本来相等的元素成为第一个,并保持不变)。
    • 最后,我们在最后反转,让订单恢复到最初的样子。

    【讨论】:

    • 无论如何,在无限列表上执行此操作并没有什么意义。你永远无法确定一个元素是否是最后一个元素,因此永远无法返回任何内容。
    • 这正是它不适用于无限列表的原因;)我只是在明确限制。
    【解决方案2】:

    如果您需要通过合并具有相同尾部的所有项目来压缩列表,请尝试此代码。

    compactList:: [[Int]] -> [[Int]]
    compactList list = reverse $ compactList' list [] 
    
    compactList':: [[Int]] -> [[Int]] -> [[Int]]
    compactList' [] res = res
    compactList' (l:ls) res
        | inList l res
        = compactList' ls res
        | otherwise
        = compactList' ls  (l:res)
    
    inList :: [Int] -> [[Int]] -> Bool
    inList [] _ = False
    inList _ [] = False
    inList val@(x:xs) ((x':xs'):ls)
        | xs == xs'
        = True
        | otherwise
        = inList val ls
    

    如果我们需要“保留后一个列表”(我之前错过了),那么只需更改 reverse 的位置

    compactList list = reverse $ compactList' list []
    

    【讨论】:

    • 谢谢!我认为这段代码可能不太适合我的情况,但它给了我一个很好的思考方法;)
    • 此代码合并相同的项目并保持唯一的尾部。添加另一个函数: import Data.List comList :: [[Int]] -> [[Int]] comList list = list \\ compactList list 帮助我得到答案!谢谢!
    【解决方案3】:

    您可以找到 2 个向量之间的欧几里得距离。例如,如果您有两个列表 [a0, a1, ... , an] 和 [b0, b1, ..., bn],那么它们之间的欧几里得距离的平方为

    sum [ (a - b) ^ 2 | (a, b) <- zip as bs ]
    

    这可以让您了解一个列表与另一个列表的接近。理想情况下,您应该取平方根,但是对于您正在做的事情,我不知道这是否有必要。

    编辑:

    抱歉,我误解了您的问题。根据您的定义,alike 是这样定义的:

    alike as bs = (tail as) == (tail bs)
    

    如果是这样的话,那么像下面这样的东西应该可以解决问题:

    xAll [] = []
    xAll xa@(x:xaa) = a : xAll b
        where
            a = last $ filter (alike x) xa
            b = filter (\m -> not (alike x m)) xa
    

    但不确定这是否是您正在寻找的......

    【讨论】:

    • 抱歉,我不太明白这对我的情况有何帮助。我想我需要使用递归函数吗?
    最近更新 更多