Как разбить список на куски одинакового размера в Racket (Схема)? - PullRequest
5 голосов
/ 04 января 2012

Пример:
Как преобразовать список:
'(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)

В список списков:
'((0 1 2 3) (4 5 6 7) (8 9 10 11) (12 13 14 15))

Основываясь на ответах, предоставленных здесь до сих пор, я пришел к такому выводу:

Сначала определите функцию для получения до 'n' элементов из начала списка:

(define (take-up-to n xs)
  (define (iter xs n taken)
    (cond
      [(or (zero? n) (empty? xs)) (reverse taken)]
      [else (iter (cdr xs) (- n 1) (cons (car xs) taken))]))
  (iter xs n '()))

Вторая - аналогичная функция для остальной части списка:

(define (drop-up-to n xs)
  (define (iter xs n taken)
    (cond
      [(or (zero? n) (empty? xs)) xs]
      [else (iter (cdr xs) (- n 1) (cons (car xs) taken))]))
  (iter xs n '()))

Это можно было бы сделать как одну функцию, которая возвращает два значения, и Racket имеет функцию split-at, которая дает тот же результат, но я сделал это в качестве упражнения.

ps.Является ли это правильным использованием хвостовой рекурсии?

Чем разделение на части можно записать так:

(define (split-into-chunks n xs)
  (if (null? xs)
      '()
      (let ((first-chunk (take-up-to n xs))
            (rest-of-list (drop-up-to n xs)))
        (cons first-chunk (split-into-chunks n rest-of-list)))))

pps.Может ли этот быть улучшен еще больше или он «достаточно хорош»?

Ответы [ 6 ]

6 голосов
/ 04 января 2012

В Scheme есть общая служебная функция в библиотеке SRFI-1 (которую предлагает Racket, но я не помню, как ее импортировать), которая называется take, которая принимает начальную n элементов из списка:

(take 4 '(0 1 2 3 4 5 6 7 8))
=> '(0 1 2 3)

В этой же библиотеке есть функция с именем drop, которая удаляет начальные n элементы из списка:

(drop 4 '(0 1 2 3 4 5 6 7 8))
=> '(4 5 6 7 8)

Вы можете разбить проблему на более мелкие части, используя такие функции.Итак, первое (, но неверное ) приближение к решению вашей проблемы будет следующим:

(define (split-into-chunks n xs)
  (if (null? xs)
      '()
      (let ((first-chunk (take n xs))
            (rest (drop n xs)))
        (cons first-chunk (split-into-chunks n rest)))))

Как я уже отметил, однако, это решение является неоптимальным.Зачем?Потому что (take n xs) выдает ошибку, если в списке xs меньше n элементов;переводится к вашей проблеме, если список содержит не-1021 * несколько элементов, вы получаете ошибку.Однако вы можете исправить это, написав пару функций take-up-to и drop-up-to, которые работают как take и drop, но не имеют этой проблемы.Поэтому пример использования функций будет выглядеть следующим образом:

(take-up-to 4 '(0 1 2))
=> '(0 1 2)

(drop-up-to 4 '(0 1 2))
=> '()

Это столько, сколько я собираюсь вам сказать.Я предлагаю вам сделать следующее:

  • Написать свои собственные реализации take, drop, take-up-to и drop-up-to и использовать их для написания функции, которую вы пытаетесь реализовать.
  • Просмотрите документацию для библиотеки SRFI-1 и ознакомьтесь с функциями, которые там выполняются.Многие из этих проблем со списком разбиваются на простые комбинации этих функций, поэтому вы хотите узнать о них.
  • Узнайте, как импортировать эту библиотеку в Racket.(Не могу вам помочь.)
  • В качестве упражнения попробуйте написать свои собственные реализации некоторых функций SRFI-1.(Можно упростить их немного для упражнения; например, хотя многие из этих функций будут работать с несколькими аргументами списка, для целей упражнения вполне нормально написать версию, которая работает только с одним списком.)

РЕДАКТИРОВАТЬ: Вот простая реализация take-up-to:

(define (take-up-to n xs)
  (if (or (zero? n) (null? xs))
      '()
      (cons (car xs) (take-up-to (- n 1) (cdr xs)))))

Это можно улучшить еще немного, используя только хвостовые вызовы (и, следовательно, запустить впостоянное пространство).Это еще одно упражнение.

3 голосов
/ 30 апреля 2014

а для меня это что-то вроде

(define (split-by lst n)
   (if (not (empty? lst))
       (cons (take lst n) (split-by (drop lst n) n))
       '() ))

например

(split-by '(3 2 1 43 54 25 100 -14 -42) 3)

выход

'((3 2 1) (43 54 25) (100 -14 -42))
3 голосов
/ 05 января 2012

Вы задаете хороший вопрос общего назначения, но я думаю, что вам здесь нужно то, что использует байтовые строки вместо списков. Вот некоторый код (включая проверку ошибок) вместе с тестовым примером:

#lang racket

(require rackunit)

;; given a byte string, split it into a vector of byte-strings
;; of length 4
(define (split-bytes bytes)
  (define len (bytes-length bytes))
  (unless (= 0 (modulo len 4))
    (raise-type-error 'split-bytes
                      "byte string of length divisible by 4"
                      0 bytes))
  (for/vector ([i (in-range (/ len 4))])
     (subbytes bytes (* 4 i) (* 4 (add1 i)))))

(check-equal?
 (split-bytes
  #"hhoh.h9ew,n49othsv97")
 (vector #"hhoh"
         #".h9e"
         #"w,n4"
         #"9oth"
         #"sv97"))
2 голосов
/ 05 января 2012

Если вы хотите лучше справляться с такими проблемами, я бы высоко рекомендовал Маленький интриган . Это научит вас мыслить в терминах, которые помогут решить эти проблемы, и потребуется всего несколько часов, чтобы пройти от корки до корки.

1 голос
/ 06 августа 2014

Если вы ищете хвостово-рекурсивное решение, один из подходов заключается в использовании с именем let :

(define (group n xs)
  (let loop ([grouped '()] [xs xs])
    (if (empty? xs)
        (reverse grouped)
        (loop (cons (take xs n) grouped)
              (drop xs n)))))

Однако это не работает, если xs будет иметь оставшиеся элементы, поэтому нам нужно добавить случай, который проверяет это:

(define (group n xs)
  (let loop ([grouped '()] [xs xs])
    (cond
      [(empty? xs)
       (reverse grouped)]
      [(<= (length xs) n)
       (loop (cons xs grouped) '())]
      [else (loop (cons (take xs n) grouped)
                  (drop xs n))])))

Это работает, но мы можем сделать лучше. Проблема здесь в том, что вычисление (length xs) выполняется за линейное время , потому что единственный способ найти длину простого списка - это просмотреть весь список. Поскольку это внутри цикла, который выполняется несколько раз пропорционально размеру xs, этот код выполняется за квадратичное время, когда его должно быть просто выполнить за линейное время. Мы можем обойти эту проблему, рассчитав длину xs один раз, а затем вычтя из нее n каждую итерацию:

(define (group n xs)
  (let loop ([grouped '()] [xs xs] [l (length xs)])
    (cond
      [(empty? xs)
       (reverse grouped)]
      [(<= l n)
       (loop (cons xs grouped) '() 0)]
      [else (loop (cons (take xs n) grouped)
                  (drop xs n)
                  (- l n))])))

И мы вернулись к линейному времени, сохраняя при этом хвостовую рекурсию и, следовательно, постоянное пространство. Однако мы можем сделать еще одно улучшение. Функция Racket split-at объединяет функции take и drop и возвращает оба списка в виде двух значений. Чтобы использовать функции, возвращающие несколько значений, мы можем использовать let-values:

(define (group n xs)
  (let loop ([grouped '()] [xs xs] [l (length xs])
    (cond
      [(empty? xs)
       (reverse grouped)]
      [(<= l n)
       (loop (cons xs grouped) '() 0)]
      [else (let-values ([(taken dropped) (split-at xs n)])
              (loop (cons taken grouped)
                    dropped
                    (- l n)))])))

Это немного быстрее, потому что split-at может избежать повторной работы по игнорированию первых n элементов в части drop его функциональности, потому что эти элементы уже будут использованы take. Однако этот код не учитывает неправильный ввод данных пользователем. Если пользователь предоставит n больше, чем длина xs, он выдаст ошибку, когда он должен вернуть (list xs). Это достаточно просто проверить, но наш код очень расширяется вправо со всем этим вложением. В дополнение к проверке этого мы можем разделить цикл на внутренне определенную функцию:

(define (group n xs)
  (define (loop grouped xs l)
    (cond
      [(empty? xs)
       (reverse grouped)]
      [(<= l n)
       (loop (cons xs grouped) '() 0)]
      [else (let-values ([(taken dropped) (split-at xs n)])
              (loop (cons taken grouped)
                    dropped
                    (- l n)))]))
  (let ([l (length xs)])
    (if (>= n l)
        (list xs)
        (loop '() xs l))))

Эта функция является хвостовой рекурсивной, не вычисляет (length xs) более одного раза, гарантирует, что (group 4 '(1 2 3)) оценивается как '((1 2 3)), и неравномерная группировка так, что (group 2 '(1 2 3) оценивается до '((1 2) (3)), работает в линейном времени и с постоянной пространство.

1 голос
/ 05 августа 2014

Другой способ сделать это - предоставить функцию map-n более высокого порядка, которая берет n значений из списка и применяет к ним функцию:

(define (map-n n fn l . lists)
  (if (any (lambda(l)(< (length l) n)) (cons l lists))
      '()
      (cons (apply fn (append-map (lambda(l)(take l n)) (cons l lists)))
            (apply map-n n fn (map (lambda(l)(drop l n)) (cons l lists))))))

(e.g. (map-n 4 + '(1 2 3 4 5 6 7 8)) ===> (10 26))
(e.g. (map-n 3 (lambda (a b c) a) '(1 2 3 4 5 6)) ===> (1 4))

Имея эту функцию, можно просто

(define (split-by n l)
  (map-n n list l))

Недостатком может быть то, что если длина списка не делится на n, остаток будет отклонен от результата.

Еще один интересный способ - создать функциюкоторый разбивает список на куски произвольного размера:

(define (chunk-list l . chunk-sizes)
  (assert (and (list? l)
               (every natual? chunk-sizes)
               (<= (fold + 0 chunk-sizes) (length l))))
  (let loop ((result '())
             (sizes chunk-sizes)
             (rest l))
    (match sizes
      (()
       (reverse result))
      ((size . sizes)
       (let-values (((this rest) (split-at rest size)))
         (loop `(,this ,@result) sizes rest))))))

(e.g. (chunk-list '(1 2 3 4 5 6 7 8 9 10) 0 1 2 3 4)
      ===> (() (1) (2 3) (4 5 6) (7 8 9 10))

и затем определяет разбиение с помощью make-list:

(define (split-by n l)
  (let ((size (quotient (length l) n)))
    (apply chunk-list l (make-list size n))))

Обратите внимание, что в определении map-n используется *Функция 1016 * из srfi-1, а chunk-list использует шаблонный подборщик Алекса Шинна (хотя его можно легко переписать, используя обычный if, eq ?, car и cdr)

...