如何解决配对组合
我正在尝试组合方案中的对列表以获得所有可能的组合。例如:
((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
将其参数提供给 amb
对 shift
的调用留下的“洞”,由上面的 ...
表示。我们可以有效地将 k
视为 lambda
-
k := (lambda (x) (list (list x (amb 1 2 3)))
amb
返回一个 append-map
表达式 -
(append-map k '(a b))
其中 append-map
将 k
应用于输入列表的每个元素,'(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
。该模式仍在继续。 amb
对 shift
的调用捕获到 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-map
将 k
应用于输入的每个元素并 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
表达式。我们将再次遵循该模式。此处 amb
对 shift
的调用将当前延续捕获为 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-map
将 k
应用于每个元素并 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-map
和 amb
使用的更多见解,请参阅 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 举报,一经查实,本站将立刻删除。