如何在Haskell中生成一个固定长度的数字来总结给定的数字

时间:2014-12-09 20:07:56

标签: haskell

我是haskell世界的新手并且想知道,给定1-9之间的任何正整数和位数,如何找到使用该正整数求和的整数的组合提供 Haskell 中的位数。例如, 使用两位数的4可以表示为[[2,2],[3,1]]的列表,使用三位数作为[[1,1,2]]的列表, 使用两位数的5可以表示为[[2,3],[4,1]]的列表,使用三位数作为[[1,1,3],[2,2,1]]的列表

5 个答案:

答案 0 :(得分:3)

假设您想要避免暴力攻击,这可以被视为典型的dynamic-programming问题:

import Data.Array

partitions :: Int -> Int -> [[Int]]
partitions m n = table ! (m, n, 9)
  where
    table   = listArray ((1, 1, 1), (m, n, 9)) l
    l       = [f i j k | i <- [1 .. m], j <- [1 .. n], k <- [1 .. 9]]

    f i 1 k = if i > k `min` 9 then [] else [[i]]
    f i j k = [d : ds | d <- [1 .. k `min` pred i], ds <- table ! (i - d, j - 1, d)]

想法是构造一个三维惰性数组table,其中索引为(i, j, k)的单元格包含正整数ds的所有分区i到列表中从j中抽取的[1 .. k]位数sum ds == i

例如:

> partitions 4 2
[[2,2],[3,1]]

> partitions 4 3
[[2,1,1]]

> partitions 5 2
[[3,2],[4,1]]

> partitions 5 3
[[2,2,1],[3,1,1]]

答案 1 :(得分:2)

如果你真的不想这个问题,你真的应该因为动态编程是好脑食,那么你可以让计算机代表你聪明。例如,您可以使用一个名为SMT求解器的工具,sbv包可让您轻松访问该工具。

在SBV中编码分区

求解器的一大优势是你只需要表达问题而不是解决方案。在这种情况下,让我们声明一些整数(由len标识),这些整数是与已知结果(1..9)相加的值sumVal

intPartitions :: Int -> Int -> IO AllSatResult
intPartitions sumVal len = allSat $ do
    xs <- mapM exists [show i | i <- [1..len]] :: Symbolic [SWord32]
    mapM (constrain . (.< 10)) xs
    mapM (constrain . (.> 0)) xs
    return $ sum xs .== fromIntegral sumVal

调用此函数非常简单,我们只需导入正确的库并打印出所谓的令人满意的&#34;模型&#34;对于我们的问题:

import Data.SBV
import Data.List (nub,sort)

main = do
    res <- intPartitions 5 3 
    print (nub (map sort (extractModels res :: [[Word32]])))

注意我排序并删除了重复的解决方案,因为您似乎并不关心[1,1,3][3,1,1]等都是解决方案 - 您只需要对结果分配进行一种排列。

对于这些硬编码值,我们得到以下结果:

[[1,1,3],[1,2,2]]

答案 2 :(得分:0)

诀窍就是一个简单的蛮力:

import Data.List
import Control.Monad

sums :: Int -> Int -> [[Int]]
sums number count = nub . map sort . filter ((==number) . sum) $ replicateM count [1..number+1-count]

请注意,非常效率低下。 nub . map sort的使用仅通过删除双重元素来缩短结果。

答案 3 :(得分:0)

这通常通过使用动态编程来解决,以避免重新计算常见的子问题。但这不是最重要的问题:你需要从提出递归算法开始!一旦解决了这个问题,您将有足够的时间考虑制定有效的解决方案。因此,这个答案有两个步骤。没有评论的全部要点is available here

我首先给类型命名,因为我对所有Int的问题感到困惑,我认为类型是文档。你可能比我更聪明,不需要所有这些额外的东西。

type Target = Int
type Digits = Int
type MaxInt = Int

现在,强力解决方案:我们已经给出了Digits分区号码的数量,Target号码以及我们可能在此分区中使用的MaxInt

partitionMaxBrute :: Digits -> Target -> MaxInt -> [[Int]]
partitionMaxBrute d t m

如果我们没有剩余数字且目标为零,我们很高兴!

  | d == 0 && t == 0               = [[]]

如果Digits MaxInt的产品小于TargetMaxInt本身小于零,我们就无法成功累积{{ 1}}非零数字! :(

Digits

如果 | d * m < t || m <= 0 = [] 大于MaxInt,那么如果我们想要一个解决方案,我们最好减少Target。将它减少到大于MaxInt + 1 - Target的任何值都没有意义。

Digits

最后,我们可以降低 | t < m = partitionMaxBrute d t (t + 1 - d) (我们不使用该数字)或从MaxInt减去MaxInt并继续(我们至少使用Target一次):

MaxInt

鉴于这个解决方案,我们可以进行强力分区:它是我们开始的 | otherwise = partitionMaxBrute d t (m - 1) ++ fmap (m :) (partitionMaxBrute (d - 1) (t - m) m) MaxInt的那个,这是有意义的,因为我们期待{ {1}}非零数字。

Target + 1 - Digits

现在是记忆的时候了:动态编程正在利用这样一个事实:我们解决的小问题是通过许多不同的路径发现的,我们不需要一遍又一遍地重新计算答案。 memoize包可以轻松实现缓存。我们只需用抽象的递归调用编写相同的函数:

Digits

确保我们缓存值:

partitionBrute :: Digits -> Target -> [[Int]]
partitionBrute d t = partitionMaxBrute d t (t + 1 - d)

答案 4 :(得分:0)

您可以直接生成所有分区:

type Count  = Int
type Max    = Int
type Target = Int

partitions :: Count -> Max -> Target -> [[Int]]
partitions 0 m 0 = [[]]
partitions k m n = do
    let m' = min m (n - k + 1)
    d <- takeWhile (\d -> n <= k * d) [m', m' - 1 .. 1]
    map (d:) $ partitions (k - 1) d (n - d)

很容易检查,没有多余的情况。我们只需要将do替换为redundant $ do,其中redundant

redundant [] = [[]]
redundant xs =  xs

如果partitions (k - 1) d (n - d)返回[],则redundant会从[[]]返回map (d:) $ partitions (k - 1) d (n - d),然后[d]将等于redundant。但输出不会随{{1}}函数而改变,因此所有分区都是直接生成的。

代码非常简单快速,因为您希望生成分区,而不是计算它们。