Haskell:检查列表中的第一个元素是否重复

Haskell:检查列表中的第一个元素是否重复,haskell,recursion,Haskell,Recursion,我正在编写一个名为threeN的递归函数,它接受一个包含一个元素的列表,并根据它是偶数还是奇数添加一个新数字。如果是偶数,除以2,如果是奇数,乘以3,减去1。我的基本情况有问题,它会检查列表,看看列表中是否已经包含元素 应该是这样的: prelude> threeN [9] [14,5,10,20,7,14,28,56,19,38,13,26,9] Prelude> :t calcNext calcNext :: Integral a => a -> a Prelud

我正在编写一个名为threeN的递归函数,它接受一个包含一个元素的列表,并根据它是偶数还是奇数添加一个新数字。如果是偶数,除以2,如果是奇数,乘以3,减去1。我的基本情况有问题,它会检查列表,看看列表中是否已经包含元素

应该是这样的:

prelude> threeN [9]
[14,5,10,20,7,14,28,56,19,38,13,26,9]
Prelude> :t calcNext
calcNext :: Integral a => a -> a
Prelude> take 10 $ iterate calcNext 9
[9,26,13,38,19,56,28,14,7,20]
Prelude> :t calcNextAcc
calcNextAcc :: Integral a => (a, [a]) -> (a, [a])
但我得到的却是:

prelude> threeN [9]
[9]
这是我目前的代码:

threeN :: [Integer] -> [Integer]
threeN [] = []
threeN [n]
    | n `elem` [n] = [n]
    | n `mod` 2 ==0 = threeN[n `div` 2] ++ [n]
    | n `mod` 2 ==1 = threeN[(3*n)-1] ++ [n]
这应该是用基本函数来完成的,所以我不能用非常高级的函数来解决这个问题,这就是我遇到问题的原因

为什么签名是[Integer]->[Integer]?输入实际上只是一个数字。下面的代码可以工作

threeN :: Integer -> [Integer]
threeN n = threeN' n []
  where threeN' n acc 
          | n `elem` acc = n:acc
          | even n = threeN' (n `div` 2) (n:acc)
          | odd n = threeN' (3 * n - 1) (n:acc)
如果强制使用[Integer]作为输入签名:

threeN :: [Integer] -> [Integer]
threeN [n] = threeN' n []
  where threeN' n acc 
          | n `elem` acc = n:acc
          | even n = threeN' (n `div` 2) (n:acc)
          | odd n = threeN' (3 * n - 1) (n:acc)
但我认为这没有道理

问候

为什么签名是[Integer]->[Integer]?输入实际上只是一个数字。下面的代码可以工作

threeN :: Integer -> [Integer]
threeN n = threeN' n []
  where threeN' n acc 
          | n `elem` acc = n:acc
          | even n = threeN' (n `div` 2) (n:acc)
          | odd n = threeN' (3 * n - 1) (n:acc)
如果强制使用[Integer]作为输入签名:

threeN :: [Integer] -> [Integer]
threeN [n] = threeN' n []
  where threeN' n acc 
          | n `elem` acc = n:acc
          | even n = threeN' (n `div` 2) (n:acc)
          | odd n = threeN' (3 * n - 1) (n:acc)
但我认为这没有道理


问候

即使您只能使用基本函数,但如果您可以将问题分解为更小的问题,仍然会有所帮助。根据OP和评论线程,似乎必须满足以下三个要求:

根据以前的数字计算一个数字 生成一个列表,其中下一个元素基于上一个元素 遇到重复项时停止生成 我建议独立解决这些问题,然后结合解决方案

计算下一个数字 乍一看,这似乎是问题的核心。在这里,您需要检测数字是偶数还是奇数,并相应地计算结果。将其实现为一个可以调用(比如)calcNext的函数。它需要有如下类型:

prelude> threeN [9]
[14,5,10,20,7,14,28,56,19,38,13,26,9]
Prelude> :t calcNext
calcNext :: Integral a => a -> a
Prelude> take 10 $ iterate calcNext 9
[9,26,13,38,19,56,28,14,7,20]
Prelude> :t calcNextAcc
calcNextAcc :: Integral a => (a, [a]) -> (a, [a])
使用它,您可以根据任何输入计算下一个数字:

Prelude> calcNext 9 
26
Prelude> calcNext 26
13
Prelude> calcNext 13
38
Prelude> calcNext 38
19
我将把calcNext的实现留作练习

生成一个列表 有时,查找现有函数可能会有所帮助。也许已经有一个函数可以用来从calcNext这样的函数生成列表

寻找此类函数的一种方法是使用

猜测并在Hoogle上的搜索字段中键入a->a->[a]将产生大量结果,其中是iterate,它的类型与a->a->a->[a]相似但不完全相同。这似乎已经足够接近了,因为它使您能够像这样开始列表生成:

prelude> threeN [9]
[14,5,10,20,7,14,28,56,19,38,13,26,9]
Prelude> :t calcNext
calcNext :: Integral a => a -> a
Prelude> take 10 $ iterate calcNext 9
[9,26,13,38,19,56,28,14,7,20]
Prelude> :t calcNextAcc
calcNextAcc :: Integral a => (a, [a]) -> (a, [a])
这里我选择了10,因为迭代将永远持续下去

