微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

配对组合

如何解决配对组合

我正在尝试组合方案中的对列表以获得所有可能的组合。例如:

((1 2) (3 4) (5 6)) --> ((1 3 5) (1 3 6) (1 4 5) (1 4 6) (2 3 5) (2 3 6) (2 4 5) (2 4 6))

我已经能够解决它(我认为)使用“取第一个并将其添加到程序的 cdr”并使用以下内容

(define (combine-pair-with-list-of-pairs P Lp)
  (apply append
         (map (lambda (num)
                (map (lambda (pair)
                       (cons num pair)) Lp)) P)))

(define (comb-N Lp)
  (if (null? Lp)
      '(())
      (combine-pair-with-list-of-pairs (car Lp) (comb-N (cdr Lp)))))

(comb-N '((1 2)(3 4)(5 6)))
; ((1 3 5) (1 3 6) (1 4 5) (1 4 6) (2 3 5) (2 3 6) (2 4 5) (2 4 6))

然而,我一直无法弄清楚如何使用一个只需要两个的过程并在它周围有一个包装器,以便能够通过调用函数来定义 comb-N。这是:

(define (combinations L1 L2)
  (apply append
         (map (lambda (L1_item)
                (map (lambda (L2_item)
                       (list L1_item L2_item))
                     L2))
              L1)))
(combinations '(1) '(1 2 3))
; ((1 1) (1 2) (1 3))

我想调用这个函数的困难在于它需要两个列表,而递归调用需要一个列表列表作为第二个参数。我如何调用这个 combinations 函数来定义 comb-N

解决方法

难度?递归?在哪里?

您可以使用 delimited continuations 编写 combinations。这里我们用 amb 表示一个模糊计算。由 reset 限定的表达式将为提供给 amb -

的每个参数运行一次
(define (amb . lst)
  (shift k (append-map k lst)))

(reset
  (list (list (amb 'a 'b) (amb 1 2 3))))
((a 1) (a 2) (a 3) (b 1) (b 2) (b 3))

工作原理

表达式通过第一个 amb 求值,其中延续被捕获到 k -

k := (list (list ... (amb 1 2 3)))

应用 k 将其参数提供给 ambshift 的调用留下的“洞”,由上面的 ... 表示。我们可以有效地将 k 视为 lambda -

k := (lambda (x) (list (list x (amb 1 2 3)))

amb 返回一个 append-map 表达式 -

(append-map k '(a b))

其中 append-mapk 应用于输入列表的每个元素,'(a b)append 结果。这有效地转化为 -

(append
 (k 'a)
 (k 'b))

接下来展开延续,k,就地 -

(append
 (list (list 'a (amb 1 2 3)))  ; <-
 (list (list 'b (amb 1 2 3)))) ; <-

继续评估,我们评估下一个 amb。该模式仍在继续。 ambshift 的调用捕获到 k 的当前延续,但这次延续有所演变 -

k := (list (list 'a ...))

同样,我们可以将 k 视为 lambda -

k := (lambda (x) (list (list 'a x)))

并且 amb 返回一个 append-map 表达式 -

(append
 (append-map k '(1 2 3)) ; <-
 (list (list 'b ...)))

我们可以继续这样工作来解决整个计算。 append-mapk 应用于输入的每个元素并 append 将结果转化为 -

(append
 (append (k 1) (k 2) (k 3)) ; <-
 (list (list 'b ...)))

就地展开 k -

(append
  (append
     (list (list 'a 1))  ; <-
     (list (list 'a 2))  ; <-
     (list (list 'a 3))) ; <-
  (list (list 'b (amb 1 2 3))))

我们现在真的可以开始看到它的发展方向了。我们可以将上面的表达式简化为 -

(append
 '((a 1) (a 2) (a 3)) ; <-
 (list (list 'b (amb 1 2 3))))

评估现在继续到最终的 amb 表达式。我们将再次遵循该模式。此处 ambshift 的调用将当前延续捕获为 k -

k := (list (list 'b ...))

lambda 方面,我们认为 k 为 -

k := (lambda (x) (list (list 'b x)))

amb 返回一个 append-map 表达式 -

(append
 '((a 1) (a 2) (a 3))
 (append-map k '(1 2 3))) ; <-

append-mapk 应用于每个元素并 append 计算结果。这转化为 -

(append
 '((a 1) (a 2) (a 3))
 (append (k 1) (k 2) (k 3))) ; <-

就地展开k -

(append
 '((a 1) (a 2) (a 3))
 (append
  (list (list 'b 1))   ; <-
  (list (list 'b 2))   ; <-
  (list (list 'b 3)))) ; <-

这简化为 -

(append
 '((a 1) (a 2) (a 3))
 '((b 1) (b 2) (b 3))) ; <-

最后我们可以计算最外面的 append,产生输出 -

((a 1) (a 2) (a 3) (b 1) (b 2) (b 3))

概括一个过程

上面我们使用了固定输入,'(a b)'(1 2 3)。我们可以创建一个通用的 combinations 过程,将 amb 应用于其输入参数 -

(define (combinations a b)
  (reset
   (list (list (apply amb a) (apply amb b)))))
(combinations '(a b) '(1 2 3))
((a 1) (a 2) (a 3) (b 1) (b 2) (b 3))

现在我们可以轻松地扩展这个想法以接受任意数量的输入列表。我们编写了一个可变参数 combinations 过程,方法是取一个 list 列表并对其进行 map,将 amb 应用到每个 -

(define (combinations . lsts)
  (reset
     (list (map (lambda (each) (apply amb each)) lsts))))
(combinations '(1 2) '(3 4) '(5 6))
((1 3 5) (1 3 6) (1 4 5) (1 4 6) (2 3 5) (2 3 6) (2 4 5) (2 4 6))

可以使用任意数量的任意长度的列表 -

(combinations
  '(common rare)
  '(air ground)
  '(electric ice bug)
  '(monster))
((common air electric monster)
 (common air ice monster)
 (common air bug monster)
 (common ground electric monster)
 (common ground ice monster)
 (common ground bug monster)
 (rare air electric monster)
 (rare air ice monster)
 (rare air bug monster)
 (rare ground electric monster)
 (rare ground ice monster)
 (rare ground bug monster))

相关阅读

在 Scheme 中,我们可以使用 Olivier Danvy 对 shift/reset 的原始实现。在 Racket 中,它们通过 racket/control

提供
(define-syntax reset
  (syntax-rules ()
    ((_ ?e) (reset-thunk (lambda () ?e)))))

(define-syntax shift
  (syntax-rules ()
    ((_ ?k ?e) (call/ct (lambda (?k) ?e)))))

(define *meta-continuation*
  (lambda (v)
    (error "You forgot the top-level reset...")))

(define abort
  (lambda (v)
    (*meta-continuation* v)))

(define reset-thunk
  (lambda (t)
    (let ((mc *meta-continuation*))
      (call-with-current-continuation
        (lambda (k)
      (begin
        (set! *meta-continuation* (lambda (v)
                    (begin
                      (set! *meta-continuation* mc)
                      (k v))))
        (abort (t))))))))

(define call/ct
  (lambda (f)
    (call-with-current-continuation
      (lambda (k)
    (abort (f (lambda (v)
            (reset (k v)))))))))

有关 append-mapamb 使用的更多见解,请参阅 this answer 以解决您的另一个问题。

另请参阅 Scheme Wiki 上的 Compoasable Continuations Tutorial

备注

一开始我真的很纠结于函数式风格。我对命令式风格深恶痛绝,我花了一些时间将递归视为以功能方式解决问题的“自然”思维方式。然而,我提供这篇文章是希望激发你达到更高层次的思考和推理。递归是我在本网站上写得最多的主题,但我在这里说的是,有时甚至可以通过更具创造性、想象力和声明性的方式来表达您的程序。

一流的延续可以将您的程序由内而外,让您可以编写一个操作、消耗和乘以自身的程序。这是一种复杂的控制级别,是 Scheme 规范的一部分,但仅在 few other languages 中得到完全支持。像递归一样,延续是一个难以破解的难题,但是一旦你“看到”,你就会希望你早点学会它们。

,

正如评论中所建议的,您可以使用递归,特别是右折叠:

(define (flatmap foo xs)
  (apply append
    (map foo xs)))

(define (flatmapOn xs foo)
  (flatmap foo xs))

(define (mapOn xs foo)
  (map foo xs))

(define (combs L1 L2)  ; your "combinations",shorter name
  (flatmapOn L1 (lambda (L1_item)
     (mapOn L2   (lambda (L2_item)        ; changed this:
             (cons L1_item L2_item))))))  ;   cons     NB!

(display
 (combs '(1 2)
   (combs '(3 4)
     (combs '(5 6) '( () )))))

; returns:
; ((1 3 5) (1 3 6) (1 4 5) (1 4 6) (2 3 5) (2 3 6) (2 4 5) (2 4 6))

所以您看,您在那里使用的 list 不太正确,我将其改回 cons(因此它变得与 combine-pair-with-list-of-pairs 完全相同)。这样它就可以扩展了:(list 3 (list 2 1)) 不好,但 (cons 3 (cons 2 (cons 1 '()))) 更好。

使用 list 它不能如您所愿:这样的函数接收元素列表,并生成元素列表的列表。在该函数的另一次调用中,这种输出不能用作预期类型的​​输入——它会产生不同类型的结果。要通过每次仅组合两个来构建多个,该组合必须产生与两个输入相同类型的输出。它就像 +,带有数字。所以要么继续使用 cons,要么完全改变组合函数。

至于我对右折叠的评论:这就是上面示例中对 combs 的嵌套调用的结构。它可以用来定义这个函数为

(define (sequence lists)
  (foldr
     (lambda (list r)   ; r is the recursive result
        (combs list r))
     '(())              ; using `()` as the base
     lists))

是的,这个函数的正确名称是 sequence(好吧,它是在 Haskell 中使用的)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。