Joan edukira
Ridgun
Euskara

17 Ridgun Liburua

csv lib-a — CSV/TSV-rako RFC 4180 Egoera-analizatzailea eta Taula Indexatuak

17.1. Zergatik existitzen da csv lib-a? (Benetako arazoa eta Ridgun filosofia)

«Zergatik behar dut csv lib bat hutsetik Ridgun hutsean idatzia, betiko C liburutegi bat estekatu beharrean?»

Hori da edozein sistema-ingeniarik csv-era iristean egiten duen lehen galdera. Eta erantzunak ridgun-libs-en filosofia osoa laburbiltzen du:

Izan gaitezen zintzoak: mundu guztiak uste du CSV bat analizatzea str split lerroa "," egitea dela... harik eta komatxo artean koma bat, "" komatxo iheskorrak edo gelaxka baten erdian \r\n lerro-jauzi bat dituen eremu bat agertzen den arte.

csv lib-ak RFC 4180 arauarekiko egoera-makina oso bat inplementatzen du CSV eta TSV formatuetarako, komatxo adimentsudun serializazio automatikoa eta goiburu-izenaren araberako zutabeak eta errenkadak kontsultatzeko CsvTable mota.

Ekosistema tradizional batean, CSV domeinuko zereginak ebazteak C-ko kanpo-mendekotasunak arrastatzea, erakusle gordinekin borrokatzea, mota-bihurketa opakuak, egiaztatu gabeko salbuespenak edo zabor-biltzailearen geldialdi ustekabekoak jasatea dakar. Ridgunen, csv lib-ak (src/main/ridgun/lib/csv.rg) lau printzipio negoziatu ezinekin heltzen dio arazoari:

  1. Deklaratu import csv (edo import csv as alias) fitxategiaren hasieran: lib bakoitzak module :: csv izen-espazioa definitzen du eta --strict moduan hitz anitzeko funtzioei zuriune naturalekin deitzen zaie beti.
  2. Kateatu zure datu-hodiak ezkerretik eskuinera -> bidez: idatzi sarrera lehen urratsa -> bigarren urratsa -> emaitza, erabili (+ _ _ +) maskarak sekuentziak desegituratzeko eta kendu _ = sententzia sekuentzialetan (edo baztertu -> () bidez).
  3. Isats-errekurtsioa TCOrekin (Tail-Call Optimization): csv-eko barne-ibilbide guztiak $O(1)$ pila-espazioko begizta natiboetara konpilatzen dira.
  4. Memoria determinista (RMM1 / RMM2): tarteko egitura bakoitza bere azken irakurketa amaitzen den une zehatzean askatzen da, Garbage Collector gabe.

17.2. Artefaktu natiboak, inportazioa eta csv APIaren katalogo osoa

ridgun-libs-en, csv lehen mailako lib bat da, src/main/ridgun/lib/csv.rg bidean kokatua (CSV domeinua). stable bundle-ekin konpilatzean, hiru formatu natibotan paketatzen da:

ArtefaktuaBideaHelburua
Ridgun Bundle-atarget/bundles/csv.rglibBanaketa eta inportazio zuzena import csv bidez
Fitxategi konpilatuatarget/csv.rgcridgunc-ek egiaztatutako tarteko artefaktua
ELF64 objektu partekatuatarget/libcsv.soC edo libc mendekotasunik gabeko liburutegi dinamiko natiboa

Lib bakoitzak bere izen-espazioa deklaratzen du module :: csv bidez: nahikoa da goiburuan import csv (edo import csv as alias) idaztea eta bere funtzioei zuriune naturalekin (nire funtzioa x) eta -> hodiekin (x nire funtzioa -> emaitza) deitzea.

17.2.1. csv-eko familia funtzionalen laburpena

Sinadura / Funtzioa / MotaZer egiten duen eta noiz erabili
csv parse / tsv parse / csv parse withCSV/TSV testua ("" ihesak eta lerro anitzeko gelaxkak barne) [[String]]-ra analizatzen du
csv stringify / tsv stringify[[String]] serializatzen du, bereizleak, komatxoak edo jauziak dituzten gelaxkak soilik komatxo artean jarriz
csv parse table / csv get cell / csv colGoiburuz indexatutako CsvTable kontsulta (errenkada, zutabe_izena) bidez edo zutabe osoa
csv row to kv / csv table to kv listErrenkada taularatuak goiburuz indexatutako [String String] hiztegietara bihurtzen ditu

17.2.2. src/main/ridgun/lib/csv.rg-eko mota eta sinadura guztien katalogoa