与OP相比,这里的顺序是颠倒的,但是如果顺序重要的话,我将把它作为另一个反转有限列表的练习

如果您想完全基于自己编写的函数来创建解决方案,可以尝试实现自己的迭代函数。如果您需要提示,那么您可以随时查看内置迭代函数的源代码—它是开源的

重复时停止 这可能是最棘手的部分,因为您需要“记住”前面的所有元素。您可以做的是创建一个函数,该函数不仅计算下一个元素,而且还保留所有以前元素的列表。您可以围绕calcNext编写一个包装函数,其类型如下:

prelude> threeN [9]
[14,5,10,20,7,14,28,56,19,38,13,26,9]
Prelude> :t calcNext
calcNext :: Integral a => a -> a
Prelude> take 10 $ iterate calcNext 9
[9,26,13,38,19,56,28,14,7,20]
Prelude> :t calcNextAcc
calcNextAcc :: Integral a => (a, [a]) -> (a, [a])
由于此函数仍然返回与其输入相同的类型,因此您仍然可以将其与iterate一起使用:

请注意,每个元组的第二个元素如何包含迄今为止计算的所有元素。现在你只需要去寻找复制品

例如,您可以使用以下类型编写名为containsDuplicates的函数:

Prelude> :t containsDuplicates
containsDuplicates :: Eq a => [a] -> Bool
这样,您就可以使用内置takeWhile之类的函数进行迭代,直到找到重复的:

Prelude> takeWhile (not . containsDuplicates . snd) $ iterate calcNextAcc (9, [])
[(9,[]),(26,[9]),(13,[26,9]),(38,[13,26,9]),(19,[38,13,26,9]),(56,[19,38,13,26,9]),
 (28,[56,19,38,13,26,9]),(14,[28,56,19,38,13,26,9]),(7,[14,28,56,19,38,13,26,9]),
 (20,[7,14,28,56,19,38,13,26,9]),(10,[20,7,14,28,56,19,38,13,26,9]),
 (5,[10,20,7,14,28,56,19,38,13,26,9]),(14,[5,10,20,7,14,28,56,19,38,13,26,9])]
最后一个元素包含您的解决方案:

Prelude> last $ takeWhile (not . containsDuplicates . snd) $ iterate calcNextAcc (9, [])
(14,[5,10,20,7,14,28,56,19,38,13,26,9])
您可以轻松地将该元组转换为列表:

Prelude> uncurry (:) $ last $ takeWhile (not . containsDuplicates . snd) $ iterate calcNextAcc (9, [])
[14,5,10,20,7,14,28,56,19,38,13,26,9]

我把各种函数的实现留作练习。

即使您只能使用基本函数,但如果您可以将问题分解为更小的问题,它仍然会有所帮助。根据OP和评论线程,似乎必须满足以下三个要求:

根据以前的数字计算一个数字 生成一个列表,其中下一个元素基于上一个元素 遇到重复项时停止生成 我建议独立解决这些问题,然后结合解决方案

计算下一个数字 乍一看,这似乎是问题的核心。在这里,您需要检测数字是偶数还是奇数,并相应地计算结果。将其实现为一个可以调用(比如)calcNext的函数。它需要有如下类型:

prelude> threeN [9]
[14,5,10,20,7,14,28,56,19,38,13,26,9]
Prelude> :t calcNext
calcNext :: Integral a => a -> a
Prelude> take 10 $ iterate calcNext 9
[9,26,13,38,19,56,28,14,7,20]
Prelude> :t calcNextAcc
calcNextAcc :: Integral a => (a, [a]) -> (a, [a])
使用它,您可以根据任何输入计算下一个数字:

Prelude> calcNext 9 
26
Prelude> calcNext 26
13
Prelude> calcNext 13
38
Prelude> calcNext 38
19
我将把calcNext的实现留作练习

生成一个列表 有时,查找现有函数可能会有所帮助。可能 已经有一个函数可以用来从calcNext之类的函数生成列表

寻找此类函数的一种方法是使用

猜测并在Hoogle上的搜索字段中键入a->a->[a]将产生大量结果,其中是iterate,它的类型与a->a->a->[a]相似但不完全相同。这似乎已经足够接近了,因为它使您能够像这样开始列表生成:

prelude> threeN [9]
[14,5,10,20,7,14,28,56,19,38,13,26,9]
Prelude> :t calcNext
calcNext :: Integral a => a -> a
Prelude> take 10 $ iterate calcNext 9
[9,26,13,38,19,56,28,14,7,20]
Prelude> :t calcNextAcc
calcNextAcc :: Integral a => (a, [a]) -> (a, [a])
这里我选择了10,因为迭代将永远持续下去

与OP相比,这里的顺序是颠倒的,但是如果顺序重要的话,我将把它作为另一个反转有限列表的练习

如果您想完全基于自己编写的函数来创建解决方案,可以尝试实现自己的迭代函数。如果您需要提示,那么您可以随时查看内置迭代函数的源代码—它是开源的

