Functional programming 函数程序-编写一个函数,从中间重新排列数组

Functional programming 函数程序-编写一个函数,从中间重新排列数组,functional-programming,scheme,racket,Functional Programming,Scheme,Racket,任务是编写一个函数,该函数获取一个列表,例如(7 8 2 9 5 6),然后从中心“展开”它,将其重新排列为2 9,然后2 9 8 5,最后输出为2 9 8 7 6 我大致算出了伪代码: 取数组A中的最后一个元素,将其附加到前面的数组B中 从数组A中删除最后一个元素 取数组A中的第一个元素,将其附加到前面的数组B中 从数组A中删除第一个元素 所以 782956-> 7 8 2 9 5->6 8295->76 829->576 29->8576 2->98576 ->2985776纠正最终输出

任务是编写一个函数,该函数获取一个列表,例如
(7 8 2 9 5 6)
,然后从中心“展开”它,将其重新排列为
2 9
,然后
2 9 8 5
,最后输出为
2 9 8 7 6

我大致算出了伪代码:

  • 取数组A中的最后一个元素,将其附加到前面的数组B中
  • 从数组A中删除最后一个元素
  • 取数组A中的第一个元素,将其附加到前面的数组B中
  • 从数组A中删除第一个元素
所以

782956
->

7 8 2 9 5
->
6

8295
->
76

829
->
576

29
->
8576

2
->
98576

->
2985776
纠正最终输出

到目前为止,我的代码就在这里(一点也不远)

在我的语法错误注释中,我试图做的是。。如果
数组U
!null,然后将
最后一个元素L
前置到一个新数组。。。然后,不知何故,我必须找出如何从
U
中删除
lastElement L
,然后获取第一个元素并删除它。。我相信这将通过
汽车
和/或
cdr
实现

编辑--替代可能的方法

(define (lastElement L)
 (if (null? (cdr L)) (car L)
 (lastElement (cdr L))))

(define (trim lst)
    (if (null? (cdr lst))
        '()
        (cons (car lst) (trim (cdr lst)))))

(define (first-half lst)
  (take lst (quotient (length lst) 2)))



(define (unwind U)
 (if (= (length U) 1 ) 999
  ( (lastElement (first-half U))
     (car (list-tail U (length(first-half U))))
          (unwind (cons
                   (trim (length (first-half U)))
                   (cdr (list-tail U (length(first-half U))))
                   )
                  )
  )
 )
)