Jarraian duzu src/main/ridgun/lib/csv.rg fitxategian definitutako mota aljebraiko (type), eragile (op ::) eta funtzio-sinadura (fn ::) guztien inbentario osoa (1 mota, 0 eragile eta 23 funtzio):

csv-eko mota aljebraikoak eta erregistroak (type):

type CsvTable = headers: [String], rows: [[String]], delimiter_char: String, has_headers: Bool
#Funtzioa / Sinadura (fn ::)Itzulera-mota
1fn :: csv delim code StringInt
2fn :: csv append final row [[String]] [String] String[[String]]
3fn :: csv parse quoted String Int Int Int String [String] [[String]][[String]]
4fn :: csv parse unquoted String Int Int Int String [String] [[String]][[String]]
5fn :: csv parse with delim String String[[String]]
6fn :: csv parse String[[String]]
7fn :: tsv parse String[[String]]
8fn :: csv build table [[String]] BoolCsvTable
9fn :: csv parse table String BoolCsvTable
10fn :: csv needs quotes loop String Int Int IntBool
11fn :: csv double quotes loop String Int Int StringString
12fn :: csv format cell String IntString
13fn :: csv join row [String] String IntString
14fn :: csv join rows [[String]] String IntString
15fn :: csv stringify with delim [[String]] StringString
16fn :: csv stringify [[String]]String
17fn :: tsv stringify [[String]]String
18fn :: csv nth str [String] IntString
19fn :: csv nth row [[String]] Int[String]
20fn :: csv col [[String]] Int[String]
21fn :: csv find header idx [String] String IntInt
22fn :: csv get by header CsvTable Int StringString
23fn :: csv row to map [String] [String][String String]

17.3. csv sakonki esploratzen: Funtzionalitate bakoitzaren urratsez urratseko adibideak

Ez da nahikoa sinadura-taula ikustea: csv lib-aren funtzionalitate bakoitza Ridgunen exekutagarriak diren adibide progresiboen bidez esploratuko dugu. Eguneroko bi errezeta taularatuekin hasiko gara eta ondoren xehetasunez ikusiko ditugu src/test/ridgun/test csv.rg-en egiaztatutako agertoki funtzional eta muga-kasu guztiak.

17.3.1. 1. adibidea: Fluxu nagusia estilo taularatuan csv erabiliz

Ikusi nola Ridgunen = ikurrak eta csv bidezko eraldaketa-urratsak taula garbi bat bezala lerrokatuta geratzen diren, beharrezkoak ez diren aldagai aldakorrik edo ezkutuko albo-ondoriorik gabe:

import csv

fn :: extraer columna precios String => [String] String
      extraer columna precios raw_csv =
        raw_csv csv parse -> tabla
        csv col by name tabla "precio" -> precios
        csv cell by name tabla 0 "producto" -> primero
        (precios, primero)

17.3.2. 2. adibidea: Konposizio segurua eta muga-kasuen kudeaketa

csv lib-eko funtzio oro | guarda boolearrekin eta ridgun-libs-eko gainerako 32 lib-ekin konposatzeko diseinatuta dago:

import csv

fn :: exportar informe => String
      exportar informe =
        [["id", "nota"], ["1", "Contiene, coma"], ["2", "Dijo "hola""]] -> filas
        filas csv encode

17.3.3. 3. adibidea: RFC 4180 CSV/TSV joan-etorria "" komatxo iheskorrekin, barne-komekin eta txertatutako lerro-jauziekin

Adibide praktiko honetan csv bloke funtzional honetan nola portatzen den sakonki aztertzen dugu, sarrera estandarrak, parametro-konbinazioak eta muga-balioak barne:

fn :: test csv tsv roundtrip => ()
      test csv tsv roundtrip =
        nl = char from code 10
        tab = char from code 9
        raw_csv = "name,role,note" + nl + "Ada,\"Admin, Lead\",\"Quote \"\"here\"\"\"" + nl + "Bob,User,\"Line 1" + nl + "Line 2\"" + nl
        parsed = csv parse raw_csv
        assert int equals (csv row count parsed) 3 "AC1: 3 rows parsed including multiline quoted cell"
        assert str eq (csv cell at parsed 1 1) "Admin, Lead" "AC1: quoted comma preserved"
        assert str eq (csv cell at parsed 1 2) "Quote \"here\"" "AC1: doubled quote unescaped"
        assert str eq (csv cell at parsed 2 2) ("Line 1" + nl + "Line 2") "AC1: embedded newline in quoted cell preserved"
        re_csv = csv stringify parsed
        assert str eq re_csv raw_csv "AC1/AC2: RFC 4180 CSV quoted comma, escaped quote, and newline round-trip"

        raw_tsv = "col1" + tab + "col2" + nl + "val,1" + tab + "val2" + nl
        parsed_tsv = tsv parse raw_tsv
        assert str eq (csv cell at parsed_tsv 1 0) "val,1" "AC1: TSV unquoted comma preserved"
        re_tsv = tsv stringify parsed_tsv
        assert str eq re_tsv raw_tsv "AC1/AC2: TSV round-trip"
        ()