重复时停止 这可能是最棘手的部分,因为您需要“记住”前面的所有元素。您可以做的是创建一个函数,该函数不仅计算下一个元素,而且还保留所有以前元素的列表。您可以围绕calcNext编写一个包装函数,其类型如下:

prelude> threeN [9]
[14,5,10,20,7,14,28,56,19,38,13,26,9]
Prelude> :t calcNext
calcNext :: Integral a => a -> a
Prelude> take 10 $ iterate calcNext 9
[9,26,13,38,19,56,28,14,7,20]
Prelude> :t calcNextAcc
calcNextAcc :: Integral a => (a, [a]) -> (a, [a])
由于此函数仍然返回与其输入相同的类型,因此您仍然可以将其与iterate一起使用:

请注意,每个元组的第二个元素如何包含迄今为止计算的所有元素。现在你只需要去寻找复制品

例如,您可以使用以下类型编写名为containsDuplicates的函数:

Prelude> :t containsDuplicates
containsDuplicates :: Eq a => [a] -> Bool
这样,您就可以使用内置takeWhile之类的函数进行迭代,直到找到重复的:

Prelude> takeWhile (not . containsDuplicates . snd) $ iterate calcNextAcc (9, [])
[(9,[]),(26,[9]),(13,[26,9]),(38,[13,26,9]),(19,[38,13,26,9]),(56,[19,38,13,26,9]),
 (28,[56,19,38,13,26,9]),(14,[28,56,19,38,13,26,9]),(7,[14,28,56,19,38,13,26,9]),
 (20,[7,14,28,56,19,38,13,26,9]),(10,[20,7,14,28,56,19,38,13,26,9]),
 (5,[10,20,7,14,28,56,19,38,13,26,9]),(14,[5,10,20,7,14,28,56,19,38,13,26,9])]
最后一个元素包含您的解决方案:

Prelude> last $ takeWhile (not . containsDuplicates . snd) $ iterate calcNextAcc (9, [])
(14,[5,10,20,7,14,28,56,19,38,13,26,9])
您可以轻松地将该元组转换为列表:

Prelude> uncurry (:) $ last $ takeWhile (not . containsDuplicates . snd) $ iterate calcNextAcc (9, [])
[14,5,10,20,7,14,28,56,19,38,13,26,9]
我把各种函数的实现留作练习。

您可以使用带有elem的head和tail来测试列表中是否已经存在第一个元素。但是请注意,头部和尾部是不安全的功能。如果给它们一个空列表,它们将崩溃

threeN :: [Integer] -> [Integer]
threeN ns | n `elem` tail ns = ns
          | even n = threeN ([n `div` 2]++ns)
          | odd  n = threeN ([3*n-1]++ns)
  where
    n = head ns
另外,如果您不希望重复编号出现在输出中,那么让第一个保护正好等于尾部ns,而不是ns。可能有一种更有效的算法来生成这些列表,但这只是修改了您提供的内容。

您可以使用head and tail with elem来测试列表中是否已经存在第一个元素。但是请注意,头部和尾部是不安全的功能。如果给它们一个空列表,它们将崩溃

threeN :: [Integer] -> [Integer]
threeN ns | n `elem` tail ns = ns
          | even n = threeN ([n `div` 2]++ns)
          | odd  n = threeN ([3*n-1]++ns)
  where
    n = head ns
另外,如果您不希望重复编号出现在输出中,那么让第一个保护正好等于尾部ns,而不是ns。可能有一种更有效的算法来生成这些列表,但这只是修改了您提供的内容。

threeN使用一个列表:xs,从其中的一个元素开始。它根据xs的head:x的值生成一个新元素x';如果x'在xs中没有出现,则它在x'到xs之前加上前缀

threeN使用一个列表:xs,从其中的单个元素开始。它根据xs的head:x的值生成一个新元素x';如果x'在xs中没有出现,则它在x'到xs之前加上前缀


您的代码与第9行'elem`[9]=[9]匹配,因此它只返回[9]我意识到这就是问题所在,但我遇到的问题是,比较不会返回函数的开头。预期行为的描述与示例有何关系?这个例子看起来像是在使用一个步骤的输出作为下一个步骤的输入,但如果是这样,为什么它在12次迭代后停止?@MarkSeemann这个例子在12次迭代时停止,因为列表14的前面已经在列表中出现过一次。为什么示例列表中倒数第二个元素是26而不是28?9是奇数,所以9*3+1=28?我是否遗漏了什么?您的代码与第9行'elem`[9]=[9]匹配,因此它只返回[9]我意识到这就是问题所在,但我遇到的问题是,比较不会返回函数的开头。预期行为的描述与示例有何关联?这个例子看起来像是在使用一个步骤的输出作为下一个步骤的输入,但如果是这样,为什么它在12次迭代后停止?@MarkSeemann这个例子在12次迭代时停止,因为列表14的前面已经在列表中出现过一次。为什么示例列表中倒数第二个元素是26而不是28?9是奇数,所以9*3+1=28?我错过什么了吗?