TSV पाठ को पार्स करने के लिए मैं एक Raku व्याकरण को कैसे परिभाषित कर सकता हूं?


13

मेरे पास कुछ TSV डेटा हैं

ID     Name    Email
   1   test    test@email.com
 321   stan    stan@nowhere.net

मैं इसे हैश की सूची में पार्स करना चाहूंगा

@entities[0]<Name> eq "test";
@entities[1]<Email> eq "stan@nowhere.net";

मूल्य पंक्तियों से शीर्षक पंक्ति को परिसीमित करने के लिए मुझे नई लाइन मेटाचचर का उपयोग करने में परेशानी हो रही है। मेरी व्याकरण की परिभाषा:

use v6;

grammar Parser {
    token TOP       { <headerRow><valueRow>+ }
    token headerRow { [\s*<header>]+\n }
    token header    { \S+ }
    token valueRow  { [\s*<value>]+\n? }
    token value     { \S+ }
}

my $dat = q:to/EOF/;
ID     Name    Email
   1   test    test@email.com
 321   stan    stan@nowhere.net
EOF
say Parser.parse($dat);

लेकिन यह लौट रहा है Nil। मुझे लगता है कि मैं रकू में रेगेक्स के बारे में कुछ बुनियादी गलतफहमी कर रहा हूं।


1
Nil। यह बहुत बंजर है जहाँ तक प्रतिक्रिया जाती है, है ना? डिबगिंग के लिए, यदि आप पहले से ही नहीं हैं, तो कमोड को डाउनलोड करें और / या देखें कि व्याकरण में त्रुटि की रिपोर्ट में कैसे सुधार किया जा सकता है? । आप Nilcuz को अपना पैटर्न बैकट्रैकिंग शब्दार्थ मानते हैं। उस बारे में मेरा जवाब देखें। मैं आपको सलाह देता हूं कि आप पीछे हट जाएं। उस बारे में @ user0721090601 का जवाब देखें। सरासर व्यावहारिकता और गति के लिए, जेजे का उत्तर देखें। इसके अलावा, परिचयात्मक सामान्य जवाब "मैं राकू के साथ एक्स को पार्स करना चाहता हूं। क्या कोई मदद कर सकता है?"
21

व्याकरण का उपयोग करें :: अनुरेखक; मेरे लिए # आतिशबाज़ी
p6steve

जवाबों:


12

संभवतः मुख्य चीज जो इसे फेंक रही है वह \sक्षैतिज और ऊर्ध्वाधर स्थान से मेल खाती है। बस क्षैतिज स्थान, उपयोग मिलान करने के लिए \h, और बस खड़ी अंतरिक्ष मिलान करने के लिए, \v

एक छोटी सी सिफारिश जो मैं करता हूं वह है टोकन में नए सिरे को शामिल करना। आप वैकल्पिक ऑपरेटरों का उपयोग करना चाहते हैं %या %%, जैसा कि वे इस प्रकार के काम को संभालने के लिए डिज़ाइन किए गए हैं:

grammar Parser {
    token TOP       { 
                      <headerRow>     \n
                      <valueRow>+ %%  \n
                    }
    token headerRow { <.ws>* %% <header> }
    token valueRow  { <.ws>* %% <value>  }
    token header    { \S+ }
    token value     { \S+ }
    token ws        { \h* }
} 

इसके लिए परिणाम Parser.parse($dat)निम्न है:

「ID     Name    Email
   1   test    test@email.com
 321   stan    stan@nowhere.net
」
 headerRow => 「ID     Name    Email」
  header => 「ID」
  header => 「Name」
  header => 「Email」
 valueRow => 「   1   test    test@email.com」
  value => 「1」
  value => 「test」
  value => 「test@email.com」
 valueRow => 「 321   stan    stan@nowhere.net」
  value => 「321」
  value => 「stan」
  value => 「stan@nowhere.net」
 valueRow => 「」

