简体   繁体   English

在 Haskell 中使用列表 Monad 回溯

[英]Backtraking with list Monad in Haskell

I'm trying to solve a decomposition problem with backtracking and list Monad in Haskell.我正在尝试使用 Haskell 中的回溯和列表 Monad 来解决分解问题 Here is the problem statement: given a positive integer n , find all lists of consecutive integers (in range i..j ) whose sum is equal to n .下面是问题陈述:给定一个正数 integer n ,找出总和等于n的所有连续整数列表(在i..j范围内)。

I came out with the following solution which seems to work fine.我提出了以下似乎工作正常的解决方案。 Could someone suggest a better/more efficient implementation using list Monad and backtracking?有人可以建议使用列表 Monad 和回溯更好/更有效的实现吗?

Any suggestions are welcome.欢迎任何建议。 Thanks in advance.提前致谢。

import Control.Monad

decompose :: Int -> [[Int]]
decompose n = concatMap (run n) [1 .. n - 1]
  where
    run target n = do
        x <- [n]
        guard $ x <= target
        if x == target
            then return [x]
            else do
                next <- run (target - n) (n + 1)
                return $ x : next

test1 = decompose 10 == [[1,2,3,4]]
test2 = decompose 9 == [[2,3,4],[4,5]]

The sum of a range of numbers k.. l with k≤l is equal to (l×(l+1)-k×(k-1))/2 . k≤l的一系列数字k.. l的总和等于(l×(l+1)-k×(k-1))/2 For example: 1.. 4 is equal to (4×5-1×0)/2=(20-0)/2=10 ;例如: 1.. 4等于(4×5-1×0)/2=(20-0)/2=10 and the sum of 4.. 5 is (5×6-4×3)/2=(30-12)/2=9 .并且4.. 5的总和是(5×6-4×3)/2=(30-12)/2=9

If we have a sum S and an offset k , we can thus find out if there is an l for which the sum holds with:如果我们有一个总和S和一个偏移量k ,那么我们可以找出是否存在一个总和成立的l

2×S = l×(l+1)-k×(k-1) 2×S = l×(l+1)-k×(k-1)

0=l 2 +l-2×Sk×(k-1) 0=l 2 +l-2×Sk×(k-1)

we can thus solve this equation with:因此,我们可以用以下方法求解这个方程:

l=(-1 + √(1+8×S+4×k×(k-1)))/2 l=(-1 + √(1+8×S+4×k×(k-1)))/2

If this is an integral number, then the sequence exists.如果这是一个整数,则该序列存在。 For example for S=9 and k=4 , we get:例如对于S=9k=4 ,我们得到:

l = (-1 + √(1+72+48))/2 = (-1 + 11)/2 = 10/2 = 5 . l = (-1 + √(1+72+48))/2 = (-1 + 11)/2 = 10/2 = 5

We can make use of some function, like the Babylonian method [wiki] to calculate integer square roots fast:我们可以利用一些 function,如巴比伦方法[wiki]快速计算 integer 平方根:

squareRoot :: Integral t => t -> t
squareRoot n 
   | n > 0    = babylon n
   | n == 0   = 0
   | n < 0    = error "Negative input"
   where
   babylon a   | a > b = babylon b
               | otherwise = a
      where b  = quot (a + quot n a) 2

We can check if the found root is indeed the exact square root with by squaring the root and see if we obtain back the original input.我们可以通过平方根来检查找到的根是否确实是精确的平方根,看看我们是否获得了原始输入。

So now that we have that, we can iterate over the lowerbound of the sequence, and look for the upperbound.所以现在我们有了这个,我们可以遍历序列的下界,并寻找上界。 If that exists, we return the sequence, otherwise, we try the next one:如果存在,我们返回序列,否则,我们尝试下一个:

decompose :: Int -> [[Int]]
decompose s = [ [k .. div (sq-1) 2 ]
              | k <- [1 .. s]
              , let r = 1+8*s+4*k*(k-1)
              , let sq = squareRoot r
              , r == sq*sq
              ]

We can thus for example obtain the items with:例如,我们可以通过以下方式获取项目:

