簡體   English   中英

可逆CSV解析

[英]Reversable CSV parsing

Prolog 新手在這里。 在 SWI Prolog 中,我試圖找出如何可逆地解析簡單的 CSV 行,但我被卡住了。 這是我得到的:

csvstring1(S, L) :-
  split_string(S, ',', ',', T),
  maplist(atom_number, T, L).
   
csvstring2(S, L) :-
  atomic_list_concat(T, ',', S),
  maplist(atom_number, T, L).

% This one is the same except that maplist comes first. 
csvstring3(S, L) :-
  maplist(atom_number, T, L),
  atomic_list_concat(T, ',', S).

現在 csvstring1 和 csvstring2 以“正向”方式工作:

?- csvstring1('1,2,3,4', L).
L = [1, 2, 3, 4].

?- csvstring2('1,2,3,4', L).
L = [1, 2, 3, 4].

但不是 csvstring3:

?- csvstring3('1,2,3,4', L).
ERROR: Arguments are not sufficiently instantiated

此外 csvstring3 反向工作,但不是其他兩個謂詞:

?- csvstring3(L, [1,2,3,4]).
L = '1,2,3,4'.

?- csvstring1(L, [1,2,3,4]).
ERROR: Arguments are not sufficiently instantiated

?- csvstring2(L, [1,2,3,4]).
ERROR: Arguments are not sufficiently instantiated

我如何將這些組合成一個謂詞?

我不知道有什么特別適合新手的方法,它不會在某處妥協。 這是最簡單的:

csvString_list(String, List) :-
    ground(String),
    atomic_list_concat(Temp, ',', String),
    maplist(atom_number, Temp, List).

csvString_list(String, List) :-
    ground(List),
    maplist(atom_number, Temp, List),
    atomic_list_concat(Temp, ',', String).

但它會產生並留下虛假的選擇點,這有點煩人。

這減少了選擇點,這在使用它時很好,但是在不知道這意味着什么的情況下進入的做法很差:

csvString_list(String, List) :-
    ground(String),
    atomic_list_concat(Temp, ',', String),
    maplist(atom_number, Temp, List),
    !.

csvString_list(String, List) :-
    ground(List),
    maplist(atom_number, Temp, List),
    atomic_list_concat(Temp, ',', String).

這使用 if/else 代碼更少:

csvString_list(String, List) :-
  ground(String) ->
      (atomic_list_concat(Temp, ',', String), maplist(atom_number, Temp, List))
    ; (maplist(atom_number, Temp, List),      atomic_list_concat(Temp, ',', String)).

但在邏輯上是錯誤的,你應該使用 if_ 來具體化分支,它不是 SWI Prolog 的內置並且使用起來不太簡單。

或者您可以使用 DCG 編寫語法,這不是新手領域:


:- set_prolog_flag(double_quotes, chars).
:- use_module(library(dcg/basics)).

csvTail([N|Ns]) --> [','], number(N), csvTail(Ns). 
csvTail([])     --> [].

csv([N|Ns]) --> number(N), csvTail(Ns).

例如

?- phrase(csv(Ns), "11,22,33,44,55").
Ns = [11, 22, 33, 44, 55]


?- phrase(csv([11, 22, 33, 44, 55]), String)
String = [49, 49, ',', 50, 50, ',', 51, 51, ',', 52, 52, ',', 53, 53]

但現在你又回到了它,在解析時留下了虛假的選擇點你必須處理 SWI Prolog 中字符串/原子/字符代碼的歷史分裂; 由於 double_quotes 標志,該列表將與"11,22,33,44,55"統一,但它看起來不像。

split_string是不可逆的。 可以使用 DCG——這是一個簡單的多行 DCG 解析器,用於 CSV:

% Nicer formatting
% https://www.swi-prolog.org/pldoc/man?section=flags
:- set_prolog_flag(answer_write_options, [quoted(true), portray(true), spacing(next_argument), max_depth(100), attributes(portray)]).

% Show lists of codes as text (if 3 chars or longer)
:- portray_text(true).

csv_lines([]) --> [].
% Newline after every line
csv_lines([H|T]) --> csv_fields(H), [10], csv_lines(T).

csv_fields([H|T]) --> csv_field(H), csv_field_end(T).

csv_field_end([]) --> [].
% Comma between fields
csv_field_end(T) --> [44], csv_fields(T).

csv_field([]) --> [].
csv_field([H|T]) -->
    [H],
    % Fields cannot contain comma, newline or carriage return
    { maplist(dif(H), [44, 10, 13]) },
    csv_field(T).

證明可逆性:

% Note: z is char 122
?- phrase(csv_lines([[`def`, `cool`], [`abc`, [122]]]), Lines).
Lines = `def,cool\nabc,z\n` ;
false.

?- phrase(csv_lines(Fields), `def,cool\nabc,z\n`).
Fields = [[`def`, `cool`], [`abc`, [122]]] ;
false.

要解析字段內容並保持可逆性,可以使用例如atom_codes

其他人給出了一些建議和大量代碼。 使用 SWI-Prolog,要解析逗號分隔的整數,您可以使用 library(dcg/basics) 和 library(dcg/high_order) 來簡單地執行此操作:

?- use_module(library(dcg/basics)),
   use_module(library(dcg/high_order)),
   portray_text(true).
true.

?- phrase(sequence(integer, ",", Ns), `1,2,3,4`).
Ns = [1, 2, 3, 4].

?- phrase(sequence(integer, ",", [-7,6,42]), S).
S = `-7,6,42`.

當然,如果你試圖解析真正的 CSV 文件,你應該使用 CSV 解析器。 這是讀取 CSV 文件並將其 output 寫入 TSV(制表符分隔)文件的最小示例。 如果這是您在名為example.csv的文件中的輸入:

$ cat example.csv
id,name,salary,department
1,john,2000,sales
2,Andrew,5000,finance
3,Mark,8000,hr
4,Rey,5000,marketing
5,Tan,4000,IT

您可以從文件中讀取它並使用制表符作為分隔符編寫它,如下所示:

?- csv_read_file('example.csv', Data),
   csv_write_file('example.tsv', Data).
Data = [row(id, name, salary, department),
        row(1, john, 2000, sales),
        row(2, 'Andrew', 5000, finance),
        row(3, 'Mark', 8000, hr),
        row(4, 'Rey', 5000, marketing),
        row(5, 'Tan', 4000, 'IT')].

該庫根據文件擴展名猜測字段分隔符。 在這里,它正確地猜測“csv”表示逗號“,”,而“tsv”表示制表符。 我們可以使用cat -t使選項卡明確可見。

$ cat example.tsv 
id  name    salary  department
1   john    2000    sales
2   Andrew  5000    finance
3   Mark    8000    hr
4   Rey 5000    marketing
5   Tan 4000    IT
$ cat -t example.tsv 
id^Iname^Isalary^Idepartment^M
1^Ijohn^I2000^Isales^M
2^IAndrew^I5000^Ifinance^M
3^IMark^I8000^Ihr^M
4^IRey^I5000^Imarketing^M
5^ITan^I4000^IIT^M

我如何將這些組合成一個謂詞?

csvstring(S, L) :-
  (  ground(S)
  -> atomic_list_concat(T, ',', S),
     maplist(atom_number, T, L)
  ;  maplist(atom_number, T, L),
     atomic_list_concat(T, ',', S)
  ).

...微測試...

?- csvstring('1,2,3,4', L).
L = [1, 2, 3, 4].

?- csvstring(L, [1,2,3,4]).
L = '1,2,3,4'.

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM