TSV metnini ayrıştırmak için bir Raku dilbilgisi nasıl tanımlayabilirim?


13

Bazı TSV verilerim var

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

Bunu bir karma listesine ayrıştırmak istiyorum

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

Üstbilgi satırını değer satırlarından ayırmak için newline meta karakteri kullanma konusunda sorun yaşıyorum. Benim dilbilgisi tanımı:

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);

Ama bu geri dönüyor Nil. Bence raku'daki regexes hakkında temel bir şeyi yanlış anlıyorum.


1
Nil. Geri bildirim gittikçe kısır, değil mi? Hata ayıklama için, henüz yapmadıysanız virgül indirin ve / veya Gramerlerdeki hata bildirimi nasıl geliştirilebilir? Konusuna bakın. . NilÇünkü deseniniz geri izleme anlambilimi aldınız . Bu konudaki cevabımı gör. Geri takip etmenizi tavsiye ederim. @ User0721090601'in bu konudaki cevabına bakınız. Saf pratiklik ve hız için JJ'nin cevabına bakın. Ayrıca, "Raku ile X'i ayrıştırmak istiyorum. Birisi yardımcı olabilir mi?" .
Mart'ta

Gramer kullanın :: Tracer; #works for me
p6steve

Yanıtlar:


12

Muhtemelen fırlatan ana şey \syatay ve dikey alanla eşleşmesidir . Yalnızca yatay boşluğu eşleştirmek için kullanın \hve yalnızca dikey boşluğu eşleştirmek için kullanın \v.

Yapacağım küçük bir öneri, yeni satırları jetona dahil etmekten kaçınmaktır. Ayrıca, alternatif işleçleri de kullanmak isteyebilirsiniz %veya %%bu tür işleri yürütmek üzere tasarlandıkları için:

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* }
} 

Bunun sonucu Parser.parse($dat)şudur:

「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 => 「」

bu bize dilbilgisinin her şeyi başarıyla ayrıştırdığını gösterir. Ancak, sorunuzun sizin için bir değişkente kullanılabilir olmasını istediğiniz ikinci kısmına odaklanalım. Bunu yapmak için, bu proje için çok basit bir eylem sınıfı sağlamanız gerekir. Sadece yöntemleri dilbilginizin yöntemleriyle eşleşen bir sınıf yaparsınız (dizgi oluşturma dışında özel işlem gerektirmeyen value/ çok basit olanlar headergöz ardı edilebilir). Sizinkinin işlenmesini ele almanın daha yaratıcı / kompakt yolları var, ancak örnekleme için oldukça temel bir yaklaşımla devam edeceğim. İşte sınıfımız:

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

Her yöntemin ($/)regex eşleştirme değişkeni olan imzası vardır. Şimdi, her jetondan hangi bilgileri istediğimizi soralım. Üstbilgi satırında, üstbilgi değerlerinin her birini arka arkaya istiyoruz. Yani:

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

Üzerinde niceleyicinin ile herhangi belirteç bir şekilde ele alınacaktır Positionalbiz de her birey başlık maçı erişebilir, böylece $<header>[0], $<header>[1]vb Ama bu eşleme nesnelerdir biz sadece hızlı bir şekilde stringify yüzden. makeKomut diğer belirteçleri Oluşturduğumuz bu özel verilere erişmesine olanak tanır.

Değer satırımız aynı görünecektir, çünkü $<value>jetonlar önem verdiğimiz şeydir.

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

Son yönteme geldiğimizde, karma içeren bir dizi oluşturmak isteyeceğiz.

  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;
  }

Burada biz işlenen şeyler erişmek nasıl görebilirsiniz headerRow()ve valueRow()kullanabilirsiniz: .madeyöntemini. Birden fazla valueRows olduğundan, madedeğerlerinin her birini elde etmek için bir harita yapmamız gerekir (bu, dilbilgimi basitçe dilbilgisine sahip olmak <header><data>ve verileri birden çok satır olarak tanımlamak için kullandığım bir durumdur, ancak bu yeterince basit değil).

Şimdi iki dizide başlıklara ve satırlara sahip olduğumuza göre, bu onları fordöngüde yaptığımız bir dizi karma yapma meselesidir . flat @x Z @ySadece unsurları intercolates ve karma atama Biz Mean Ne mi ama istediğiniz karma dizisini almak için başka yolları da vardır.

İşiniz bittiğinde, sadece siz makeve daha sonra madeayrıştırma işleminde kullanılabilir olacak :

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

