【问题标题】:Sort list into sublists将列表排序为子列表
【发布时间】:2018-07-19 17:58:44
【问题描述】:

我正在尝试创建一个程序,该程序对列表进行排序,然后将列表的每个部分分组为单独的列表并将其输出到列表列表中。这是一个应该更清楚的检查:

> (sort-lists > '())
empty

> (sort-lists < '(1 2 3))
(list (list 1 2 3))

> (sort-lists >= '(2 2 2 2))
(list (list 2 2 2 2))

> (sort-lists < '(5 4 3 2 1))
(list (list 5) (list 4) (list 3) (list 2) (list 1))

> (sort-lists < '(1 2 3 4 2 3 4 5 6 1 2 9 8 7))
(list
 (list 1 2 3 4)
 (list 2 3 4 5 6)
 (list 1 2 9)
 (list 8)
 (list 7))

这是我所拥有的:

(define (sort-lists rel? ls)
  (cond
    [(empty? ls) '()]
    [(rel? (first ls) (first (rest ls)))
     (list (cons (first ls) (sort-lists rel? (rest ls))))]
    [else (cons (first ls) (sort-lists rel? (rest (rest ls))))]))

我的 (first (rest ls)) 部分有问题,因为如果没有 first of rest 则它会给出错误,与 rest of rest 相同。

在 ISL+ 中,这也必须是一个没有任何帮助程序的单程函数。任何帮助都会很棒。

有没有办法使用local把递归子问题的解合并到一个ans变量中,然后补全答案。所以对于(sort-lists &lt; '(1 2 3 4 2 3 4 5 6 1 2 9 8 7)),你可以定义 ans 是运行(sort-lists &lt; '(2 3 4 2 3 4 5 6 1 2 9 8 7)) 的结果,即'((2 3 4) (2 3 4 5 6) (1 2 9) (8) (7)).

【问题讨论】:

    标签: scheme racket


    【解决方案1】:

    我不会真正称其为排序,而是某种类型的分区。您正在尝试收集已根据谓词排序的最长连续元素序列。我知道您说过您必须将所有这些都捆绑到一个函数中,但是首先将其编写为单独的函数,然后将它们组合成一个可能要容易得多。

    在解决此问题时,将其分解为子任务可能会有所帮助。首先,在最高级别,当列表进入时,有一些升序元素的初始前缀,然后是其余元素。结果应该是第一个前缀的列表,然后是处理其余元素的结果。这给了我们这样的结构:

    (define (slice predicate lst)
      (if (empty? lst)
          ;; If lst is empty, then there no contiguous 
          ;; subsequences within it, so we return '() 
          ;; immediately.
          '()
          ;; Otherwise, there are elements in lst, and we 
          ;; know that there is definitely a prefix and
          ;; a tail, although the tail may be empty. Then
          ;; the result is a list containing the prefix,
          ;; and whatever the sliced rest of the list is.
          (let* ((prefix/tail (ordered-prefix predicate lst))
                 (prefix (first prefix/tail))
                 (tail (second prefix/tail)))
            (list* prefix (slice predicate tail)))))
    

    我希望该函数中的逻辑相对清晰。唯一可能有点不寻常的是执行顺序绑定的 let* 和与 **cons 相同的 list**。还有一个我们还没有定义的函数的引用,ordered-prefix。它的任务是返回一个包含两个值的列表;第一个是列表的有序前缀,第二个是该前缀之后的列表尾部。现在我们只需要编写那个函数:

    (define (ordered-prefix predicate lst)
      (cond
        ;; If the list is empty, then there's no prefix,
        ;; and the tail is empty too.
        ((empty? lst)
         '(() ()))
        ;; If the list has only one element (its `rest` is
        ;; empty, then the prefix is just that element, and 
        ;; the tail is empty.
        ((empty? (rest lst))
         (list (list (first lst)) '()))
        ;; Otherwise, there are at least two elements, and the
        ;; list looks like (x y zs...).
        (else 
         (let ((x (first lst))
               (y (second lst))
               (zs (rest (rest lst))))
           (cond
             ;; If x is not less than y, then the prefix is (x),
             ;; and the tail is (y zs...).
             ((not (predicate x y))
              (list (list x) (list* y zs)))
             ;; If x is less than y, then x is in the prefix, and the 
             ;; rest of the prefix is the prefix of (y zs...).  
             (else 
              (let* ((prefix/tail (ordered-prefix predicate (list* y zs)))
                     (prefix (first prefix/tail))
                     (tail (second prefix/tail)))
                (list (list* x prefix) tail))))))))
    

    现在,这足以让 slice 工作:

    (slice < '())                ;=> ()
    (slice < '(1 2 3 4 2 3 4 5)) ;=> ((1 2 3 4) (2 3 4 5))
    

    不过,这并不是一个功能。为此,您需要将 ordered-prefix 的定义放入 slice 的定义中。您可以使用 let 在其他函数中绑定函数,例如:

    (define (repeat-reverse lst)
      (let ((repeat (lambda (x)
                      (list x x))))
        (repeat (reverse lst))))
    

    (repeat-reverse '(1 2 3)) ;=> ((3 2 1) (3 2 1))
    

    但是,这不适用于 ordered-prefix,因为 ordered-prefix 是递归的;它需要能够引用自己。您可以使用 letrec 来做到这一点,它允许函数引用自己。例如:

    (define (repeat-n-reverse lst n)
      (letrec ((repeat-n (lambda (x n)
                           (if (= n 0) 
                               '()
                               (list* x (repeat-n x (- n 1)))))))
        (repeat-n (reverse lst) n)))
    

    (repeat-n-reverse '(1 2 3) 3)     ;=> ((3 2 1) (3 2 1) (3 2 1))
    (repeat-n-reverse '(x y) 2)       ;=> ((y x) (y x))
    (repeat-n-reverse '(a b c d e) 0) ;=> ()
    

    好的,现在我们准备好将它们放在一起。 (由于 ordered-prefix 现在定义在 within slice,它已经可以访问谓词,我们可以将它从参数列表中删除,但仍然使用它。)

    (define (slice predicate lst)
      (letrec ((ordered-prefix
                (lambda (lst)
                  (cond
                    ((empty? lst)
                     '(() ()))
                    ((empty? (rest lst))
                     (list (list (first lst)) '()))
                    (else 
                     (let ((x (first lst))
                           (y (second lst))
                           (zs (rest (rest lst))))
                       (cond
                         ((not (predicate x y))
                          (list (list x) (list* y zs)))
                         (else 
                          (let* ((prefix/tail (ordered-prefix (list* y zs)))
                                 (prefix (first prefix/tail))
                                 (tail (second prefix/tail)))
                            (list (list* x prefix) tail))))))))))
        (if (empty? lst)
            '()
            (let* ((prefix/tail (ordered-prefix lst))
                   (prefix (first prefix/tail))
                   (tail (second prefix/tail)))
              (list* prefix (slice predicate tail))))))
    

    这也是相对有效的。它不会分配任何不必要的数据,除了为了清楚起见我使用 (list* y zs) 的地方,那里的值与 (rest lst) 相同。您可能应该更改它,但为了清楚起见,我想保持原样。

    唯一的性能考虑是这不是尾递归,所以你使用了更多的堆栈空间。为了解决这个问题,您需要将递归转换为反向构建列表的形式,然后在返回时将其反转。这就是我在原版中所做的(您仍然可以查看编辑历史记录),但对于看似学术的练习来说,这可能有点矫枉过正。

    【讨论】:

    • 我正在查看这个,但其中大部分没有意义。我们还没有完成 letrec、loop 或 let。我查找了 letrec 和 let ,它们看起来像是本地函数和 lambda 函数,但是你在 letrec 中有一个 letrec 和 "^" 前缀,这没有多大意义。有没有一种语言过于简单的方法?
    • @Ryan 实际上,我清理了一些代码。 letrec 与 let 类似,只是它绑定的变量在其他绑定的范围内。例如,您可以这样做 (letrec ((p (lambda (n) (p n)))) (p 10)) 将是一个无限循环,因为 p 绑定到一个调用 p 的函数(它本身)。 loop 没什么特别的;它只是绑定到函数的另一个变量。我只使用了名称loop,因为它是一个本质上执行循环的递归函数。变量上的 ^ 也不做任何事情;我只是用它来表示实际需要的值
    • @Ryan 在返回之前被反转。在许多列表处理练习中,更容易以相反的顺序构建结果,然后在返回时将其反转。因此,在从 (1 2 3 1) 构建 (1 2 3) 时,我们将逐步构建前缀/尾部组合 ()/(1 2 3 1),然后是 (1)/(2 3 1),然后(2 1)/(3 1),然后是(3 2 1)/(1),然后我们反转前缀返回(1 2 3)/(1)。
    • 好的。我仍然不明白 letrec 如何将其转换为本地,但我注意到您使用了 reverse 并且它看起来像其他此类函数多次通过列表而不是单次传递,这是主要的部分。这是很好的代码,但是由于所有的通道都带有反向和其他功能,所以在长列表上花费的时间太长。
    • Reverse 永远不会被主列表调用,这只会通过主列表。除非你可以使用 set!和定车!还有 set-cdr!,我认为大多数解决方案要么反向构建结果,要么复制大量中间数据。
    【解决方案2】:

    您想将列表分解为最长的升序数字序列。而要在 ISL+ 中完成,一次性完成

    这是在逻辑编程伪代码(好吧,Prolog)中完成的:

    runs([],  [[] ]).
    runs([A], [[A]]).
    runs([A,B|C], R) :- 
       (   A > B   ->  runs([B|C],  S  ), R=[[A  ]|S]   
       ;   true    ->  runs([B|C],[D|S]), R=[[A|D]|S]   ).
    

    这在类似 Scheme 的伪代码中也是如此(好吧,完整的 Racket):

    (define (runs xs)
       (match xs 
         ((list )  '(()))
         ((list A)  (list (list A)))
         ((list A B C ...)
            (cond
              ((> A B)
                 (let ([S (runs (cons B C))])
                    (cons (list A) S)))
              (else
                 (let ([d_s (runs (cons B C))])
                    (let ([D (car d_s)]
                          [S (cdr d_s)])
                       (cons (cons A D) S))))))))
    

    您要做的就是将其融入 ISL+ 语言。我不知道 + 代表什么,但在“使用 lambda 的中级学生”语言中肯定允许使用 lambda 构造。这让我们可以模拟嵌套 lets 的分阶段分配

              ( (lambda (d_s)
                   ( (lambda (D S)
                         (cons (cons A D) S))
                     (car d_s)
                     (cdr d_s)))
                (runs (cons B C)))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-01
      • 2019-02-11
      • 1970-01-01
      • 1970-01-01
      • 2021-06-28
      • 2010-11-02
      • 2015-03-19
      相关资源
      最近更新 更多