17.3.4. 4. adibidea: CRLF (\r\n) lerro-amaieren, gelaxka huts jarraituen eta mugatzaile pertsonalizatuen kudeaketa

Adibide praktiko honetan csv bloke funtzional honetan nola portatzen den sakonki aztertzen dugu, sarrera estandarrak, parametro-konbinazioak eta muga-balioak barne:

fn :: test csv crlf and empty cells => ()
      test csv crlf and empty cells =
        cr = char from code 13
        nl = char from code 10
        crlf = cr + nl
        raw_crlf = "a,b,c" + crlf + "1,,3" + crlf + ",2," + crlf
        rows_crlf = csv parse raw_crlf
        assert int equals (csv row count rows_crlf) 3 "AC1: CRLF row count"
        assert str eq (csv cell at rows_crlf 1 0) "1" "AC1: cell (1,0)"
        assert str eq (csv cell at rows_crlf 1 1) "" "AC1: middle empty cell (1,1)"
        assert str eq (csv cell at rows_crlf 1 2) "3" "AC1: cell (1,2)"
        assert str eq (csv cell at rows_crlf 2 0) "" "AC1: leading empty cell (2,0)"
        assert str eq (csv cell at rows_crlf 2 1) "2" "AC1: cell (2,1)"
        assert str eq (csv cell at rows_crlf 2 2) "" "AC1: trailing empty cell (2,2)"

        semi_rows = csv parse with delim ("k1;k2" + nl + "v1;v2" + nl) ";"
        assert int equals (csv row count semi_rows) 2 "AC1: semicolon delimiter row count"
        assert str eq (csv cell at semi_rows 1 1) "v2" "AC1: semicolon delimiter cell (1,1)"
        assert int equals (csv row count (csv parse "")) 0 "AC1: empty input yields 0 rows"
        ()

17.3.5. 5. adibidea: Goiburudun taulak (CsvTable): errenkada eta zutabe-izenaren araberako bilaketa (csv get cell), zutabe-erauzketa eta gako-balio erregistroak

Adibide praktiko honetan csv bloke funtzional honetan nola portatzen den sakonki aztertzen dugu, sarrera estandarrak, parametro-konbinazioak eta muga-balioak barne:

fn :: test csv table lookup => ()
      test csv table lookup =
        nl = char from code 10
        raw_csv = "id,name,dept" + nl + "10,Ada,Math" + nl + "20,Grace,Compilers" + nl
        tbl = csv parse table raw_csv True
        assert true (has_headers tbl) "AC3: table has_headers True"
        assert int equals (csv len (headers tbl)) 3 "AC3: 3 headers extracted"
        assert str eq (csv get by header tbl 0 "name") "Ada" "AC3: csv get by header row 0"
        assert str eq (csv get by header tbl 1 "dept") "Compilers" "AC3: csv get by header row 1"
        assert str eq (csv get by header tbl 0 "missing") "" "AC3: csv get by header missing column"
        assert str eq (csv get by header tbl 99 "name") "" "AC3: csv get by header out-of-bounds row"

        tbl_no_h = csv parse table raw_csv False
        assert false (has_headers tbl_no_h) "AC3: table has_headers False"
        assert int equals (csv row count (rows tbl_no_h)) 3 "AC3: headerless table keeps all 3 rows"

        names = csv col (rows tbl) 1
        assert int equals (csv len names) 2 "AC3: csv col length"
        assert str eq (csv str at names 0) "Ada" "AC3: csv col[0]"
        assert str eq (csv str at names 1) "Grace" "AC3: csv col[1]"

        rmap = csv row to map ["id", "name", "dept"] ["10", "Ada", "Math"]
        assert str eq (csv pair val rmap "id") "10" "AC3: csv row to map id"
        assert str eq (csv pair val rmap "name") "Ada" "AC3: csv row to map name"
        assert str eq (csv pair val rmap "dept") "Math" "AC3: csv row to map dept"
        ()

17.4. Kaputaren azpian: nola dagoen eraikia csv Ridgun hutsean (src/main/ridgun/lib/csv.rg)