Bunları bir yönteme sarmak oldukça yaygındır.

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

Bu şekilde söyleyebilirsin

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

Eylem sınıfını farklı yazacağımı düşünüyorum. 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 }Tabii ki önce bunu başlatmanız gerekir :actions(Actions.new).
Brad Gilbert

@BradGilbert evet, somutlaşmayı önlemek için eylem sınıflarımı yazmaya eğilimliyim, ancak somutlaştırıyor olsaydım, muhtemelen yapacaktım class Actions { has @!header; has %!entries … }ve sadece valueRow girişlerini doğrudan ekleyecek şekilde ekleyeceğim method TOP ($!) { make %!entries }. Ama sonuçta bu Raku ve TIMTOWTDI :-)
user0721090601

Bu bilgileri ( docs.raku.org/language/regexes#Modified_quantifier:_%,_%% ) okuyarak , anladığımı düşünüyorum <valueRow>+ %% \n(yeni satırlarla sınırlandırılmış satırları yakala), ancak bu mantığı izleyerek <.ws>* %% <header>"yakalama isteğe bağlı" msgstr "boşluk içermeyen boşluk". Bir şey mi kaçırıyorum?
Christopher Bottoms

@ChristopherBottoms neredeyse. <.ws>(Yakalama gelmez <ws>olur). OP, TSV formatının isteğe bağlı bir boşlukla başlayabileceğini belirtti. Gerçekte, bu muhtemelen \h*\n\h*valueRow'un daha mantıklı olarak<header> % <.ws>
user0721090601

@ user0721090601 Daha önce okuduğumu hatırlamıyorum %/ %%"alternatif" olarak adlandırdım. Ama bu doğru isim. (İçin bunun kullanımı ise |, ||ve kuzenler zaman garip olarak beni vurdu.). Daha önce bu "geriye" tekniği düşünmemiştim. Ancak, sadece desenin eşleşmeleri arasında değil, aynı zamanda her iki uçta (kullanarak %%) veya başlangıçta değil, bitmeyen (kullanarak %), bazı ayırıcı iddiasıyla tekrarlanan bir desenle eşleşen düzenli ifadeler yazmak için güzel bir deyim , alternatifi sonunda ruleve başlangıç ​​mantığı ve :s. Güzel. :)
raiph

11

TL; DR: bilmiyorsunuz. Text::CSVHer formatla başa çıkabilmek için kullanın .

Text::CSVMuhtemelen kaç yaşında yararlı olacağını göstereceğim :

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;

Buradaki anahtar kısım, ilk dosyayı bir diziye veya dizilere (in @data) dönüştüren veri ayıklamasıdır . Bununla birlikte, sadece gerekli, çünkü csvkomut dizelerle başa çıkamıyor; veriler bir dosyadaysa, hazırsınız demektir.

Son satır yazdırılacaktır:

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

Kimlik alanı karmanın ve her şey bir dizi karmanın anahtarı olacaktır.


2
Pratiklik nedeniyle yukarı oy. Yine de, OP'nin gramerleri (cevabımın yaklaşımı) öğrenmeyi daha fazla amaçlayıp hedeflemediğini veya sadece ayrıştırmaya (cevabınızın yaklaşımı) ihtiyacı olup olmadığından emin değilim. Her iki durumda da, gitmek için iyi olmalı :-)
user0721090601

2
Aynı nedenden dolayı oy kullanıldı. :) OP'nin normal ifade semantiği (dolayısıyla cevabım) açısından neyi yanlış yaptıklarını öğrenmeyi, doğru yapmayı (cevabınızı) öğrenmeyi veya sadece ayrıştırmayı (JJ cevabı) öğrenmeyi amaçladığını düşünmüştüm. ). Takım çalışması. :)
raiph

7

TL; DR regex nin geri izi. tokenyok. Bu yüzden deseniniz uyuşmuyor. Bu cevap bunu açıklamaya ve gramerinizi nasıl düzelteceğinize odaklanıyor. Bununla birlikte, muhtemelen yeniden yazmalı veya mevcut bir ayrıştırıcı kullanmalısınız, bu da raku regexes'i öğrenmek yerine TSV'yi ayrıştırmak istiyorsanız kesinlikle yapmanız gereken şeydir .

Temel bir yanlış anlama?

Bence raku'daki normal ifadelerle ilgili temel bir şeyi yanlış anlıyorum.

("Normal ifadeler" teriminin zaten oldukça belirsiz olduğunu biliyorsanız, bu bölümü atlamayı düşünün.)

