Racket/Personreg

Från Täpp-Anders
Hoppa till navigeringHoppa till sök
#lang racket

;;;;;
;; Enkel personinfodatabas implementerad i Racket
;; Täpp-Anders Sikvall 2026-04-03 anders@sikvall.se
;;;;;

;; Global lista som håller alla personer, just nu tom
(define personer '())

;; Skapa en person med namngivna parametrar
(define (skapa-person
         #:förnamn förnamn
         #:efternamn efternamn
         #:ålder ålder
         #:stad [stad ""]           ;Valfria parametrar med defaultvärden
         #:telefon [telefon ""])
  (define person (list (cons 'förnamn förnamn)         ;när man consar par såhär
                       (cons 'efternamn efternamn)     ;måste man använda car/cdr!
                       (cons 'ålder ålder)
                       (cons 'stad stad)
                       (cons 'telefon telefon)))

  (set! personer (cons person personer)) ; lägger till en person i listan personer och returnerar
  person)                                ; sedan samma person

;; Sökfunktion (oförändrad)
(define hitta-person
  (make-keyword-procedure
   (lambda (keywords keyword-values . rest)
     (if (empty? keywords)
         personer
         (filter (λ (person)
                   (for/and ([kw keywords]
                             [val keyword-values])
                     (let ([field-key (string->symbol (keyword->string kw))])
                       (let ([found (assoc field-key person)])
                         (and found
                              (equal? (string-downcase (format "~a" val))
                                      (string-downcase (format "~a" (cdr found)))))))))
                 personer)))))

;; Pretty-print funktioner
(define (pretty-print-person person)
  (printf "  Förnamn:   ~a\n" (cdr (assoc 'förnamn person)))    ;här går det inte använda rest
  (printf "  Efternamn: ~a\n" (cdr (assoc 'efternamn person)))  ;för det är inte en lista, det
  (printf "  Ålder:     ~a\n" (cdr (assoc 'ålder person)))      ;är ett consat par (ålder . 34)
  
  (let ([stad (cdr (assoc 'stad person))])
    (when (and stad (not (equal? stad "")))
      (printf "  Stad:      ~a\n" stad)))
  
  (let ([telefon (cdr (assoc 'telefon person))])
    (when (and telefon (not (equal? telefon "")))
      (printf "  Telefon:   ~a\n" telefon)))
  
  (newline))

(define (pretty-print-persons lst)
  (if (empty? lst)
      (displayln "  Inga personer hittades.")
      (for ([p (reverse lst)])
        (pretty-print-person p))))

;; Spara och ladda – Lispformat (.dat)
(define data-fil "personer.dat") ;default filnamn

(define (spara-personer [filnamn data-fil]) ;men i anrop kan du ge eget namn
  (with-output-to-file filnamn
    (λ () (write personer))
    #:exists 'replace) ;om filen finns, skriv över den
  (printf "* Sparat ~a personer till \"~a\"\n"
          (length personer) filnamn))

(define (ladda-personer [filnamn data-fil])
  (if (file-exists? filnamn)
      (begin
        (set! personer (with-input-from-file filnamn read))
        (printf "* Laddat ~a personer från \"~a\"\n" (length personer) filnamn))
      (printf "! Filen \"~a\" finns inte.\n" filnamn)))

;; Spara och ladda – CSV-format (för enkel redigering)
(require csv-writing csv-reading)
(define csv-fil "personer.csv")

;; Hjälpfunktion: person → CSV-rad
(define (person->csv-row person)
  (list (format "~a" (cdr (assoc 'förnamn person)))
        (format "~a" (cdr (assoc 'efternamn person)))
        (format "~a" (cdr (assoc 'ålder person)))
        (format "~a" (cdr (assoc 'stad person)))
        (format "~a" (cdr (assoc 'telefon person)))))

;; SPARA till CSV
(define (spara-personer-csv [filnamn csv-fil])
  (define rubriker (list (list "Förnamn" "Efternamn" "Ålder" "Stad" "Telefon")))
  (define rader (map person->csv-row (reverse personer)))   ; reverse så ordningen blir "naturlig"

  (with-output-to-file filnamn #:exists 'replace
    (λ ()
      (display-table rubriker)
      (display-table rader)))
  
  (printf "* Sparat ~a personer till CSV-filen \"~a\"\n" (length personer) filnamn))

;; LADDA från CSV
(define (ladda-personer-csv [filnamn csv-fil])
  (if (file-exists? filnamn)
      (let* ([reader (make-csv-reader (open-input-file filnamn))]
             [all-rows (csv->list reader)] ;konvertera från csv till Lisp lista
             [data-rows (if (empty? all-rows) '() (rest all-rows))]) ; hoppa över rubrik-rad

        (define nya-personer
          (for/list ([row data-rows])
            (list (cons 'förnamn (list-ref row 0))
                  (cons 'efternamn (list-ref row 1))
                  (cons 'ålder (list-ref row 2))        ; behåll som sträng eller konvertera 
                  (cons 'stad (list-ref row 3))         ; med string->number om du vill
                  (cons 'telefon (list-ref row 4)))))

        (set! personer (append nya-personer personer))  ; eller (set! personer nya-personer) om du vill ersätta
        (printf "* Laddat ~a personer från CSV-filen \"~a\"\n" (length nya-personer) filnamn))
      (printf "! CSV-filen \"~a\" finns inte.\n" filnamn)))


;; SORTERA PERSONER
;; Sorterar först på efternamn över hela listan
;; därefter på efternamn. Då blir det som det ska
(define (sortera-personer)
  ;; Steg 1: Sortera på förnamn
  (set! personer
        (sort personer
              (λ (p1 p2)
                (string-ci<? (cdr (assoc 'förnamn p1))        ;string-ci är "string case insensitive"
                             (cdr (assoc 'förnamn p2))))))    

  ;; Steg 2: Sortera på efternamn (detta blir den slutgiltiga ordningen)
  (set! personer
        (sort personer
              (λ (p1 p2)
                (string-ci<? (cdr (assoc 'efternamn p1))
                             (cdr (assoc 'efternamn p2))))))

  personer)   ; returnerar den sorterade listan (bra för kedjad användning)

;; ================================================================
;; Exempel på användning
;; ================================================================

;; Skapa testpersoner, detta behöver vi bara göra en gång för sedan skrivs den till disk och
;; vi behöver därefter bara hämta den vid behov!

(skapa-person #:förnamn "Anna"      #:efternamn "Andersson" #:ålder 34  #:stad "Stockholm" #:telefon "070-123 45 67")
(skapa-person #:förnamn "Rickard"   #:efternamn "Nilsson"   #:ålder 28  #:stad "Kista")
(skapa-person #:förnamn "Enar"      #:efternamn "Nilsson"   #:ålder "74" #:stad "Färjestaden")
(skapa-person #:förnamn "Anna"      #:efternamn "Svensson"  #:ålder 45  #:stad "Malmö")
(skapa-person #:förnamn "Johan"     #:efternamn "Larsson"   #:ålder 31  #:stad "Stockholm")

(sortera-personer)

(spara-personer-csv "personlista.csv") 

(displayln "=== Personer efter skapande ===")
(pretty-print-persons (hitta-person))

;; Spara både binärt och CSV
(displayln "\n=== Sparar till disk (både .dat och .csv) ===")
(spara-personer "personlista.dat")
(spara-personer-csv "personlista.csv")

;; Rensa minnet (simulerar omstart)
(set! personer '())
(displayln "\n=== Listan rensad ===")
(pretty-print-persons (hitta-person))

;; Ladda tillbaka
(displayln "\n=== Laddar från Lispdata ===")
(ladda-personer "personlista.dat")
(pretty-print-persons (hitta-person))

(set! personer '()) ; Nolla listan igen

(ladda-personer-csv "personlista.csv")
(displayln "\n=== Personer efter laddning från CSV ===")
(pretty-print-persons (hitta-person))

;; Exempel på sökning efter laddning
(displayln "\n=== Sök efter laddning (förnamn anna) ===")
(pretty-print-persons (hitta-person #:förnamn "anna"))