ridgun-libs-en abantaila handienetako bat C-ko kutxa beltzik ez egotea da. src/main/ridgun/lib/csv.rg ireki dezakezu eta eragiketa bakoitza pattern matching, | / |. guarda-zuhaitzak eta isats-errekurtsioa (TCO) erabiliz nola inplementatuta dagoen irakurri. Ikus ditzagun bere benetako inplementazioaren funtsezko piezak:

17.4.1. Barne-inplementazioa: RFC 4180 State-Machine Parser

Ikusi nola csv.rg-ek lehenik fn :: ... => ... sinadurak egituratzen dituen eta kasu bakoitza lerrokatutako ekuazioen eta isats-deien bidez ebazten duen:

module :: csv

fn :: csv delim code String => Int
      csv delim code "" = 44
      csv delim code d = str char at d 0

fn :: csv append final row [[String]] [String] String => [[String]]
      csv append final row rows [] "" = rows
      csv append final row rows cur_row cell = rows :: [cur_row :: [cell]]

fn :: csv parse quoted String Int Int Int String [String] [[String]] => [[String]]
      csv parse quoted s pos len d_code cell cur_row rows =
        | pos >= len = csv append final row rows cur_row cell
        | (str char at s pos) == 34 and (pos + 1) < len and (str char at s (pos + 1)) == 34 = csv parse quoted s (pos + 2) len d_code (cell + (char from code 34)) cur_row rows
        | (str char at s pos) == 34 = csv parse unquoted s (pos + 1) len d_code cell cur_row rows
        | otherwise = csv parse quoted s (pos + 1) len d_code (cell + (str slice s pos (pos + 1))) cur_row rows

fn :: csv parse unquoted String Int Int Int String [String] [[String]] => [[String]]
      csv parse unquoted s pos len d_code cell cur_row rows =
        | pos >= len = csv append final row rows cur_row cell
        | (str char at s pos) == 34 and cell == "" = csv parse quoted s (pos + 1) len d_code "" cur_row rows
        | (str char at s pos) == d_code = csv parse unquoted s (pos + 1) len d_code "" (cur_row :: [cell]) rows
        | (str char at s pos) == 13 and (pos + 1) < len and (str char at s (pos + 1)) == 10 = csv parse unquoted s (pos + 2) len d_code "" [] (rows :: [cur_row :: [cell]])
        | (str char at s pos) == 10 or (str char at s pos) == 13 = csv parse unquoted s (pos + 1) len d_code "" [] (rows :: [cur_row :: [cell]])
        | otherwise = csv parse unquoted s (pos + 1) len d_code (cell + (str slice s pos (pos + 1))) cur_row rows

fn :: csv parse with delim String String => [[String]]
      csv parse with delim s delim =
        csv parse unquoted s 0 (str len s) (csv delim code delim) "" [] []

fn :: csv parse String => [[String]]
      csv parse s = csv parse with delim s ","

fn :: tsv parse String => [[String]]
      tsv parse s = csv parse with delim s (char from code 9)

fn :: csv build table [[String]] Bool => CsvTable
      csv build table [] has_h = CsvTable [] [] "," has_h
      csv build table (h:rest) True = CsvTable h rest "," True
      csv build table all_rows False = CsvTable [] all_rows "," False

fn :: csv parse table String Bool => CsvTable
      csv parse table s has_h = csv build table (csv parse s) has_h

17.4.2. Barne-inplementazioa: RFC 4180 Serialization & Escaping

Ikusi nola csv.rg-ek lehenik fn :: ... => ... sinadurak egituratzen dituen eta kasu bakoitza lerrokatutako ekuazioen eta isats-deien bidez ebazten duen:

fn :: csv needs quotes loop String Int Int Int => Bool
      csv needs quotes loop s pos len d_code =
        | pos >= len = False
        | (str char at s pos) == d_code or (str char at s pos) == 34 or (str char at s pos) == 10 or (str char at s pos) == 13 = True
        | otherwise = csv needs quotes loop s (pos + 1) len d_code

fn :: csv double quotes loop String Int Int String => String
      csv double quotes loop s pos len acc =
        | pos >= len = acc
        | (str char at s pos) == 34 = csv double quotes loop s (pos + 1) len (acc + (char from code 34) + (char from code 34))
        | otherwise = csv double quotes loop s (pos + 1) len (acc + (str slice s pos (pos + 1)))

fn :: csv format cell String Int => String
      csv format cell cell d_code =
        | csv needs quotes loop cell 0 (str len cell) d_code = (char from code 34) + (csv double quotes loop cell 0 (str len cell) "") + (char from code 34)
        | otherwise = cell

