Strumienie danych

Synthroid Without Prescription Inderal No Prescription Nexium For Sale Prevacid Generic Buy Elimite Online Prevacid Without Prescription Ultram No Prescription Prevacid For Sale Ultram Generic Buy Prednisone Online Dzisiaj skupimy się na rozwiązaniu ćwiczeń z drugiego rozdziału Wizard Booka. Na początku zajmiemy się tworzeniem struktur danych, a później tym, w czym podobno Lisp jest najlepszy: przetwarzaniem list.

2.2

Wprawka do dalszych ćwiczeń, nie wymaga szczególnego komentarza.
(define make-segment cons)
(define start-segment car)
(define end-segment cdr)
 
(define make-point cons)
(define x-point car)
(define y-point cdr)
 
(define (midpoint-segment segment)
  (make-point (average (x-point (start-segment segment))
                       (x-point (end-segment segment)))
              (average (y-point (start-segment segment))
                       (y-point (end-segment segment)))))
 
(define (average x y)
  (/ (+ x y) 2))

2.3

Poniższa implementacja zakłada, że prostokąt tworzymy podając współrzędne przeciwległych wierzchołków (punktów, utworzonych przy pomocy make-point). Warto zwrócić uwagę na kolejność definicji poszczególnych funkcji. Na początku zdefiniowałem funkcje obliczające obwód (circumference) i powierzchnię (area), korzystając z jeszcze nie istniejących definicji wysokości (height) i szerokości (width) prostokąta. Po tym przeszedłem do definicji tych dwóch brakujących funkcji, korzystając z nieistniejących jeszcze selektorów prostokąta (rectangle-p1 i rectangle-p2) i selektorów punktu (x-point i y-point). Wreszcie cztery selektory udało się zdefiniować korzystając wyłącznie z funkcji wbudowanych w język (cons, car i cdr). Wishful thinking to potężna technika, używajcie jej jednak z rozwagą.
Obwód prostokąta.
(define (circumference rectangle)
  (+ (* 2 (height rectangle))
     (* 2 (width rectangle))))
Powierzchnia prostokąta.
(define (area rectangle)
  (* (height rectangle)
     (width rectangle)))
Długość boku ułożonego wzdłuż osi X.
(define (height rectangle)
  (abs (- (y-point (rectangle-p1 rectangle))
          (y-point (rectangle-p2 rectangle)))))
Długość boku ułożonego wzdłuż osi Y.
(define (width rectangle)
  (abs (- (x-point (rectangle-p1 rectangle))
          (x-point (rectangle-p2 rectangle)))))
Konstruktory i selektory dla prostokąta.
(define make-rectangle cons)
(define rectangle-p1 car)
(define rectangle-p2 cdr)
Konstruktory i selektory dla punktu.
(define make-point cons)
(define x-point car)
(define y-point cdr)

2.4

Proste zadanie, które zaciera różnicę między kodem, a danymi.
(define (cdr z)
  (z (lambda (p q) q)))

2.5

Kolejna zabawna implementacja funkcji cons, car i cdr.
(define (dividable? a b)
  (= (remainder a b) 0))
 
(define (logn a b)
  (/ (log b) (log a)))
 
(define (cons x y)
  (* (expt 2 x)
     (expt 3 y)))
 
(define (car x)
  (if (dividable? x 3)
      (car (quotient x 3))
      (logn 2 x)))
 
(define (cdr x)
  (if (dividable? x 2)
      (cdr (quotient x 2))
      (logn 3 x)))

2.17

Mamy zdefiniować funkcję zwracającą listę z ostatnim elementem danej listy. Niezliczone ilości napisanych do tej pory funkcji przeglądających listy powodują, że nie musimy się nawet zastanawiać nad rozwiązaniem.
(define (last-pair seq)
  (if (null? (cdr seq))
    seq
    (last-pair (cdr seq))))

2.18

Odwrócenie listy? Nic prostszego.
(define (reverse sequence)
  (define (iter sequence result)
    (if (null? sequence)
        result
        (iter (cdr sequence)
              (cons (car sequence) result))))
  (iter sequence()))
Używając funkcji fold z SRFI 1 sprawa staje się jeszcze prostsza:
(require (only (lib “1.ss” “srfi”) fold))
(define (reverse sequence)
  (fold cons() sequence))

2.20

