我是haskell世界的新手并且想知道,给定1-9之间的任何正整数和位数,如何找到使用该正整数求和的整数的组合提供 Haskell 中的位数。例如,
使用两位数的4可以表示为[[2,2],[3,1]]
的列表,使用三位数作为[[1,1,2]]
的列表,
使用两位数的5可以表示为[[2,3],[4,1]]
的列表,使用三位数作为[[1,1,3],[2,2,1]]
的列表
答案 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
的产品小于Target
或MaxInt
本身小于零,我们就无法成功累积{{ 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}}函数而改变,因此所有分区都是直接生成的。
代码非常简单快速,因为您希望生成分区,而不是计算它们。