fn :: csv join row [String] String Int => String
      csv join row [] _ _ = ""
      csv join row (c:[]) _ d_code = csv format cell c d_code
      csv join row (c:cs) delim d_code = (csv format cell c d_code) + delim + (csv join row cs delim d_code)

fn :: csv join rows [[String]] String Int => String
      csv join rows [] _ _ = ""
      csv join rows (r:rs) delim d_code = (csv join row r delim d_code) + (char from code 10) + (csv join rows rs delim d_code)

fn :: csv stringify with delim [[String]] String => String
      csv stringify with delim rows delim =
        csv join rows rows delim (csv delim code delim)

fn :: csv stringify [[String]] => String
      csv stringify rows = csv stringify with delim rows ","

fn :: tsv stringify [[String]] => String
      tsv stringify rows = csv stringify with delim rows (char from code 9)

17.4.3. Barne-inplementazioa: Column Extraction & Header-Mapped Lookup

Ikusi nola csv.rg-ek lehenik fn :: ... => ... sinadurak egituratzen dituen eta kasu bakoitza lerrokatutako ekuazioen eta isats-deien bidez ebazten duen:

fn :: csv nth str [String] Int => String
      csv nth str [] _ = ""
      csv nth str (x:xs) idx =
        | idx < 0 = ""
        | idx == 0 = x
        | otherwise = csv nth str xs (idx - 1)

fn :: csv nth row [[String]] Int => [String]
      csv nth row [] _ = []
      csv nth row (r:rs) idx =
        | idx < 0 = []
        | idx == 0 = r
        | otherwise = csv nth row rs (idx - 1)

fn :: csv col [[String]] Int => [String]
      csv col [] _ = []
      csv col (r:rs) col_idx = [csv nth str r col_idx] :: (csv col rs col_idx)

fn :: csv find header idx [String] String Int => Int
      csv find header idx [] _ _ = (0 - 1)
      csv find header idx (h:hs) target idx =
        | h == target = idx
        | otherwise = csv find header idx hs target (idx + 1)

fn :: csv get by header CsvTable Int String => String
      csv get by header tbl row_idx col_name =
        headers tbl -> csv find header idx col_name 0 -> c_idx
        rows tbl -> csv nth row row_idx -> csv nth str c_idx

fn :: csv row to map [String] [String] => [String String]
      csv row to map [] _ = []
      csv row to map _ [] = []
      csv row to map (h:hs) (v:vs) = [(h, v)] :: (csv row to map hs vs)

17.5. Berme algoritmikoak, RMM1/RMM2 memoria eta csv-en Urrezko 5 Arauak

  • Berme algoritmikoa eta segurtasunekoa: \r\n eta \n lerro-jauzien euskarri zehatza komatxoen barruan zein kanpoan, eta zero datu-galera csv parse (csv stringify errenkadak) joan-etorrietan.
  • $O(1)$ pila-konplexutasuna TCOrekin: src/main/ridgun/lib/csv.rg-eko begizta errekurtsibo guztiak isats-posizioan daude; beraz, ridgunc-ek pila-hazkuntzarik gabeko jauzi natiboetara konpilatzen ditu.
  • Memoria determinista (RMM1/RMM2): Zero zabor-biltzaile etenaldi; tarteko egitura guztiak modu deterministan askatzen dira esparrutik irtetean.
  • Etengabeko egiaztapena: Zorrotz probatua stable check eta src/test/ridgun/test csv.rg suite automatizatuaren bidez.

csv-ekin programatzeko Urrezko 5 Arauak

  1. Deklaratu import csv fitxategiaren hasieran eta baliatu zuriuneen eta azpimarren arteko baliokidetasuna zure kodea prosa bezala irakur dadin.
  2. Lerrokatu zure datu-hodiak = ikurrarekin: kateatu csv-erako deiak izen propioko urratsetan eta utzi stable format-i zutabeak lerrokatzen.
  3. Baliatu csv-eko funtzio osoak zure | guardetan: egiaztatu itzulera-balioak edo askatu ADTak pattern matching bidez, kanpoko sarrera beti baliozkoa dela suposatu beharrean.
  4. Saihestu egitura handiak kopiatzea eremu bat kontsultatu besterik behar ez baduzu: gogoratu RMM2-n sarrerako argumentuek ez dituztela kontagailuak handitzen edo txikitzen (+0 / -0).
  5. Konbinatu csv eta test (import test): idatzi beti asertzioak assert equals edo assert true erabiliz zure domeinuaren inbarianteak finkatzeko.

18. kapituluan ini lib-a sakonki esploratuko dugu (ini lib-a — Atalkako .ini Fitxategiak, .env eta Aldagaien Interpolazioa).