Yanlış anlayabileceğiniz temel şeylerden biri "normal ifadeler" sözcüğünün anlamıdır. İşte halkın varsaydığı bazı popüler anlamlar:

  • Resmi düzenli ifadeler.

  • Perl regexes.

  • Perl Uyumlu Düzenli İfadeler (PCRE).

  • Yukarıdakilerden herhangi birine benzeyen ve benzer bir şey yapan "regexes" adı verilen metin deseni eşleştirme ifadeleri.

Bu anlamların hiçbiri birbiriyle uyumlu değildir.

Perl regex'leri semantik olarak resmi düzenli ifadelerin üst kümesidir, ancak birçok yönden çok daha yararlıdırlar, ancak aynı zamanda patolojik geri izlemeye karşı daha savunmasızdırlar .

Perl Uyumlu Düzenli İfadeler, 1990'ların sonunda orijinal Perl regexes'leriyle aynı oldukları ve Perl'in PCRE motoru dahil takılabilir regex motorlarını desteklediği anlamıyla Perl ile uyumlu olsa da, PCRE regex sözdizimi standartla aynı değildir Perl regex 2020'de Perl tarafından varsayılan olarak kullanılır.

"Normal ifadeler" olarak adlandırılan metin modeli eşleme ifadeleri genellikle birbirine benziyor ve tüm metinleri eşleştiriyor olsa da, sözdiziminde ve hatta aynı sözdiziminin anlamlarında düzinelerce, belki de yüzlerce varyasyon var.

Raku metin deseni eşleştirme ifadelerine genellikle "kurallar" veya "normal ifadeler" denir. "Regexes" teriminin kullanımı, diğer regex'lere benzediği gerçeğini ifade eder (sözdizimi temizlenmesine rağmen). "Kurallar" terimi , ayrıştırmaya (ve ötesine) kadar ölçeklenen çok daha geniş bir özellik ve araç grubunun parçası olduklarını ifade eder .

Hızlı düzeltme

"Regexes" kelimesinin yukarıdaki temel yönü ortadan kalktığında, artık "regex" davranışınızın temel yönüne dönebilirim .

Biz sizin dilbilgisi kalıplarını üçünü geçerseniz tokeniçin Bildiricisi regexBildiricisi istediğiniz gibi, sizin dilbilgisi çalışır:

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

A tokenve a arasındaki tek fark regex, regexgeriye doğru izlenirken, a'nın tokenolmamasıdır. Böylece:

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

Son paternin işlenmesi sırasında (ki genellikle "regex" olarak adlandırılabilir, ancak gerçek beyanı tokenolmayan, değil regex), önceki satırdaki regex'in işlenmesi sırasında geçici olarak yaptığı gibi \Syutulur 'b'. Ancak, kalıp bir olarak bildirildiğinden token, kural motoru ("normal ifade motoru" olarak da bilinir) geri gitmez , bu nedenle genel eşleşme başarısız olur.

OP'nizde olan bu.

Doğru düzeltme

Genel olarak daha iyi bir çözüm kendinizi vazgeçirmek için varsayarak yavaş ve (program asılı ayırt edilemez) bile felaket yavaş olabilir, çünkü bir karakter yanlışlıkla talihsiz kombinasyonu ile kötü amaçla inşa dize veya birine karşı eşleştirme kullanıldığında, geri izleme davranışı.

Bazen regexs uygundur. Örneğin, bir kereye mahsus yazarsanız ve regex işi yaparsa, işiniz bitti demektir. Bu iyi. Bu / ... /, raku'daki sözdiziminin, tıpkı bir geri izleme modeli beyan etmesinin nedeninin bir parçası regex. (Daha sonra / :r ... /, cırcırlamayı açmak istiyorsanız tekrar yazabilirsiniz - "cırcır", "geri izlemenin " tersi anlamına gelir, bu nedenle :rnormal ifadeyi tokensemantiğe geçirir.)

Bazen geri izleme, ayrıştırma bağlamında hala bir role sahiptir. Örneğin, raku dilbilgisi genellikle geri izlemeden kaçınır ve bunun yerine yüzlerce rules ve tokens olsa da, yine de 3 regexs vardır.


Yararlı olduğu için @ user0721090601 ++ 'ın cevabını iptal ettim. Ayrıca, bana hemen kodunuzda deyimsel olarak kapalı görünen bazı şeylere de hitap eder ve daha da önemlisi, tokens'ye yapışır . Tercih ettiğiniz cevap iyi olabilir, ki bu harika olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.