功能程序 - 编写一个函数从中间重新排列数组

时间:2015-11-05 23:41:31

标签: functional-programming scheme racket

任务是编写一个函数,该函数采用(7 8 2 9 5 6)之类的列表,然后从中心“展开”它,将其重新排列为2 9然后2 9 8 5,最后结束输出为2 9 8 5 7 6

我已经想出了粗伪代码:

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

所以,

7 8 2 9 5 6 - >

7 8 2 9 5 - > 6

8 2 9 5 - > 7 6

8 2 9 - > 5 7 6

2 9 - > 8 5 7 6

2 - > 9 8 5 7 6

- > 2 9 8 5 7 6更正最终输出

这是我的代码到目前为止(不是很远)

(define (lastElement L) ;returns the last element of array L
 (if (null? (cdr L)) (car L)
 (lastElement (cdr L))))

(define (unwind U)
 (if (null? U) ( (cons (lastElement L) '() )) ;generates a syntax error
 (U)
  )

在我的语法错误评论中,我想要做的是......如果array U!null,那么将lastElement L添加到新数组......然后以某种方式从那里开始我必须弄清楚如何从lastElement L移除U然后获取第一个元素并将其删除..我认为这将通过car和/或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))

4 个答案:

答案 0 :(得分:2)

我拿了一个经典的海龟和野兔递归将这个名单分成两半。你使用cdrcddr cdr的{​​{1}}进行操作,这样当快速重复的一半为空或单个列表时,较慢的一半会给你下半部分的清单。我也积累了一个反转的前半部分,因为它后来派上用场。

cdr

答案 1 :(得分:2)

我想添加另一个解决方案,使用我在Haskell中所知的zipper

基本上,列表上的拉链包含一个点,标记当前焦点,列出该点左边的内容以及另一个列表,其中列出了该点。

;; 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 ++等语言中的指针。您可以向左或向右移动,并且可以修改当前指向的值(无需昂贵的重建和遍历所有值,就像使用简单列表一样)。

;; Movement on zippers.
;; (2 1) 3 (4 5)
;; move-right => (3 2 1) 4 (5)
;; move-left => (1) 2 (3 4 5)
(define (zipper-move-right z)
 "Return a zipper with the point moved one to the
 right."
 (make-zipper
  (cons (zipper-point z) (zipper-left z))
  (car (zipper-right z))
  (cdr (zipper-right z))))
(define (zipper-move-left z)
 "Return a zipper with the point moved one to the
 left."
 (make-zipper
  (cdr (zipper-left z))
  (car (zipper-left z))
  (cons (zipper-point z) (zipper-right z))))

现在有一件特别的事情,我希望我的拉链能够完成这项任务:在点处摧毁价值并用左侧或右侧的值填充产生的间隙:

;; 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)))))))

请注意,存在第二个变体,首先是左值,然后是右值。

这样,编写unwind变得微不足道:将列表转换为拉链,移动到中间值,然后拉:

;; 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)。上面的代码没有针对性能进行优化(尾递归..),但为了便于理解。

一个完整的例子是here

作为最后的注释,拉链可以在没有值的情况下实现,但是点在两个值之间(例如,在emacs中完成),但是我希望保持接近Haskell版本

答案 2 :(得分:1)

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

(define (unwind lst)
  (let loop ((lst lst)
             (acc '())
             (last? #t))
    (cond ((null? lst)
           acc)
          ((null? (cdr lst))
           (if last?
               (append acc lst)
               (cons (car lst) acc)))
          (last?
           (loop (drop-right lst 1)
                 (cons (last lst) acc)
                 #f))
          (else
           (loop (cdr lst)
                 (cons (car lst) acc)
                 #t)))))

请注意,我使用了几个内置函数来简化操作,尤其是appendlastdrop-right过程。关键的见解是传递一个布尔标志,指示每一步是否应该采用列表的第一个或最后一个元素,这甚至用于只剩下一个元素的情况。它按预期工作:

(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)

答案 3 :(得分:1)

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

(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)