(unwind '(7 8 2 9 5 6))

这是一个棘手的问题…这里有一个接近您在问题中描述的方法,包括正确处理边缘情况(空列表,元素奇数的列表):

请注意,我使用了两个内置函数来简化操作,特别是,和过程。关键的洞察是在每个步骤传递一个布尔标志,指示我们是否应该获取列表的第一个或最后一个元素,即使只剩下一个元素时也会使用该标志。它按预期工作:

(unwind '())
=> '()

(unwind '(7 6))
=> '(7 6)

(unwind '(7 8 2 9 5 6))
=> '(2 9 8 5 7 6)

(unwind '(7 8 2 0 9 5 6))
=> '(2 9 8 5 7 6 0)

我使用了一个经典的龟兔递归将列表一分为二。您使用
cdr
cddr
cdr
cdr
)遍历它,因此当较快的循环部分为空或单件列表时,较慢的部分会给出列表的最后一半。我还积累了列表的前半部分,以便以后使用

   (define (unwind L)
       (let loop ((HalfR '()) (Turtle L) (Hare L))
          (cond ((null? Hare) (interleave HalfR Turtle))
                ((null? (cdr Hare)) 
                 (cons (car Turtle) (interleave HalfR (cdr Turtle))))
                (else (loop (cons (car Turtle) HalfR)
                            (cdr Turtle)
                            (cddr Hare))))))

(define (interleave L1 l2)
  (OR (AND (null? L1) L2)   ;;**to catch cases where L1 and L2 are not equal.
      (AND (null? L2) L1)   ;;after interleaving to the extent possible. 
      (cons (car L1)        
            (cons (car L2) 
                  (interleave (cdr L1) (cdr L2)))))) 

1 ]=> (unwind '(1 1 2 3 5 8 13))
;Value 11: (3 2 5 1 8 1 13)

1 ]=> (unwind '(7 8 2 9 5 6))
;Value 12: (2 9 8 5 7 6)

更简单的解决方案是创建列表的反向副本,然后选择每个列表的第一个元素。当结果列表的长度与初始列表的长度相同时,停止条件为:

(define (unwind lst)
  (define maxlen (length lst))
  (let loop ((lst1 lst) (lst2 (reverse lst)) (res null) (len 0))
    (if (= len maxlen)
        res
        (if (even? len)
            (loop lst1 (cdr lst2) (cons (car lst2) res) (add1 len))
            (loop (cdr lst1) lst2 (cons (car lst1) res) (add1 len))))))
测试:

> (unwind '(1 1 2 3 5 8 13))
'(3 2 5 1 8 1 13)
> (unwind '(7 8 2 9 5 6))
'(2 9 8 5 7 6)

我想添加另一个解决方案,使用我从Haskell那里了解到的内容

基本上,列表上的拉链由一个标记当前焦点的点、该点左侧的列表以及该点右侧的另一个列表组成

;; Creating our zipper abstraction
(define (make-zipper l p r)
 "Create a zipper with what's to come left of point,
 what's at point and what's right of the point."
 (list l p r))
(define (zipper-point z)
 "Get the point of the zipper."
 (cadr z))
(define (zipper-left z)
 "Get what's left of the zipper, in the order as if
 the point moved to the left."
 (car z))
(define (zipper-right z)
 "Get what's right of the zipper."
 (caddr z))
列表可以很容易地转换为第一个元素处的拉链:

;; Conversion into our new data type
(define (zipper-from-list l)
 "Create a zipper from a (non empty) list, and
 place the point at the first element."
 (make-zipper '() (car l) (cdr l)))
拉链带来了一个很酷的东西:四处走动。基本上,这就像是C语言或C++语言中的指针。您可以向左或向右移动,并且可以修改当前指向的值(无需像使用简单列表那样花费高昂的代价重新构建和遍历所有值)

现在,我想让我的拉链为这项任务做一件特殊的事情:销毁点处的值,并用左侧或右侧的值填充结果间隙:

;; A special kind of moving, destructing the value at point.
;; (2 1) 3 (4 5)
;; zipper-squash-left => (1) 2 (4 5)
;; zipper-squash-right => (2 1) 4 (5)
(define (zipper-squash-right z)
 "Squash the value at point and close the gap
 with a value from right."
 (make-zipper
  (zipper-left z)
  (car (zipper-right z))
  (cdr (zipper-right z))))
(define (zipper-squash-left z)
 "Squash the value at point and close the gap
 with a value from left."
 (make-zipper
  (cdr (zipper-left z))
  (car (zipper-left z))
  (zipper-right z)))
添加一些无聊的测试函数

;; Testing for the end points of the zipper.
(define (zipper-left-end? z)
 "Check whether the zipper is at the left end."
 (eq? '() (zipper-left z)))
(define (zipper-right-end? z)
 "Check whether the zipper is at the right end."
 (eq? '() (zipper-right z)))
。。。我们来到了我答案的核心:“从拉链中拉出”一张清单。把拉链想象成绳子上的记号。当你拉那个标记时,绳子的两部分会折叠在一起。如果绳子上有标记(即数字),你首先会看到离你拉的标记最近的标记

;; Pull out a list from the current position of the
;; point.
(define (pull-list-from-zipper z)
 "Pull out a list from the current point of the
 zipper. The list will have the point as first value, followed
 by the one right to it, then the one left of it, then another
 one from the right and so on."
 (cond
  ((zipper-left-end? z) (cons (zipper-point z) (zipper-right z)))
  ((zipper-right-end? z) (cons (zipper-point z) (zipper-left z)))
  (else
   (let* ((p1 (zipper-point z))
             (z1 (zipper-squash-right z))
             (p2 (zipper-point z1))
             (z2 (zipper-squash-left z1)))
    (cons p1 (cons p2 (pull-list-from-zipper z2)))))))
请注意,还有第二个变量,它将首先取左值,然后取右值

这样,编写
展开
就变得很简单:将列表转换为拉链,移动到中间值,然后拉动:

;; What we wanted to to.
(define (unwind l)
 "Move to the mid and pull a list out of the list."
 (let ((steps (quotient (- (length l) 1) 2)))
  (pull-list-from-zipper
   ((repeated zipper-move-right steps) (zipper-from-list l)))))
就性能而言,这需要完全遍历列表,再加上将拉链移动一半距离,当然拉出一个新列表将在
O(n)
中。上面的代码不是为了性能(尾部递归…)而优化的,而是为了易于理解

一个完整的例子是


最后请注意,拉链可以在点上没有值的情况下实现,但点在两个值之间(例如,在emacs中就是这样),但我想接近Haskell版本。

我们可以假设列表中有偶数个元素吗?@scarLópez实际上我们不能。。我忘了提那件事了。如果它有一个奇数,它应该从最中间的元素开始,然后像正常的一样继续,如果这有意义的话。对任何人来说:我感觉非常迷恋函数式编程风格,如果有人能给我一些关于一般方法/事情的建议,让我“开始”如何从伪代码角度找出最佳方法,这也是非常有用的。指针对于列表来说,当在其上递归时,不要考虑第n个或最后一个单元。这通常是不必要的。考虑基本情况并从那里递归。哇,这很有效。。。我必须仔细阅读此代码!我刚刚编辑了一个我正在做的尝试,通过将列表一分为二以不同的方式解决了这个问题。你介意看一看,告诉我我是否接近了吗?(我只是想知道投入的努力是否在正确的轨道上,或者我是否应该完全放弃它,只研究你的解决方案)。无论如何,非常感谢您的帮助。@HC_u检查我的新解决方案!哇,肯定更简洁了。令人惊叹的。。。对我来说,有很多东西需要接受,我现在会仔细看一看:)谢谢@你的解决方案可能需要稍加润色。它到处使用
length
(那是什么
999);; Testing for the end points of the zipper.
(define (zipper-left-end? z)
 "Check whether the zipper is at the left end."
 (eq? '() (zipper-left z)))
(define (zipper-right-end? z)
 "Check whether the zipper is at the right end."
 (eq? '() (zipper-right z)))
;; Pull out a list from the current position of the
;; point.
(define (pull-list-from-zipper z)
 "Pull out a list from the current point of the
 zipper. The list will have the point as first value, followed
 by the one right to it, then the one left of it, then another
 one from the right and so on."
 (cond
  ((zipper-left-end? z) (cons (zipper-point z) (zipper-right z)))
  ((zipper-right-end? z) (cons (zipper-point z) (zipper-left z)))
  (else
   (let* ((p1 (zipper-point z))
             (z1 (zipper-squash-right z))
             (p2 (zipper-point z1))
             (z2 (zipper-squash-left z1)))
    (cons p1 (cons p2 (pull-list-from-zipper z2)))))))
;; What we wanted to to.
(define (unwind l)
 "Move to the mid and pull a list out of the list."
 (let ((steps (quotient (- (length l) 1) 2)))
  (pull-list-from-zipper
   ((repeated zipper-move-right steps) (zipper-from-list l)))))