जो हमें दिखाता है कि व्याकरण ने हर चीज को सफलतापूर्वक पार कर लिया है। हालाँकि, अपने प्रश्न के दूसरे भाग पर ध्यान केंद्रित करें, कि आप इसे अपने लिए एक चर में उपलब्ध कराना चाहते हैं। ऐसा करने के लिए, आपको एक एक्शन क्लास की आपूर्ति करनी होगी जो इस परियोजना के लिए बहुत सरल है। आप बस एक ऐसा वर्ग बनाते हैं, जिसके तरीके आपके व्याकरण के तरीकों से मेल खाते हैं (हालाँकि बहुत ही सरल, जैसे value/ headerजिन्हें विशेष प्रक्रिया की आवश्यकता नहीं है, इसके अलावा, उन्हें अनदेखा किया जा सकता है)। आपके प्रसंस्करण को संभालने के लिए कुछ और रचनात्मक / कॉम्पैक्ट तरीके हैं, लेकिन मैं चित्रण के लिए काफी रूढ़िवादी दृष्टिकोण के साथ जाऊंगा। यहाँ हमारी कक्षा है:

class ParserActions {
  method headerRow ($/) { ... }
  method valueRow  ($/) { ... }
  method TOP       ($/) { ... }
}

प्रत्येक विधि में हस्ताक्षर हैं ($/)जो रेगेक्स मैच चर है। तो अब, आइए पूछें कि हम प्रत्येक टोकन से क्या जानकारी चाहते हैं। हेडर पंक्ति में, हम प्रत्येक हेडर मान को एक पंक्ति में चाहते हैं। इसलिए:

  method headerRow ($/) { 
    my   @headers = $<header>.map: *.Str
    make @headers;
  }

उस पर एक परिमाणक के साथ कोई टोकन एक माना जाएगा Positional, तो हम भी साथ प्रत्येक व्यक्ति के हैडर मैच पहुंच पा रहे थे $<header>[0], $<header>[1],, आदि लेकिन उन मैच वस्तुओं रहे हैं तो हम सिर्फ जल्दी से उन्हें stringify। makeआदेश अन्य टोकन इस विशेष डेटा है कि हम बना लिया है का उपयोग करने की अनुमति देता है।

हमारी मूल्य पंक्ति पहचान में दिखेगी, क्योंकि $<value>टोकन वही हैं जिनकी हम परवाह करते हैं।

  method valueRow ($/) { 
    my   @values = $<value>.map: *.Str
    make @values;
  }

जब हम अंतिम विधि प्राप्त करते हैं, तो हम हैश के साथ सरणी बनाना चाहेंगे।

  method TOP ($/) {
    my @entries;
    my @headers = $<headerRow>.made;
    my @rows    = $<valueRow>.map: *.made;

    for @rows -> @values {
      my %entry = flat @headers Z @values;
      @entries.push: %entry;
    }

    make @entries;
  }

यहां आप देख सकते हैं कि हम अपने द्वारा संसाधित किए गए सामान का उपयोग कैसे करते हैं : headerRow()और valueRow()आप .madeविधि का उपयोग करते हैं । क्योंकि कई madeमान हैं, उनके प्रत्येक मान को प्राप्त करने के लिए , हमें एक नक्शा करने की आवश्यकता है (यह एक ऐसी स्थिति है, जहां मैं व्याकरण में बस लिखने के लिए अपना व्याकरण लिखना चाहता हूं <header><data>, और डेटा को कई पंक्तियों के रूप में परिभाषित करता हूं, लेकिन यह है) बहुत आसान है) यह बहुत बुरा नहीं है।

अब जबकि हमारे पास दो सरणियों में हेडर और पंक्तियाँ हैं, यह केवल उन्हें हैश की एक सरणी बनाने की बात है, जिसे हम forलूप में करते हैं । flat @x Z @yबस तत्वों intercolates, और हैश काम हम क्या मतलब है, लेकिन वहाँ अन्य तरीकों से हैश में सरणी आप चाहते हैं पाने के लिए कर रहे हैं।

एक बार जब आप कर लेते हैं, तो आप makeइसे कर लेते हैं , और फिर यह madeपार्स में उपलब्ध होगा :

say Parser.parse($dat, :actions(ParserActions)).made
-> [{Email => test@email.com, ID => 1, Name => test} {Email => stan@nowhere.net, ID => 321, Name => stan} {}]

इनको एक विधि में लपेटना काफी आम है, जैसे

sub parse-tsv($tsv) {
  return Parser.parse($tsv, :actions(ParserActions)).made
}

इस तरह आप सिर्फ कह सकते हैं

my @entries = parse-tsv($dat);
say @entries[0]<Name>;    # test
say @entries[1]<Email>;   # stan@nowhere.net