Zadanie znów proste, ale pokazuje jak definiować funkcje o dowolnej liczbie argumentów, co warto zapamiętać na przyszłość.
(require (only (lib “1.ss” “srfi”) filter))
 
(define (same-parity first . rest)
  (let ((parity (remainder first 2)))
    (cons first
          (filter (lambda (x) (= (remainder x 2) parity))
                  rest))))

2.23

(define (for-each proc seq)
  (if (null? seq)
      #t
      (let ((result (proc (car seq))))
        (for-each proc (cdr seq)))))
Być może znając już procedurę map kusiło by nas, by napisać:
(define (for-each proc seq)
  (begin
    (map proc seq)
    #t))
Jednak według standardu R5RS procedura map nie specyfikuje kolejności wykonania mapowań poszczególnych elementów. Przy ewaluacji wyrażenia (map 1+ '(1 2 3)) mogło by się okazać, że najpierw wykonywane jest wyrażenie (1+ 2), potem (1+ 3) i na końcu dopiero (1+ 1). Wbrew pozorom właściwość ta jest bardzo przydatna. Nietrudno sobie bowiem wyobrazić implementację map przeznaczoną dla komputerów wieloprocesorowych wykonująca obliczenia równolegle. Dopóki więc unikamy efektów ubocznych (ang. side-effects) przy mapowaniu, możemy w przyszłości przenieść nasze obliczenia na wiele procesorów, wymieniając jedynie definicję funkcji map.

2.24

Mamy pokazać trzy reprezentacje wyniku ewaluacji wyrażenia (list 1 (list 2 (list 3 4))) przez interpreter. Zacznijmy od tego, co wypisze sam interpreter:
(1 (2 (3 4)))
Teraz wynik ewaluacji w postaci drzewa:

drzewo

I wreszcie postać pudełkowo-wskaźnikowa:

pudełka

2.25

Musimy tylko przy pomocy car i cdr wydobyć z podanych list liczbę 7. Tak pewnie wygląda kod spagetti w Scheme.
  • (car (cdr (car (cdr (cdr(1 3 (5 7) 9))))))
  • (caar((7)))
  • (cadr (cadr (cadr (cadr (cadr (cadr(1 (2 (3 (4 (5 (6 7))))))))))))
Trochę oszukałem i użyłem funkcji caar, która jest tym samym co (car (car ...)), jak i funkcji cadr, która odpowiada kombinacji (car (cdr ...)).

2.27

Przy rozwiązaniu tego ćwiczenia pozwoliłem sobie znowu skorzystać z funkcji fold.
(require (only (lib “1.ss” “srfi”) fold))
 
(define (deep-reverse seq)
  (fold (lambda (element result)
           (cons (if (pair? element)
                     (deep-reverse element)
                     element)
                 result))()
         seq))

2.28

Tak przywykłem już do przydatnych funkcji z SRFI-1, że i tym razem nie mogłem się powstrzymać przed użyciem jednej z nich. Bohaterem tego zadania jest append-map.
(require (only (lib “1.ss” “srfi”) append-map))
 
(define (fringe tree)
  (if (pair? tree)
      (append-map fringe tree)
      (list tree)))

2.30 i 2.31

Najpierw rozwiążemy problem podnoszenia liści drzewa do kwadratu przy pomocy map, a potem wyodrębnimy z tej procedury ogólny schemat mapowania liści drzewa.
(define (square x) (* x x))
 
;; square-tree przy pomocy map
(define (square-tree seq)
  (if (pair? seq)
      (map square-tree seq)
      (square seq)))
 
;; tree-map do mapowania lisci drzewa
(define (tree-map proc tree)
  (if (pair? tree)
      (map (lambda (t) (tree-map proc t)) tree)
      (proc tree)))
 
;; square-tree przy pomocy tree-map
(define (square-tree seq)
  (tree-map square seq))

2.32

Odrobina teorii mnogości jeszcze nikomu nie zaszkodziła.
(define (subsets s)
  (if (null? s)
      (list nil)
      (let ((rest (subsets (cdr s))))
        (append rest
                (map (lambda (el) (cons (car s) el))
                     rest)))))
Działanie algorytmu najłatwiej wyjaśnić na przykładach. Jest tylko jeden zbiór zbioru pustego:
> (subsets ‘())
(())
Zbiór jednoelementowy ma dwa podzbiory:
> (subsets ‘(1))
(() (1))
Podzbiory zbioru dwuelementowego osiągamy poprzez zebranie wszystkich podzbiorów zbioru o jednoelementowego (odpowiada za to wyrażenie (rest (subsets (cdr s)))) i następnie dodanie do każdego z tych podzbiorów elementu pierwszego (wyrażenie (map (lambda (el) (cons (car s) el)) rest)).
> (subsets ‘(2 1))
(() (1) (2) (2 1))
Jak widać, wzięliśmy wszystkie podzbiory zbioru (1), czyli () i (1), a następnie utworzyliśmy nowe podzbiory dodając do tych poprzednich nowy element 2, osiągając (2) i (2 1). Podobnie rzecz wygląda dla zbiorów o większej ilości elementów:
> (subsets ‘(3 2 1))
(() (1) (2) (2 1) (3) (3 1) (3 2) (3 2 1))
Bierzemy wszystkie podzbiory zbioru (2 1) (czyli () (1) (2) (2 1) z kroku poprzedniego) i wrzucamy na początek każdego z nich trójkę. Połączenie tych dwóch zbiorów daje nam wynik.

2.33

Ciekawe jest, że odwzorowanie (map) można wyrazić przy pomocy kumulacji. Podobnie zdefiniować można funkcje append i length.
(define (map p sequence)
  (accumulate (lambda (x y) (cons (p x) y))() sequence))
 
(define (append seq1 seq2)
  (accumulate cons seq2 seq1))
 
(define (length sequence)
  (accumulate (lambda (x y) (+ y 1)) 0 sequence))

2.34

Tym razem uzupełniamy funkcję obliczającą wartość wielomianu za pomocą schematu Hornera.
(define (horner-eval x coefficient-sequence)
  (accumulate (lambda (this-coeff higher-terms)
                (+ (* x higher-terms) this-coeff))
              0
              coefficient-sequence))

2.35

Zliczanie liści w drzewie - prosta sprawa.
(define (sum seq)
  (accumulate + 0 seq))
 
(define (count-leaves t)
  (sum (map (lambda (x)
              (if (pair? x)
                  (count-leaves x)
                  1))
            t)))

2.36

Uzupełnić brakujący kod? Śmiesznie proste.
(define (accumulate-n op init seqs)
  (if (null? (car seqs))()
      (cons (accumulate op init (map car seqs))
            (accumulate-n op init (map cdr seqs)))))

2.37

W tym zadaniu korzystamy ze standardowego map i z funkcji accumulate-n zdefiniowanej w poprzednim zadaniu.
(define (matrix-*-vector m v)
  (map (lambda (x) (sum (map * x v))) m))
 
(define (transpose mat)
  (accumulate-n cons() mat))
 
(define (matrix-*-matrix m n)
  (let ((cols (transpose n)))
    (map (lambda (x) (matrix-*-vector cols x)) m)))

2.38

Znamy już prawostronne składanie (fold-right), teraz poznamy składanie lewostronne (fold-left). Wyniki wyrażeń są następujące:
  • (fold-right / 1 (list 1 2 3)) ; => 3/2 
     
  • (fold-left / 1 (list 1 2 3)) ; => 1/6 
     
  • (fold-right list() (list 1 2 3)) ; => (1 (2 (3 ()))) 
     
  • (fold-left list() (list 1 2 3)) ; => (((() 1) 2) 3) 
     
W tym momencie warto jednak zatrzymać się na chwilę i porównać sobie funkcje składania przedstawione w Wizard Booku i te z SRFI 1. Na początek warto zauważyć, że funkcje fold i fold-right z SRFI 1 potrafią obsłużyć kilka list, podczas gdy proste wersje zwijania z Wizard Booka przyjmują jako ostatni argument dokładnie jedną listę. Po za tym fold-right według SRFI i według panów z MIT działa identycznie. Wywołany jako:
(fold-right op initial list)
zwróci wynik ewaluacji
(op e1 (op e2 … (op en initial)))
gdzie e1, e2, …, en to kolejne elementy listy list. Inaczej rzecz się ma ze zwijaniem lewostronnym. fold SRFI-owe rozwinie się w :
(op en … (op e2 (op e1 initial)))
podczas, gdy to z Wizard Booka rozwinie się do postaci zgoła odmiennej:
(op (op (op initial e1) e2) … en)
Różnica powstaje z odmiennej kolejności przekazanych argumentów do funkcji op. Jak widać powyżej Wizard Bookowa wersja jako pierwszy argument stawia dotychczasowy wynik (w szczególności initial), zaś w przypadku SRFI na pierwszym miejscu stoi aktualny element iteracji. Dzięki temu, że ta różnica jest tak niewielka, z łatwością wyrazić można SICP-owe fold-left przy pomocy fold z SRFI 1:
(define (sicp-fold-left op initial sequence)
  (fold (lambda (x y) (op y x)) initial sequence))

2.41

Ostatnie ćwiczenie, którego rozwiązanie chciałbym dzisiaj przedstawić, dotyczy generowania permutacji i wybierania z nich podzbioru spełniającego pewny warunek. Zadanie 2.41 polega na znalezieniu zbioru takich uporządkowanych trójek liczb naturalnych (bez zera), których suma ma określoną wartość. Zadanie to można jednak uogólnić, co postaram się poniżej uczynić. Na początek kilka pomocniczych definicji, które znane są wam z poprzednich wpisów:
(define nil())
 
(define (accumulate op initial seq)
  (if (null? seq)
      initial
      (op (car seq)
          (accumulate op initial (cdr seq)))))
 
(define (sum seq)
  (accumulate + 0 seq))
 
(define (flatmap proc seq)
  (accumulate append nil (map proc seq)))
Będziemy również potrzebować funkcji remove i filter z SRFI-1:
(require (only (lib “1.ss” “srfi”) remove filter))
Przyda nam się również oparta na remove funkcja remove-= usuwająca z listy liczby równe danej liczbie:
(define (remove-= num l)
  (remove (lambda (x) (= x num)) l))
Przejdźmy nareszcie do definicji naszego problemu. Musimy znaleźć trójki niepowtarzających się dodatnich liczb całkowitych nie większych od N, których suma jest równa zadanej liczbie S. Znamy już więc sygnaturę naszej funkcji:
(define (uniq-triples-with-sum s n))
Graficznie:

Zakładając, że potrafimy wygenerować odpowiednie trójki liczb nie większych od N, możemy zdefiniować szukaną funkcję następująco:
(define (uniq-triples-with-sum s n)
  (filter (lambda (triple) (sum-is? s triple))
          (uniq-triples n)))
co na schemacie możemy przedstawić jako:

Definicja predykatu sum-is? jest prosta:
(define (sum-is? n seq)
  (= (sum seq) n))
uniq-triples to twardsza sztuka. Patrząc jednak na problem z odpowiedniej perspektywy można spostrzec, że zbiór unikalnych uporządkowanych trójek nie większych niż liczba N można otrzymać generując wszystkie możliwe wariacje bez powtórzeń zbioru liczb od 1 do N. Zapisując to w Scheme:
(define (uniq-triples n)
  (if (< n 3)
      (error “n must be greater than 3, otherwise we won’t be able to generate a triple”)
      (permutations (enumerate n) 3)))
Schemat zaś przedstawia się następująco:

Zredukowaliśmy problem do implementacji dwóch funkcji: enumerate i permutations. Nie trzymam was więcej w niepewności - poniżej ich definicje, które kończą rozwiązanie naszego zadania.
(define (enumerate n)
  (define (iter n result)
    (if (zero? n)
        result
        (iter (- n 1) (cons n result))))
  (iter n nil))
(define (permutations s k)
  (if (zero? k)
      (list nil)
      (flatmap (lambda (x)
                 (map (lambda (p) (cons x p))
                      (permutations (remove-= x s) (- k 1))))
               s)))
Skoro jednak zahaczyliśmy o kombinatorykę, szkoda byłoby nie zgłębić tematu trochę bardziej. Poniżej znajdziecie definicję permutacji:
(define (permutations-for-k=n s)
  (permutations s (length s)))
Skorzystałem tutaj z powiązania permutacji i wariacji bez powtórzeń. Definicja wariacji z powtórzeniami wymaga jednak trochę więcej zachodu:
(define (permutations-with-repetition n k)
  (if (zero? k)
      (list nil)
      (flatmap (lambda (perm-part) (map (lambda (element)
                                     (cons element perm-part))
                                   (enumerate n)))
               (permutations-with-repetition n (- k 1)))))
Przykładowo, by wygenerować zbiór wszystkich trójek (tym razem nie unikalnych) nie większych od N, wystarczy napisać:
(define (triples n)
  (permutations-with-repetition n 3))