【问题标题】:Haskell: finding tuples of elements that equal nHaskell:查找等于 n 的元素的元组
【发布时间】:2018-04-11 20:01:31
【问题描述】:

我正在尝试在 Haskell 中编写一个函数,该函数接受一个整数列表和一个整数 n,并找到所有等于 n 的元组。到目前为止,我有一个可行的实现

tuplesum :: (Eq b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = [(x1,x2) | x1 <- xs, x2 <- xs, x1 + x2 == n, x1 <= x2]

所以如果我给这个函数输入

tuplesum [5,1,4,0,5,6,9] 10

输出是 [(5,5),(5,5),(1,9),(4,6),(5,5),(5,5)] 但是,我有 4 个 (5,5) 解决方案的副本。我希望函数输出[(5,5),(1,9),(4,6)],但我不知道如何约束具有相同整数的元组而不将其作为解决方案完全删除。

【问题讨论】:

  • 应该x1 &lt;= x2,或者这只是试图执行对称性破坏
  • 从 Data.List 尝试nub
  • 是的,我试图避免重复的对称解
  • 顺便说一句:对于命令式编程,有一个 O(n log n) 算法可以做到这一点。你的是 O(n^2).
  • @Elmex80s:在 Haskell 中也是如此,因为我们使用 HashSets(并且散列集可以函数式编程中实现)。

标签: haskell


【解决方案1】:

元组生成中的对称性破坏

我的印象是您正在寻找一种从列表中选择两个元素的方法,这样x1 总是位于之前 x2

始终让x2 遍历列表其余部分的常用方法是使用tails :: [a] -&gt; [[a]]。对于列表,tails 将生成列表所有尾部的列表,从列表本身开始。例如:

Prelude Data.List> tails [1, 4, 2, 5]
[[1,4,2,5],[4,2,5],[2,5],[5],[]]

我们可以使用它与模式匹配来选择一个元素,并获得对剩余元素的引用。例如:

import Data.List(tails)

tuplesum :: (Eq b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = [(x1,x2) | (x1:x2s) <- tails xs, x2 <- x2s, x1 + x2 == n]

注意这里仍然可以得到重复,例如5会在列表中出现3次,因为在这种情况下x1可以选择第一个5,然后x2可以选择第二个5 以及最后一个。为此,我们可以使用像 nub :: Eq a =&gt; [a] -&gt; [a] 这样的 uniqness 过滤器:

import Data.List(nub, tails)

tuplesum :: (Eq b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = nub [(x1,x2) | (x1:x2s) <- tails xs, x2 <- x2s, x1 + x2 == n]

请注意,这里最好使用tails,因为它会提高性能,因为我们首先会生成少量的重复项。

使用哈希集获取“其他”元素

上面的算法还是O(n2),而且不是很快。然而我们可以反过来解决这个问题:我们可以先构造一个元素的HashSet,然后对于每个元素x1,检查n - x1是否是一个成员,比如:

import Data.Hashable(Hashable)
import Data.HashSet(fromList, member)

tuplesum :: (Ord b, Hashable b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = nub [(x1,x2) | x1 <- xs, let x2 = n-x1, x1 <= x2, member x2 hs]
    where hs = fromList xs

但是由于nub,运行时间仍然是O(n2),但是我们可以在这里使用hashNub :: (Eq a, Hashable a) =&gt; [a] -&gt; [a]

hashNub :: (Eq a, Hashable a) => [a] -> [a]
hashNub = go HashSet.empty
  where
    go _ []     = []
    go s (x:xs) =
      if x `HashSet.member` s
      then go s xs
      else x : go (HashSet.insert x s) xs

然后让它工作:

import Data.Hashable(Hashable)
import Data.HashSet(fromList, member)

tuplesum :: (Ord b, Hashable b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = hashNub [(x1,x2) | x1 <- xs, let x2 = n-x1, x1 <= x2, member x2 hs]
    where hs = fromList xs

现在它可以在 O(n log n) 中运行。

【讨论】:

  • tails 是否应该应用于xs
  • @4castle: 是的,从那时起我们遍历尾部列表(但它从完整列表开始)所以我们可以模式匹配元素x1,而x2s 是列表剩余元素。
【解决方案2】:

我真的很喜欢你的函数tuplesum xs n = [(x1,x2) | x1 &lt;- xs, x2 &lt;- xs, x1 + x2 == n, x1 &lt;= x2],因为它是笛卡尔积,它消除了大多数对称对,否则它们会占一半。它很好地获得了谓词匹配。剩下的唯一问题是重复的元素。直到最近我才忘记了这一点,Pg。 Graham Hutton“Haskell 编程” 和他的 rmdups 函数中的 86 个。我喜欢他的rmdups 的一点是它既不依赖于导入。

rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : filter (/= x) (rmdups xs)

Hutton 的解决方案非常通用且经典递归。我不想在不添加原始内容的情况下在这里发布他的解决方案,所以这里是一个列表理解,以消除任何数据类型(包括元组)的重复。

rmdups ls = [d|(z,d)<- zip [0..] ls, notElem d $ take z ls]

您可以将 rmdups 函数放在您的函数 rmdups.tuplesum 前面 您的函数消除了大多数对称对,因此 rmdups 不会。

rmdups [(5,5),(5,5),(1,9),(4,6),(5,5),(5,5)]

[(5,5),(1,9),(4,6)]

或者

rmdups "abcabcdefdef" OR "abcdefabcdef"

“abcdef”

【讨论】:

    猜你喜欢
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 2018-04-05
    • 2016-02-15
    • 2017-08-04
    • 2013-01-12
    • 2022-01-18
    • 1970-01-01
    相关资源
    最近更新 更多