मुझे लगता है कि मैं एक्शन क्लास को अलग लिखूंगा। class Actions { has @!header; method headerRow ($/) { @!header = @<header>.map(~*); make @!header.List; }; method valueRow ($/) {make (@!header Z=> @<value>.map: ~*).Map}; method TOP ($/) { make @<valueRow>.map(*.made).List }आप निश्चित रूप से पहले इसे तुरंत करना होगा :actions(Actions.new)
ब्रैड गिल्बर्ट

@BradGilbert हाँ, मैं तात्कालिकता से बचने के लिए अपनी कार्रवाई कक्षाएं लिखना चाहता हूं, लेकिन अगर तात्कालिकता है, तो मैं शायद करूँगा class Actions { has @!header; has %!entries … }और बस मूल्य दर्ज करें प्रविष्टियों को सीधे जोड़ दें ताकि आप बस के साथ समाप्त हो जाएं method TOP ($!) { make %!entries }। लेकिन यह सभी के बाद Raku है और TIMTOWTDI :-)
user0721090601

इस जानकारी ( docs.raku.org/language/regexes#Modified_quantifier:_%,_%%% ) को पढ़ने से , मुझे लगता है कि मैं समझता हूं <valueRow>+ %% \n(उन पंक्तियों को कैप्चर करें जो न्यूलाइन द्वारा सीमांकित की जाती हैं), लेकिन इस तर्क का अनुसरण करते हुए, <.ws>* %% <header>"वैकल्पिक पर कब्जा करेंगे" व्हॉट्सएप गैर-व्हाट्सएप द्वारा सीमांकित किया गया है "। क्या मैं कुछ भूल रहा हूँ?
क्रिस्टोफर बॉटम्स

@ChristopherBottoms लगभग। <.ws>कब्जा नहीं करता है ( <ws>होगा)। ओपी ने कहा कि टीएसवी प्रारूप एक वैकल्पिक व्हाट्सएप से शुरू हो सकता है। हकीकत में, यह संभवतः एक लाइन-स्पेसिंग टोकन के रूप में परिभाषित के साथ बेहतर रूप से परिभाषित किया जाएगा \h*\n\h*, जो मान के लिए अनुमति देगा। अधिक तार्किक रूप से परिभाषित किया जाएगा<header> % <.ws>
user0721090601

@ user0721090601 मैं पढ़ने से पहले %/ %%एक "प्रत्यावर्तन" ऑप को याद नहीं करता । लेकिन यह सही नाम है। (जबकि इसके लिए उपयोग किया गया है |, ||और चचेरे भाई हमेशा अजीब के रूप में मुझे मारा है।) मैंने पहले इस "बैकवर्ड" तकनीक के बारे में नहीं सोचा था। लेकिन कुछ विभाजक जोर के साथ दोहराया पैटर्न से मेल खाते regexes लिखने के लिए यह एक अच्छा मुहावरा है, न केवल पैटर्न के मैचों के बीच, बल्कि इसे दोनों सिरों (उपयोग %%), या प्रारंभ में, लेकिन अंत (उपयोग कर %), ए, एर के रूप में अनुमति नहीं देता है वैकल्पिक अंत में नहीं बल्कि करने के तर्क शुरू ruleऔर :s। अच्छा लगा। :)
raiph

11

TL; DR: आप नहीं। बस उपयोग करें Text::CSV, जो हर प्रारूप से निपटने में सक्षम है।

मैं दिखाऊंगा कि पुराना कितना Text::CSVउपयोगी होगा:

use Text::CSV;

my $text = q:to/EOF/;
ID  Name    Email
   1    test    test@email.com
 321    stan    stan@nowhere.net
EOF
my @data = $text.lines.map: *.split(/\t/).list;

say @data.perl;

my $csv = csv( in => @data, key => "ID");

print $csv.perl;

यहाँ मुख्य भाग डेटा मिंगिंग है जो प्रारंभिक फ़ाइल को एक सरणी या सरणियों (में @data) में परिवर्तित करता है । हालाँकि, इसकी केवल आवश्यकता है, क्योंकि csvकमांड स्ट्रिंग्स से निपटने में सक्षम नहीं है; यदि डेटा किसी फ़ाइल में है, तो आप जाने के लिए अच्छे हैं।

अंतिम पंक्ति छपेगी:

${"   1" => ${:Email("test\@email.com"), :ID("   1"), :Name("test")}, " 321" => ${:Email("stan\@nowhere.net"), :ID(" 321"), :Name("stan")}}%

