【问题标题】:Given a string of ints and an objective number print out all the possible combinations of tuples and their results给定一串整数和一个目标数,打印出所有可能的元组组合及其结果
【发布时间】:2013-04-29 17:05:42
【问题描述】:

嗯,我确实明白,haskell 的全部意义(主要)是使用递归从更简单的函数中构建更复杂函数的优势,我有一个名为 pairs 的函数,它从一个整数字符串返回所有可能的元组组合,然后我有另一个称为操作的函数,它使用给定的元组打印出两个数字可以执行的所有可能操作(*、+、-、/),现在是我无法理解的部分:

-- Find all possible 2-combinations of the elements of xs.
pairs :: [Int] -> [(Int, Int)]
pairs xs = [(x, y) | (x:ys) <- tails xs, y <- ys]

operations :: (Int, Int) -> [(Int, Int, Char, Int)]
operations (x, y) =
    [ (x, y, '+', x + y) ] ++
    [ (x, y, '*', x * y) ] ++
    [ (x, y, '-', x - y) | x > y, (x/=4 && y/=2) ] ++
    [ (x, y, '/', x `div` y) | x >= y, x `mod` y == 0]

我正在尝试实现一个给定整数字符串和目标数字的函数(最终目标是使用整数字符串获得该数字)我打印出所有可能的元组组合及其结果,例如)

solve ( 100 , [1,4,5] , [] )

[ ( 100 , [5,5] , [(1,4,'+',5)] ),take first tuple 1,4 add and subs into "new tuple"5,5
( 100 , [3,5] , [(4,1,'-',3)] ),
( 100 , [6,4] , [(1,5,'+',6)] ),
( 100 , [4,4] , [(5,1,'-',4)] ),
( 100 , [9,1] , [(4,5,'+',9)] ),
( 100 , [1,1] , [(5,4,'-',1)] ),
( 100 , [20,1] , [(4,5,'*',20)] ) ]

我对如何解决这个问题感到困惑,因为我知道我已经有一个函数可以在一个元组上打印所有可能的操作,一个生成所有元组的函数但我看不到如何组合它们,任何帮助都是感谢,谢谢。


我看到了你的解决方案并且有道理,但我从头开始已经太晚了,

我已经这样做了:

solve(n,ns) = [ e | ns' <- pairs ns
                      , e   <- operations ns'] 

( 100 , [3,5] , [(4,1,'-',3)] ),是我想要的

我明白了,我想尝试我的工作方式,因为它似乎有点不同,在第二次之后我感到困惑,我在 Haskell 上仍然有点糟糕。 所以这就是我的功能所做的: 对:当给定一个字符串时,返回所有可能的元组:对 [1,2,3,4,5,6] 将返回 [(1,2),(1,3)...等] operations 接受一个元组并返回该元组所有可能的操作(必须是正整数结果,否则我们不想要它),最后

solve(n,ns) = [ e | ns' <- pairs ns
                      , e   <- operations ns'] 

取 n 为目标数, ns 为 6 个 +int 的字符串,到目前为止返回一个字符串,其中包含打印的所有元组组合,例如:[(3,'+',4,7),(3,´* ´,4,12)...等] 但是我希望它在每个阶段都打印:

[n,(result of tuple operation,string number)(tuple operation)]
eg ( 100 , [5,5] , [(1,4,'+',5)] ),take first tuple 1,4 add and subs into "new tuple"5,5
( 100 , [3,5] , [(4,1,'-',3)] ),
( 100 , [6,4] , [(1,5,'+',6)] ),

【问题讨论】:

  • 你能提供你已经拥有的函数的类型签名,以及你正在尝试制作的函数吗?这会让你的问题更清楚。
  • 好的,我会编辑主帖
  • 我对 solve 函数的示例输出感到困惑。没有一个操作实际上产生目标数字 100。另外,每个结果元组的第二个元素是什么意思?
  • 是的,我还没有找到,我只是找到了所有可能的操作并以这种方式打印出来,所以在 ( 100 , [3,5] , [(4,1,'- ',3)] ),作为结果的 3 被存储回元组 [3,5] 因为这是一个新的元组,我可以再次递归地使用操作(这是我的疯狂猜测),我是否有意义?
  • 所以,从一个初始的整数字符串可以产生所有的元组,从这些元组上进行的操作每个元组产生一组新的数字,这些数字又可以用来创建一个新的元组。也许我应该先说这个;但目的是使用 add,subb, div, mult 在一串数字上得到一个目标数字,这是我必须做的,但我发现这更复杂和冗长glc.us.es/~jalonso/vestigium/…

标签: haskell


【解决方案1】:

有很多方法可以解决这个问题。下面是一个相对简单的 Haskell 式解决方案的大纲。请注意,它使用代数数据类型,因此如果您还没有熟悉它,则需要熟悉它。

注意:这是一个有点复杂的问题。我的解决方案(比较干净)是 55 行。

第一步是为您的问题定义适当的数据类型。我会选择以下:

data Expr = Lit Int
   | Plus Expr Expr
   | Times Expr Expr
   | Minus Expr Expr
   | Divide Expr Expr
  deriving Show

Expr 类型的值是一个表达式树,它由四个二元运算组成,并且在其叶子处具有整数。使用此定义,您需要定义以下函数:

eval :: Expr -> Int       -- "evaluate" a expression

exprs :: [Int] -> [Expr]  -- derive all expression trees whose literals come from
                          -- a list of integers

然后找到计算结果为特定数字的表达式就是:

findexprs :: [Int] -> Int -> [Expr]
findexprs xs y = filter (\e -> eval e == y) $ exprs xs

写作eval

eval 函数将是一个直截了当的案例分析:

eval (Lit x) = x
eval (Plus a b) = (eval a) + (eval b)
eval (Minus a b) = (eval a) - (eval b)
...

提示:对于除法,查找quot 函数。

写作exprs

exprs 的前几个案例非常简单:

exprs :: [Int] -> [Expr]
exprs []  = []
exprs [x] = [ Lit x ]
exprs xs  = ...

当列表中只有一个数字时,您可以创建的唯一表达式是Lit

exprs 的最后一个例子是这样的:

  1. xs分为两个子列表:leftright
  2. 使用列表left 制定表达式树
  3. 使用列表right 制定表达式树
  4. 用二元运算符组合两个表达式树

第 2 步和第 3 步只是对 exprs 函数的递归调用。第 4 步只是遍历所有可能的二元运算符。您可以为此使用列表推导。

对于第 1 步,我们需要找到将列表拆分为两个子列表的所有方法。 也就是说,我们需要定义一个函数:

parts :: [Int] -> [ ([Int], [Int]) ]

例如,parts [1,2] = [ ([1,2],[]), ([1],[2]), ([2],[1]), ([], [1,2]) ]

当然parts可以递归定义,诀窍就是找到模式:

parts [] = [ ([],[]) ]
parts (x:xs) = ...???...

这里的提示是问自己如何从parts xs 形成parts (x:xs)

注意事项

我遗漏了一些实现细节。首先,如果你真的想正确实现除法,你可能不得不重新考虑eval 的这种类型签名:

eval :: Expr -> Int

最初为了让事情正常工作,您可能希望省略除法运算符。那么您可能想了解Maybe 数据类型。

我还在exprs 的定义中省略了细节。在我概述的步骤中潜伏着一个无限循环的陷阱(可以很容易地避开)。

祝你好运!

评论

(由于 SO 不喜欢 cmets 中长时间运行的线程,我将在此处解决 OP 的问题。)

正如我之前提到的,有很多方法可以解决这个问题,例如看 Algorithm for permutations of operators and operands

这种方法比较复杂,但它是一种有用的分解模式 你会看到它在 Haskell 中被广泛使用。也很注意分开 以下问题:

  1. 生成可能的树(exprs 函数)
  2. 评估表达式树(eval 函数)
  3. 找到感兴趣的树 (filter ...)

您的方法结合了前两个问题。这可能不是问题 如果你只是解决这个问题,但假设你改变了标准 为合法表达。例如,如果列表中的数字可以重复使用会怎样 多次(目前数字只能使用一次。)或者,如果你不这样做怎么办 需要使用所有的数字吗?这些变化只需要改变 exprs 函数。

【讨论】:

  • 非常感谢!!!是的,“backgorund”确实是树,我要把我的头绕在一些我以前从未见过的概念上,如果我被卡住了哈!再次感谢
  • 问题:我已经设法以“我的方式”完成了第一个功能,我清楚地看到了它们是如何工作和理解的,现在虽然我开始理解你的建议,但它似乎要复杂得多,有没有办法使用我最初所做的?再次感谢您提供如此出色的解释和解决方案
  • 我看到了你的解决方案并且说得通,但是我从头开始已经太晚了,尽管我很喜欢 haskell,但我不认为我会在一段时间内使用它,我已经这样做了
  • 0 否决票接受我看到您的解决方案并且有道理,但是我从头开始为时已晚,我已经这样做了:solve (n,ns) = [ e | ns'
【解决方案2】:

我想我终于明白你要做什么了。

阅读以下代码时,您应该在整数示例列表上运行 choose1pairs 以查看它们的作用,例如choose1 [2,5,7]pairs [1,2,3]

phi 将所有可能的评估作为一对(x,hs) 返回,其中x 是最终结果,hs 是操作历史记录(列表)。请注意,历史是倒退的 - hs 列表的第一个元素是最后执行的操作。

hs 列表中的每个元素本身都是(Int,Char,Int,Int) 形式的元组——例如(3,'-',4,-1) 表示操作3-4 =&gt; -1

作为测试,试试:head $ solve [3,7,13,19,23,29] 823

import Data.List (inits,tails)

always _ _ = True
canDivide a b = (b /= 0) && (a `mod` b) == 0

ops :: [ ( Int -> Int -> Int, Char, Int -> Int -> Bool) ]
ops = [ ((+), '+', always),
        ((-), '-', always),
        ((*), '*', always),
        (div, '/', canDivide) ]

choose1 xs = zip xs zs
  where zs = zipWith (++) (inits xs) (tail $ tails xs)

pairs xs = [ (x,y,r2) | (x,r1) <- choose1 xs, (y,r2) <- choose1 r1 ]

phi xs = go xs []
    where
      go [] hs  = []
      go [x] hs = [ (x,hs) ]
      go xs hs  = [ (x,h) |
          (a,b,rest) <- pairs xs,
          (op,name,can) <- ops,
          can a b,
          let c = op a b,
          (x,h) <- go (c:rest) ((a,name,b,c):hs) ]

solve :: [Int] -> Int -> [ (Int, [ (Int, Char, Int, Int) ] ) ]
solve xs n = filter (\(x,hs) -> (x == n)) $ phi xs

【讨论】:

  • 我明白了,我想尝试一下我的工作方式,因为它看起来有点不同,我在 2md 之后感到困惑,我在 Haskell 上仍然有点糟糕。
  • 再次编辑了我的主帖
  • 但是你的方法是完全正确的,问题是我在这个阶段对haskell的无能。
猜你喜欢
  • 2015-11-05
  • 2016-09-02
  • 2015-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多