8 Stimmen

Wie teilt man in Racket (Scheme) eine Liste in gleich große Stücke auf?

Beispiel:
Wie man die Liste umwandelt:
'(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15)

In die Liste der Listen:
'((0 1 2 3) (4 5 6 7) (8 9 10 11) (12 13 14 15))

Auf der Grundlage der bisherigen Antworten habe ich mir Folgendes überlegt:

Definieren Sie zunächst eine Funktion, die bis zu 'n' Elemente vom Anfang der Liste aufnimmt:

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

Die zweite ist eine ähnliche Funktion für den Rest der Liste:

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

Das hätte man auch als eine Funktion machen können, die zwei Werte zurückgibt, und Racket hat eine Funktion 'split-at', die das gleiche Ergebnis liefert, aber ich habe das als Übung gemacht.

ps. Ist dies die korrekte Anwendung der Schwanzrekursion?

Dann kann Split-in-Chunks wie folgt geschrieben werden:

(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. Kann diese noch weiter verbessert werden oder ist sie "gut genug"?

8voto

Luis Casillas Punkte 29236

In Scheme gibt es eine gemeinsame Nutzenfunktion, in der SRFI-1-Bibliothek (das Racket anbietet, aber ich weiß nicht mehr, wie man es importiert), genannt take die die anfängliche n Elemente aus einer Liste:

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

In der gleichen Bibliothek gibt es auch eine Funktion namens drop die die anfängliche n Elemente aus einer Liste:

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

Sie können das Problem in kleinere Teile zerlegen, indem Sie Funktionen wie diese verwenden. So kann eine erste ( aber falsch ) eine Annäherung an die Lösung Ihres Problems wäre dies:

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

Wie ich bereits erwähnt habe, ist diese Lösung jedoch suboptimal. Warum? Weil (take n xs) gibt eine Fehlermeldung aus, wenn die Liste xs hat weniger als n Elemente; übersetzt auf Ihr Problem, wenn die Liste ein Nicht n mehrere Elemente, erhalten Sie einen Fehler. Sie können dies jedoch beheben, indem Sie ein Funktionspaar schreiben, take-up-to y drop-up-to die funktionieren wie take y drop haben aber dieses Problem nicht. Ein Beispiel für die Verwendung der Funktionen würde so aussehen:

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

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

Das ist alles, was ich Ihnen sagen werde. Ich schlage vor, dass Sie diese Dinge tun:

  • Schreiben Sie Ihre eigenen Implementierungen von take , drop , take-up-to y drop-up-to und verwenden Sie diese, um die Funktion zu schreiben, die Sie implementieren wollen.
  • Durchblättern die Dokumentation für die SRFI-1-Bibliothek und machen Sie sich mit den Funktionen vertraut, die dort zur Verfügung stehen. Viele dieser Listenprobleme lassen sich in einfache Kombinationen dieser Funktionen zerlegen, so dass Sie sie kennen sollten.
  • Erfahren Sie, wie Sie diese Bibliothek in Racket importieren können. (Da kann ich Ihnen nicht helfen.)
  • Versuchen Sie als Übung, Ihre eigenen Implementierungen einiger der SRFI-1-Funktionen zu schreiben. (Es ist in Ordnung, sie für die Übung ein wenig zu vereinfachen; obwohl viele dieser Funktionen mit mehreren Listenargumenten arbeiten, ist es für die Übung in Ordnung, eine Version zu schreiben, die nur eine Liste verarbeitet).

EDIT: Hier ist eine einfache Implementierung von 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)))))

Es ist möglich, dies noch weiter zu verbessern, um nur Schwanzaufrufe zu verwenden (und somit in konstantem Raum zu laufen). Das bleibt als weitere Übung übrig.

6voto

yari Punkte 155

Bei mir ist es etwa so

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

zum Beispiel

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

ergibt

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

3voto

John Clements Punkte 16220

Sie stellen eine nette Frage für allgemeine Zwecke, aber ich denke, was Sie hier wollen, ist etwas, das Byte-Zeichenfolgen verwendet, anstatt Listen. Hier ist etwas Code (einschließlich Fehlerprüfung), zusammen mit einem Testfall:

#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"))

2voto

Maciek Godek Punkte 162

Eine andere Möglichkeit besteht darin, eine Funktion höherer Ordnung map-n bereitzustellen, die n Werte aus einer Liste nimmt und eine Funktion auf sie anwendet:

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

Wenn man diese Funktion hat, kann man einfach

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

Der Nachteil könnte sein, dass, wenn die Länge der Liste nicht durch n teilbar ist, der Rest vom Ergebnis abgezogen wird.

Eine andere Möglichkeit besteht darin, eine Funktion zu erstellen, die eine Liste in Stücke beliebiger Größe zerlegt:

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

und definieren Sie dann split-by mit make-list:

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

Beachten Sie, dass die Definition von map-n verwendet die any Funktion von srfi-1 y chunk-list verwendet Alex Shinns Mustervergleicher (obwohl es leicht mit einfachen if, eq?, car und cdr umgeschrieben werden könnte)

2voto

Jack Punkte 2193

Wenn Sie nach einer tail-recursiven Lösung suchen, ist ein Ansatz, die benannt lassen :

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

Dies schlägt jedoch fehl, wenn xs würden Elemente übrig bleiben, also müssen wir einen Fall hinzufügen, der dies überprüft:

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

Das funktioniert, aber wir können es besser machen. Das Problem dabei ist, dass die Berechnung (length xs) läuft ein lineare Zeit Denn die einzige Möglichkeit, die Länge einer einfachen Liste zu ermitteln, besteht darin, die gesamte Liste abzuarbeiten. Da dies innerhalb einer Schleife geschieht, die proportional zur Größe der Liste xs Dieser Code läuft in quadratischer Zeit, obwohl er eigentlich in linearer Zeit zu erledigen wäre. Wir können dieses Problem umgehen, indem wir die Länge von xs einmal und subtrahiert dann n von ihm bei jeder Iteration:

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

Und schon sind wir wieder bei linearer Zeit, wobei die Schwanzrekursion und damit der Platzbedarf konstant bleiben. Es gibt jedoch noch eine weitere Verbesserung, die wir vornehmen können. Die Racket-Funktion split-at kombiniert die Funktionalität von take y drop und gibt beide Listen als zwei Werte zurück. Um Funktionen, die mehrere Werte zurückgeben, zu verwenden, können wir 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)))])))

Dies ist etwas schneller, weil split-at kann die Wiederholung der Arbeit des Ignorierens des ersten n Elemente im drop Teil der Funktionalität, da diese Elemente bereits von take . Dieser Code berücksichtigt jedoch keine falschen Benutzereingaben. Wenn ein Benutzer eine n größer als die Länge von xs wird eine Fehlermeldung ausgegeben, obwohl sie eigentlich Folgendes zurückgeben sollte (list xs) . Es ist einfach genug, dies zu überprüfen, aber unser Code wird durch die Verschachtelung nach rechts hin furchtbar lang. Zusätzlich zu dieser Überprüfung können wir die Schleife in eine intern definierte Funktion aufteilen:

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

Diese Funktion ist schwanzrekursiv, berechnet nicht (length xs) mehr als einmal, stellt sicher, dass (group 4 '(1 2 3)) wertet aus zu '((1 2 3)) und eine ungerade Gruppierung, so dass (group 2 '(1 2 3) wertet aus zu '((1 2) (3)) und läuft in linearer Zeit und konstantem Raum.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X