आईडी फ़ील्ड हैश की कुंजी बन जाएगी, और पूरी चीज़ हैश की एक सरणी बन जाएगी।


2
व्यावहारिकता के कारण उन्नति। मुझे यकीन नहीं है, हालांकि, अगर ओपी व्याकरण (मेरे उत्तर के दृष्टिकोण) को सीखने के लिए अधिक लक्ष्य कर रहा है या बस पार्स करने की आवश्यकता है (आपके उत्तर के दृष्टिकोण)। किसी भी स्थिति में, उसे जाने के लिए अच्छा होना चाहिए :-)
user0721090601

2
उसी कारण से अपवित्र। :) मैंने सोचा था कि ओपी यह जानने के लिए लक्षित हो सकता है कि उन्होंने रेगेक्स शब्दार्थ के संदर्भ में क्या गलत किया है (इसलिए मेरा उत्तर), यह सीखने का लक्ष्य है कि यह कैसे करना है (आपका उत्तर), या बस पार्स करने की आवश्यकता है (जेजे का उत्तर) )। टीम का काम :)
21

7

टीएल; डीआर regex बैकट्रैक। tokenनहीं है इसलिए आपका पैटर्न मेल नहीं खा रहा है। यह उत्तर यह समझाने पर केंद्रित है, और अपने व्याकरण को तुच्छ रूप से कैसे ठीक करें। हालांकि, आपको शायद इसे फिर से लिखना चाहिए, या किसी मौजूदा पार्सर का उपयोग करना चाहिए, जो कि आपको निश्चित रूप से करना चाहिए, यदि आप आरकेयू रेक्सक्स के बारे में जानने के बजाय टीएसवी को पार्स करना चाहते हैं।

एक बुनियादी गलतफहमी?

मुझे लगता है कि मैं रकू में रेगेक्स के बारे में कुछ बुनियादी गलतफहमी कर रहा हूं।

(यदि आप पहले से ही जानते हैं कि "रेगेक्स" शब्द एक अत्यधिक अस्पष्ट है, तो इस खंड को छोड़ दें।)

एक बुनियादी बात जो आपको गलतफहमी हो सकती है वह है "रेगेक्स" शब्द का अर्थ। यहाँ कुछ लोकप्रिय अर्थ लोक मान्यता हैं:

  • औपचारिक नियमित अभिव्यक्ति।

  • पर्ल रीगेक्स।

  • पर्ल कम्पेटिबल रेगुलर एक्सप्रेशंस (PCRE)।

  • "रेगेक्स" नामक पाठ पैटर्न मिलान अभिव्यक्तियाँ जो उपरोक्त में से किसी भी तरह दिखती हैं और कुछ ऐसा ही करती हैं।

इनमें से कोई भी अर्थ एक दूसरे के साथ संगत नहीं है।

जबकि पर्ल रीगेक्स औपचारिक रूप से नियमित अभिव्यक्ति का एक सुपरसेट है, वे कई मायनों में अधिक उपयोगी हैं, लेकिन पैथोलॉजिकल बैकट्रैकिंग के लिए भी अधिक संवेदनशील हैं

पर्ल कम्पैटिबल रेग्युलर एक्सप्रेशंस अर्थ में पर्ल के साथ संगत कर रहे हैं जबकि वे थे मूल रूप से 1990 के दशक में मानक पर्ल regexes के रूप में ही है, और इस अर्थ में कि पर्ल PCRE इंजन सहित प्लगेबल regex इंजन का समर्थन करता है में, PCRE regex वाक्यविन्यास मानक के समान नहीं है 2020 में पर्ल द्वारा डिफ़ॉल्ट रूप से पर्ल रेगेक्स का उपयोग किया गया।

और जबकि पाठ पैटर्न मिलान अभिव्यक्ति "रेगेक्स" कहा जाता है, आम तौर पर एक दूसरे की तरह कुछ दिखते हैं, और सभी पाठ मेल खाते हैं, वाक्य रचना में भिन्नता के दर्जनों, शायद सैकड़ों, और यहां तक ​​कि एक ही वाक्यविन्यास के लिए शब्दार्थ में भी हैं।