Prelude> decompose 1
[[1]]
Prelude> decompose 2
[[2]]
Prelude> decompose 3
[[1,2],[3]]
Prelude> decompose 3
[[1,2],[3]]
Prelude> decompose 1
[[1]]
Prelude> decompose 2
[[2]]
Prelude> decompose 3
[[1,2],[3]]
Prelude> decompose 4
[[4]]
Prelude> decompose 5
[[2,3],[5]]
Prelude> decompose 6
[[1,2,3],[6]]
Prelude> decompose 7
[[3,4],[7]]
Prelude> decompose 8
[[8]]
Prelude> decompose 9
[[2,3,4],[4,5],[9]]
Prelude> decompose 10
[[1,2,3,4],[10]]
Prelude> decompose 11
[[5,6],[11]]

We can further constrain the ranges, for example specify that k<l , with:我们可以进一步限制范围,例如指定k<l ,其中:

decompose :: Int -> [[Int]]
decompose s = [ [k .. l ]
              | k <- [1 .. div s 2 ]
              , let r = 1+8*s+4*k*(k-1)
              , let sq = squareRoot r
              , r == sq*sq
              , let l = div (sq-1) 2
              , k < l
              ]

This then gives us:这给了我们:

Prelude> decompose 1
[]
Prelude> decompose 2
[]
Prelude> decompose 3
[[1,2]]
Prelude> decompose 4
[]
Prelude> decompose 5
[[2,3]]
Prelude> decompose 6
[[1,2,3]]
Prelude> decompose 7
[[3,4]]
Prelude> decompose 8
[]
Prelude> decompose 9
[[2,3,4],[4,5]]
Prelude> decompose 10
[[1,2,3,4]]
Prelude> decompose 11
[[5,6]]

NB This answer is slightly tangential since the question specifically calls for a direct backtracking solution in Haskell.注意这个答案有点切题,因为这个问题特别要求 Haskell 中的直接回溯解决方案。 Posting it in case there is some interest in other approaches to this problem, in particular using off-the-shelf SMT solvers.发布它以防对解决此问题的其他方法感兴趣,特别是使用现成的 SMT 求解器。

These sorts of problems can be easily handled by off-the-shelf constraint solvers, and there are several libraries in Haskell to access them.这类问题可以通过现成的约束求解器轻松处理,并且 Haskell 中有几个库可以访问它们。 Without going into too much detail, here's how one can code this using the SBV library ( https://hackage.haskell.org/package/sbv ):无需过多详细介绍,以下是如何使用 SBV 库 ( https://hackage.haskell.org/package/sbv ) 对其进行编码:

import Data.SBV

decompose :: Integer -> IO AllSatResult
decompose n = allSat $ do
                 i <- sInteger "i"
                 j <- sInteger "j"

                 constrain $ 1 .<= i
                 constrain $ i .<= j
                 constrain $ j .<  literal n

                 constrain $ literal n .== ((j * (j+1)) - ((i-1) * i)) `sDiv` 2

We simply express the constraints on i and j for the given n , using the summation formula.我们使用求和公式简单地表示给定nij的约束。 The rest is simply handled by the SMT solver, giving us all possible solutions. rest 由 SMT 求解器简单处理,为我们提供所有可能的解决方案。 Here're a few tests:这里有几个测试:

*Main> decompose 9
Solution #1:
  i = 4 :: Integer
  j = 5 :: Integer
Solution #2:
  i = 2 :: Integer
  j = 4 :: Integer
Found 2 different solutions.

and

*Main> decompose 10
Solution #1:
  i = 1 :: Integer
  j = 4 :: Integer
This is the only solution.

While this may not provide much insight into how to solve the problem, it sure leverages existing technologies.虽然这可能无法提供有关如何解决问题的太多见解,但它确实利用了现有技术。 Again, while this answer doesn't use the list-monad as asked, but hopefully it is of some interest when considering applications of SMT solvers in regular programming.同样,虽然这个答案没有按照要求使用 list-monad,但希望在考虑 SMT 求解器在常规编程中的应用时它会引起一些兴趣。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM