【问题标题】:Calculate possible sums of int list list recursively递归计算 int list list 的可能总和
【发布时间】:2013-01-31 02:34:34
【问题描述】:

我正在努力编写代码,它将计算 int 列表列表的总和。例如,如果我们考虑以下列表

[[1],[1,5],[7],[2,3,4]]

我们可以得到不同的可能总和,这取决于我们每次选择哪个整数:

[11,12,13,15,16,17]

有人可以给出可能的方法(不需要代码)我可以解决它吗?

【问题讨论】:

    标签: sml smlnj ml


    【解决方案1】:

    这是我尝试仅使用列表和模式匹配来制作尾递归(有点)版本的算法。它是用 OCaml 编写的,而不是 SML 并且需要内部列表的升序

    let reverse list = 
        let rec iter list acc :int list = match list with
        [] -> acc
        | x::xs -> iter xs (x::acc)
     in iter list [];;
    
    let listAdd num list = 
      let rec iter num list acc :int list = match list with
         [] -> acc
        | x::xs -> iter num xs ((x + num)::acc)
      in reverse( iter num list []);;   
    
    let listMerge listX listY = 
      let rec iter listX listY acc : int list = match listX with 
         [] -> (reverse acc) @ listY
         | x :: xs -> match listY with 
            [] -> (reverse acc ) @ listX
            | y :: ys -> match ( compare x y ) with
               0  ->  iter xs ys ( x::acc )
              |(-1) ->  iter xs listY ( x::acc )    
              | 1 ->  iter listX ys ( y::acc )
      in iter listX listY [];;  
    
    let listSums listX listY =
     let rec iter listX listY acc = match listX with 
       [] -> acc
       | x :: xs -> iter xs listY ( listMerge acc (listAdd x listY ) )
     in iter listX listY [];;  
    
    let possibleSums shallowList = match shallowList with 
       [] -> []
       | headList :: lists -> 
          let rec iter acc lists  = match lists with 
              [] -> acc
              | headList :: other -> iter ( listSums acc headList ) other  
          in iter headList lists;;
    

    这里possibleSums [[1];[1;5];[7];[2;3;4]];; 计算为int list = [11; 12; 13; 15; 16; 17]

    所以基本上合乎逻辑的步骤是:

    • reverse 函数只是在生成累加器后恢复顺序的实用程序 - 尾递归优化的常用概念
    • listAdd 函数可避免在构建摘要时使用 map 实用程序
    • listMerge 函数等于 set union 以将排序列表用作整数集
    • listSums 函数用于将求和映射到两组的笛卡尔积
    • posibleSums 函数用于将总和映射到所有集合的完整笛卡尔积的最终级别。

    该算法不仅通过TCO避免了递归中的堆栈溢出,而且还为这个计算问题提供了接近最优的解决方案。

    此外,此解决方案还可以通过添加过早的 merge sorting 并从集合中删除重复元素来进一步改进,因此内部集合顺序现在无关紧要:

    let split lst = 
        let rec iter lst partX partY = match lst with
            [] -> partX,partY
            | x::xs -> iter xs partY (x::partX)
        in iter lst [] [];;
    
    let rec mergeSort lst = match lst with 
        [] -> []
        |[x] -> [x]
        | _ -> let partX,partY = split lst in
               listMerge (mergeSort partX) (mergeSort partY);;
    
    let possibleSums shallowList = match shallowList with 
       [] -> []
       | headList :: lists -> 
          let rec iter acc lists  = match lists with 
              [] -> acc
              | headList :: other -> iter ( listSums acc (mergeSort headList) ) other  
          in iter (mergeSort headList) lists;;
    

    您可以通过简单的示例测试其效率。让我们定义重复函数来制作重复列表:

     let rec repeat elem n = match n with
         0 -> []
        | _ -> elem :: (repeat elem (n - 1));;
    

    在这种情况下,possibleSums( repeat( repeat 1 30) 30 ) 将立即计算正确的单例答案int list = [30]。虽然更直接的解决方案(如 Jesper 的解决方案)将永远做到这一点。

    【讨论】:

    • 不用多说,我的简单解决方案可以通过“运行”求和来优化,而不是生成列表列表然后对它们求和。但是无论如何它可能不会匹配:)
    • @Jesper.Reenberg 问题不在生成方法中。仅对于整数的情况,所有可能的数字组合的集合都太大了。此解决方案速度很快,因为在中间结果中删除了重复,因此它根据初始数计数和估计结果长度保持多项式复杂度。
    【解决方案2】:

    以下是尝试将步骤分解为可读且希望可以理解的部分

    fun sums xss =
        let
          fun extend xss y = map (fn xs => y :: xs) xss
          fun extend' xss ys = foldl (fn (y, b) => extend xss y @ b) [] ys
          fun extend'' xss = foldl (fn (xs,b) => extend' b xs) [[]] xss
          fun sum xs = foldl op+ 0 xs
        in
          map sum (extend'' xss)
        end
    
    
    - sums [[1],[1,5],[7],[2,3,4]];
    val it = [17,13,16,12,15,11] : int list
    

    显然,大多数函数都可以按照正确的顺序将参数作为对进行处理,因此直接提供给 map 和 fold 函数,而不是将它们包装在匿名函数中。

    【讨论】:

    • 谢谢!你认为不使用map和foldl可以实现这个功能吗?我正在上一门编程课程,但还不允许使用这些功能:) 我尝试使用模式匹配和递归来实现它,但完全卡住了......
    • 一切皆有可能。您可以实现自己的 map 和 fold 函数,甚至是它们的特殊版本,但是在后一种情况下,您可以将它们全部内联到一个函数中。
    【解决方案3】:

    我会尝试用递归循环来解决它 - 在伪代码中:

    sum = 0;
    
    while (i < arrayOfArrays.length ) {
         sumUp(arrayOfArrayElements);
         i ++
     }
    
     function sumUp() {
         while( j < arrayOfArrayElement.length){
         sum = sum + arrayOfArrayElement[j].val
         j ++
    
      }
    
    
    }
    

    因此,您调用一个函数,该函数对数组的每个数组元素求和,并在一个函数内将这些值相加,该函数为该数组数组中的每个元素调用该函数。哈哈

    1. 创建一个函数,该函数接受一个整数数组并使用 for 或 while 循环添加包含整数。
    2. 循环遍历主数组并调用每个包含数组元素的函数以求和,直到没有更多元素为止,您可以通过使用数组的 .length 来实现此目的,该数组应该在每个脚本/编程中都可以访问语言

    【讨论】:

    • 你的伪代码解决了不同的问题,是不是不够清楚?
    • 嘿伙计,那你的问题可能还不够清楚?我想你想得到一个列表中的整数的总和。
    • 1) 看例子,我猜你甚至没有看 2) 你的解决方案甚至不是递归的
    • 好吧,我不明白这个问题,谢谢你的提示,祝你好运。
    • 您的解决方案使用了一种非常命令式的思维方式,这对于在 SML(一种函数式语言)中进行编码有点用处。
    猜你喜欢
    • 1970-01-01
    • 2012-02-14
    • 1970-01-01
    • 2021-03-08
    • 1970-01-01
    • 2019-08-08
    • 2020-07-04
    • 1970-01-01
    • 2014-12-28
    相关资源
    最近更新 更多