Raku पाठ पैटर्न मिलान अभिव्यक्ति आमतौर पर या तो "नियम" या "रेगेक्स" कहलाते हैं। "रेगेक्स" शब्द का उपयोग इस तथ्य को बताता है कि वे कुछ अन्य रेगेक्स की तरह दिखते हैं (हालांकि सिंटैक्स को साफ किया गया है)। शब्द "नियम" इस तथ्य को बताता है कि वे सुविधाओं और उपकरणों के बहुत व्यापक सेट का हिस्सा हैं जो पार्सिंग (और परे) तक के पैमाने पर हैं।

जल्दी ठीक

"रेगेक्स" शब्द के उपरोक्त मूलभूत पहलू से, मैं अब आपके "रेगेक्स" व्यवहार के मूल पहलू की ओर मुड़ सकता हूं ।

अगर हम के लिए अपने व्याकरण में पैटर्न के तीन स्विच tokenकरने के लिए declarator regexdeclarator, अपने व्याकरण काम करता है के रूप में आप का इरादा:

grammar Parser {
    regex TOP       { <headerRow><valueRow>+ }
    regex headerRow { [\s*<header>]+\n }
    token header    { \S+ }
    regex valueRow  { [\s*<value>]+\n? }
    token value     { \S+ }
}

एक tokenऔर एक के बीच एकमात्र अंतर regexयह है कि regexबैकट्रैक tokenनहीं है। इस प्रकार:

say 'ab' ~~ regex { [ \s* a  ]+ b } # 「ab」
say 'ab' ~~ token { [ \s* a  ]+ b } # 「ab」
say 'ab' ~~ regex { [ \s* \S ]+ b } # 「ab」
say 'ab' ~~ token { [ \s* \S ]+ b } # Nil

अंतिम पैटर्न के प्रसंस्करण के दौरान (जिसे अक्सर "रेगेक्स" कहा जा सकता है, लेकिन जिसका वास्तविक घोषणाकर्ता है token, नहीं regex), वह \Sनिगल जाएगा 'b', जैसा कि अस्थायी रूप से पूर्व पंक्ति में रेगेक्स के प्रसंस्करण के दौरान किया गया होगा। लेकिन, क्योंकि पैटर्न एक के रूप में घोषित किया गया है token, नियम इंजन (उर्फ "रेगेक्स इंजन") पीछे नहीं हटता है , इसलिए समग्र इंजन विफल हो जाता है।

आपके ओपी में यही चल रहा है।

सही तय

सामान्य रूप से एक बेहतर समाधान यह है कि आप अपने आप को पीछे हटने वाले व्यवहार से दूर रखें , क्योंकि यह दुर्भावनापूर्ण रूप से निर्मित स्ट्रिंग के खिलाफ या पात्रों के आकस्मिक संयोग के साथ मेल खाने में उपयोग किए जाने पर धीमी गति से और भयावह रूप से धीमी (प्रोग्राम हैंगिंग से अप्रभेद्य) हो सकता है।

कभी-कभी regexउपयुक्त होते हैं। उदाहरण के लिए, यदि आप एक बंद लिख रहे हैं और एक regex काम करता है, तो आप कर रहे हैं। कोई बात नहीं। यही कारण है कि / ... /रक्कू में वाक्य रचना ठीक उसी तरह एक पीछे के पैटर्न की घोषणा करता है regex। (फिर आप फिर से लिख सकते / :r ... /हैं कि क्या आप रैचिंग पर स्विच करना चाहते हैं - "शाफ़्ट" का अर्थ "बैकट्रैक" के विपरीत है, इसलिए :rएक रेगेक्स को tokenशब्दार्थ में बदल देता है।)

कभी-कभी बैकट्रैकिंग की अभी भी एक पार्सिंग संदर्भ में भूमिका होती है। उदाहरण के लिए, जबकि raku के लिए व्याकरण आम तौर पर पीछे हटने से बच जाता है, और इसके बजाय सैकड़ों rules और tokens होते हैं, फिर भी यह अभी भी 3 regexs है।


मैंने upvoted @ user0721090601 ++ का उत्तर दिया है क्योंकि यह उपयोगी है। यह कई चीजों को भी संबोधित करता है जो मुझे तुरंत आपके कोड में मुहावरेदार लग रहे थे, और, महत्वपूर्ण रूप से, एस से चिपक जाता है token। यह आपके द्वारा पसंद किया जाने वाला उत्तर हो सकता है, जो अच्छा होगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.