Bu tür çözümleyici adı, VEYA neden yok


27

Geleneksel ayrıştırıcılar girdilerinin tümünü tüketir ve tek bir ayrıştırma ağacı oluşturur. Sürekli bir akış tüketen ve ayrıştırma ormanı üreten birini arıyorum [ değiştir: bu terimin bu kullanımın neden sıradışı olabileceği ile ilgili yorumlarda tartışmaya bakın ]. Bağırsaklarım, böyle bir çözümleyiciye ihtiyaç duyan (ya da ihtiyacım olduğunu düşünen ) ilk kişi olamayacağımı söylüyor , ama aylarca boşuna aradım.

XY probleminden kurtulabileceğimi biliyorum. Nihai amacım, bir metin akışını ayrıştırmak, çoğunu yok sayarak ve tanınan bölümlerden bir ayrıştırma ağaç akışı üretmek.

Bu yüzden benim sorum şartlı: eğer bu özelliklere sahip bir sınıf ayrıştırıcı varsa, buna ne denir? Ve değilse, neden olmasın? Alternatif nedir? Belki de geleneksel ayrıştırıcıları istediğimi yapmaları için bir yoldan özlüyorum.


1
Temelde ayrıştırıcınız tek bir dokümanı ayrıştırır ve bir ayrıştırma ağacı verir, ardından hemen başka bir dokümanı ayrıştırmaya başlar, vb. Dolayısıyla bunun için özel bir terim eksikliği.
9000,

3
"Ayrıştırma Ormanı" için bir Google Araması yaptım ve Earley Ayrıştırıcısının bunları ürettiğini öğrendim .
Robert Harvey

7
Muhtemelen monadik ayrıştırıcı birleştiricileri mi arıyorsunuz - başka bir deyişle daha küçük ayrıştırıcılardan oluşan daha büyük bir ayrıştırıcı. Bir dilin "adasının" diğerine gömülü olduğu durumlar için kullanışlıdır. C # tasarım ekibindeki eski meslektaşım Luke Hoban'ın
Eric Lippert

3
Bazı karışıklıklar var. Akışınızdaki her belge için bir ayrıştırma ağacı istediğinizi ve bir ayrıştırma ormanı oluşturduğunu mu kastediyorsunuz? Ayrıştırma ormanının normal anlamı bu değildir. Ayrıştırma ormanı, farklı şekillerde ayrıştırılabilecek tek bir belirsiz belge (biraz basitleştirme) için ayrıştırma ağaçları kümesidir. Ve tüm cevaplar bununla ilgili. Akışınız çöplerle ayrılmış birçok tam belgeden mi oluşuyor yoksa kısmen deşifre edilmiş tek bir belge mi? Belgenizin sözdizimsel olarak doğru olup olmadığı mı yoksa? Doğru teknik cevap buna bağlı.
babou

1
O zaman ayrıştırma ormanları ve Earley, GLR, Marpa, türevleri ile ilgili tüm cevapları unutun. Görünüşe göre istediğin gibi değil, başka bir sebep ortaya çıkmadıkça Belgeleriniz sözdizimsel olarak doğru mu? Bazı ayrıştırma teknikleri, kısmen bozuk belgeler için bağlamı yeniden oluşturabilir. Bu belgeler için kesin bir sözdiziminiz var mı? Hepsi için aynı mı? Ayrıştırma ağaçlarını gerçekten istiyor musunuz, yoksa belgeleri izole ederek ve daha sonra bunları ayrı ayrı ayrıştırmaktan memnun musunuz? Sanırım işleminizi neyin iyileştirebileceğini biliyorum, ama bunu raftan kaldırabileceğinizden emin değilim.
babou

Yanıtlar:


48

Tüm girdi tüketilmeden önce (kısmi) bir sonuç veren bir ayrıştırıcıya artımlı ayrıştırıcı denir . Dilbilgisinde yalnızca daha sonra girişte karar verilen yerel belirsizlikler varsa, artan ayrıştırma zor olabilir. Bir diğer zorluk ise, ayrıştırma ağacının henüz ulaşılmayan kısımlarını feda etmektir.

Tüm olası ayrıştırma ağaçlarının bir ormanını döndüren bir ayrıştırıcı - yani belirsiz bir gramerin olası her bir türetmesi için bir ayrıştırma ağacı döndürür - denir… Bu şeylerin henüz bir adı olup olmadığından emin değilim. Marpa ayrıştırıcı jeneratörünün bunu yapabileceğini biliyorum , ancak herhangi bir Earley veya GLR tabanlı ayrıştırıcı bunu kaldırabiliyor olmalı.


Ancak, bunların hiçbirini istemiyor gibisin. Birden çok gömülü belgeye sahip bir akışınız var, bunlar arasında çöp var:

 garbagegarbage{key:42}garbagegarbage[1,2,3]{id:0}garbage...

Çöpten atlayan bir ayrıştırıcı istiyor gibisiniz ve (lazily) her belge için bir AST dizisi veriyor. Bu olabilir en genel anlamda bir artan ayrıştırıcı olarak değerlendirilebilir. Ama aslında böyle bir döngü uygularsınız:

while stream is not empty:
  try:
    yield parse_document(stream at current position)
  except:
    advance position in stream by 1 character or token

parse_docmentFonksiyon daha sonra geleneksel bir non-artımlı ayrıştırıcı olurdu. Başarılı bir ayrıştırma için giriş akışını yeterince okuduğunuzdan emin olmak için küçük bir zorluk var. Bunun nasıl kullanılabileceği, kullandığınız ayrıştırıcının türüne bağlıdır. Olasılıklar arasında belli ayrıştırma hataları üzerinde bir tamponun büyütülmesi veya tembel belirteci kullanılması sayılabilir.

Tembel belirteç, girdi akışınızdan dolayı muhtemelen en zarif çözümdür. Bir lexer aşamasının sabit bir belirteç listesi üretmesi yerine, ayrıştırıcı bir sonraki belirleyiciyi bir lexer geri çağrısından [1] tembel bir şekilde isteyecektir . Lexer daha sonra gerektiği kadar akımı tüketirdi. Bu şekilde, ayrıştırıcı yalnızca akışın gerçek sonuna ulaşıldığında veya gerçek bir ayrıştırma hatası oluştuğunda (yani, hala çöp içindeyken ayrıştırmaya başladık) başarısız olabilir.

[1] geri arama odaklı bir Lexer, diğer bağlamlarda da iyi bir fikirdir, çünkü bu en uzun belirteçli eşleşmeyle ilgili bazı problemleri önleyebilir .

Ne tür belgeler aradığınızı biliyorsanız, atlamayı yalnızca gelecek vaat eden yerlerde durmak için optimize edebilirsiniz. Örneğin, bir JSON belgesi her zaman {veya karakteri ile başlar [. Bu nedenle, çöp, bu karakterleri içermeyen herhangi bir dizedir.


5
Sahte kodunuz aslında yaptığım şeydi, ama sadece çirkin bir hack olduğunu düşündüm. Çözümleyici , akış konumunu ilerletmem veya daha fazla girdi beklemem gerekip gerekmediğini ayırt etmeme izin veren iki tür istisna ( NO_MATCHve UNDERFLOW) istisnası atar .
Kevin Krumwiede

5
@Kevin: Bir ağdan gelen verileri tescilli formatta ele almak için bazı güvenlik özelliklerinde bunu da kullanıyorum . Hiçbir şey hack!
Monica ile Hafiflik Yarışları

5

Bunu yapan bir ayrıştırıcı için belirli bir ad yoktur. Ancak bunu yapan bir algoritmayı vurgulayacağım: türevlerle ayrıştırma .

Her seferinde bir jeton girişi tüketir. Giriş sonunda bir ayrıştırma ormanı üretecektir. Alternatif olarak, ayrıştırma ortasında ( kısmi bir ayrıştırma) iken tüm ayrıştırma ormanını da alabilirsiniz .

Türevlerle ayrıştırma bağlamsız gramerleri işler ve belirsiz gramerler için bir ayrıştırma ormanı üretecektir.

Gerçekten de zarif bir teoridir, ancak yalnızca başlangıç ​​aşamasındadır ve yaygın bir şekilde konuşlandırılmamıştır. Matt Might'ın Scala / Racket / etc içindeki çeşitli uygulamalara bağlantılar listesi vardır.

Teori, türevlerle tanıma ile başlarsanız (yani, dillerin türevlerini almaya başlayarak, bunun geçerli olup olmadığını belirlemek için bir girdiyi tanıma ile başlarsanız) öğrenmesi daha kolaydır ve daha sonra türevlerle ayrıştırmak için programı değiştirir ( başka bir deyişle, dil türevlerini almak yerine , ayrıştırıcı türevlerini alır ve ayrıştırma ormanını hesaplar).


4
Downvoter: Lütfen bir downvoteun neye layık olduğunu açıklayabilir misiniz? Düzeltmem ya da geliştirmem gereken bir şey varsa, kesinlikle bilmek güzel olurdu.
Cornstalks

Ben indirici değilim ve yorum yapmadan indirmeyi hayal etmem. Ancak coşkulu makalenizde, karmaşıklığı ve ayrıştırma ormanıyla ilgili aynı sonucu elde eden varolan ayrıştırıcılara atıfta bulunulmamaktadır. İşlevsel programlama harika, ancak konuyla ilgili mevcut literatürle bir sonuç karşılaştırmak da güzel. Ayrıştırma ormanınız daha fazla kullanım için ne kadar uygundur?
babou

@babou: Kayıt için, o blog / makalenin yazarı değilim. Ancak evet, bu algoritmayı diğerleriyle karşılaştırarak daha fazla ayrıntı ekleyebileceğimi ve ayrıntılı olarak açıklayabileceğimi kabul ediyorum. Matt Might'ın bu konuda bir konferansı var , ancak bu cevabı birleştirmek güzel olurdu. Zamanım olursa bu cevabı genişletmeye çalışacağım.
Cornstalks

1
Genişletmek için çok fazla zaman harcamayın. Söyleyebileceğim kadarıyla, OP'nin peşinde olduğu şey bu değil. Sorusu dikkatli bir okuma gerektirir. Ayrıştırma ormanı kullanımı senin değil. - - Türevlerle ilgili olarak… ilginç gibi görünüyor, ama bir önceki eserle ilgili olmalı ... ve bunun önemli bir gövdesi var. Ancak bu cevapta kastetmiyorum, ama M Might'ın gazetelerinde ya da blogunda.
babou

2

İdeal olmaktan uzak, ancak bir kereden fazla yapıldığını gördüm: her giriş satırında ayrıştırmaya çalışın. başarısız olursa, çizgiyi koruyun ve bir sonrakini ekleyin. Sözde kodda:

buffer = ''
for each line from input:
    buffer = buffer + line
    if can parse buffer:
        emit tree
        buffer = ''

Büyük problem, bazı dillerde, bir sonraki satırı okumadan önce bir ifadenin tamamlanıp tamamlanmadığını bilemeyeceğinizdir. Bu durumda, bir sonrakini okuyabilir ve geçerli bir başlangıç ​​veya geçerli bir devam edip etmediğini kontrol edersiniz ... Ama bunun için tam bir dil sözdizimine ihtiyacınız var

Daha da kötüsü, bu dillerde, uzun bir ifade olmasa bile, dosyanın sonuna kadar çözümlenemeyen patolojik bir durum oluşturmak zor değildir.


0

Kısaca

Sorununuza en hızlı çözüm, belgelerin olası tüm başlangıçlarını tanıyan bir REGEX veya bir FSA (sonlu durum otomatiği) tanımlamak gibi görünüyor (aslında bir belgeye karşılık gelmeyen yanlış pozitiflere izin verilir). Ardından, bir belgenin birkaç hatayla başlayabileceği bir sonraki yeri tanımlamak için girdilerinizde çok hızlı şekilde çalıştırabilirsiniz. Bir belge başlangıcı için birkaç hatalı konuma neden olabilir, ancak ayrıştırıcı tarafından tanınacak ve terk edilecektir.

Bu yüzden Sonlu Durum Otomatı , aradığınız çözümleyici adı olabilir. :)

Sorun

Pratik bir sorunu anlamak her zaman zordur, özellikle de kelime haznesinde çok fazla yorum olduğunda. Ayrıştırma ormanı kelimesi, çeşitli ayrıştırma ağaçlarına sahip belirsiz cümlelerin ayrıştırılması için Bağlamsız (CF) kelimesiyle ayrıştırıldı (afaik). Bir cümle örgüsünü veya diğer dilbilgisi türlerini ayrıştırmak için genelleştirilebilir. Dolayısıyla, Earley, GLR, Marpa ve türev ayrıştırıcılarla ilgili tüm cevaplar (bu konuda pek de geçerli değil) var.

Ama görünüşe göre aklınızdaki şey bu değil. Belli başlı belgelerin bir dizisi olan benzersiz bir dize ayrıştırmak ve her biri için bir ayrıştırma ağacı veya bir tür yapılandırılmış gösterim elde etmek istiyorsunuz; resmi bir dil bakış açısı. Elinizde olan, bir belgenin başında başlatıldığında ayrıştırma işini yapacak bir algoritma ve tablolardır. Öyle olsun.

Asıl sorun, doküman akışınızın dokümanları ayıran önemli miktarda çöp içermesidir. Görünen o ki, senin zorluğun bu çöpleri yeterince hızlı taramak. Şu anki tekniğiniz en baştan başlamak ve ilk karakterden taramaya çalışmak ve tüm belgeyi taranana kadar bir sonraki karakterde yeniden başlatmaya geçmek. Ardından, belge tarandıktan sonra ilk karakterden gelen ifadeleri tekrarlayın.

Bu aynı zamanda cevabın ikinci bölümünde @ amon tarafından önerilen çözümdür .

Çok hızlı bir çözüm olmayabilir (test edemem), çünkü çözümleyici kodunun bir belgenin başlangıcında çok verimli bir şekilde başlatılması için optimize edilmiş olması olası değildir. Normal kullanımda bunu yalnızca bir kez yapar, böylece optimizasyon açısından sıcak bir nokta değildir. Bu nedenle, bu çözüm ile ılımlı mutluluğunuz çok şaşırtıcı değil.

Öyleyse gerçekten ihtiyacınız olan, hızlı bir şekilde bir çöp kütlesiyle başlayan bir belgenin başlangıcını bulabilen bir algoritma. Ve şanslısınız: bu tür algoritmalar var. Ve bildiğinizden eminim: buna bir REGEX aramak denir.

Basit bir çözüm

Yapmanız gereken, bu belgelerin nasıl başladığını bulmak için belgelerinizin özelliklerini analiz etmektir. Size tam olarak nasıl söyleyemem, onların sözdizimi özelliklerinin biçimsel olarak nasıl düzenlendiğinden emin değilim. Muhtemelen hepsi sınırlı bir listeden bir sözcükle başlar, muhtemelen bazı noktalama işaretleri veya sayılarla karıştırılırlar. Bu senin kontrol etmen için.

Yapmanız gereken, sonlu durumlu bir otomatiği (FSA) tanımlamak veya çoğu programcı için eşdeğerde bir belgenin ilk birkaç karakterini tanıyabilen normal bir ifade (REGEX) tanımlamaktır: ne kadar çok, o kadar iyi, ama çok fazla olması gerekmez büyük (bu zaman ve mekan alabilir). Bu, belgelerinizin özelliklerini belirlemek için nispeten kolay olmalı ve muhtemelen belgelerinizin özelliklerini okuyan bir programla otomatik olarak yapılabilir.

Regexp'inizi oluşturduktan sonra, ilk (veya sonraki) belgenizin başlangıcına çok hızlı bir şekilde ulaşmak için giriş akışınızda çalıştırabilirsiniz:

Ben varsayalım:
- docstarttüm belgelerin başlangıcını eşleşen bir düzenli ifade ise
- search(regex, stream)bir işlev olduğunu aramalar streameşleştiği bir alt dizeyi regex. Döndüğünde, akış ilk eşleşen alt dizenin başlangıcından başlayarak sonek alt akışına indirgenir veya boş akışa eşleşme bulunmaz.
- parse(stream)bir belgeyi akışın başından itibaren (geriye kalanı) ayrıştırmaya çalışır ve ayrıştırma ağacını ne olursa olsun veya geri döndürür. Geri döndüğünde akış, ayrıştırılan belgenin sonunu hemen takip eden pozisyondan başlayarak sonek alt akışına indirgenir. Ayrıştırma başarısız olursa bir istisna çağırır.

forest = empty_forest
search(docstart, stream)
while stream is not empty:
  try:
    forest = forest + parse(stream)
  except
    remove first character from stream
  search(docstart, stream)

İlk karakterin kaldırılması gerektiğine dikkat edin, böylece sonraki arama aynı eşleşmeyi tekrar bulamaz.

Elbette akışın kısaltılması bir görüntüdür. Sadece akışta bir dizin olabilir.

Son bir not, regex'iniz tüm başlangıçları tanıdığı sürece çok doğru olması gerekmediğidir. Zaman zaman bir belgenin başlangıcı olamayacak bir dize tanırsa (yanlış pozitif), o zaman tek ceza ayrıştırıcıya gereksiz bir çağrının maliyetidir.

Böylece, eğer mümkünse regex'in basitleştirilmesine yardımcı olabilir.

Daha hızlı bir çözüm olasılığı hakkında

Yukarıdaki çözüm çoğu durumda oldukça iyi çalışmalıdır. Bununla birlikte, işlenecek çok fazla çöp ve terabayt dosyanız varsa, daha hızlı çalışan başka algoritmalar olabilir.

Bu fikir Boyer-Moore string arama algoritmasından türetilmiştir . Bu algoritma, tek bir dizge için çok hızlı bir akış arayabilir çünkü dizgenin yapısal bir analizini kullanır, akışın çoğunu okumak, parçalara bile bakmadan atlamak için yapısal bir analiz kullanır. Tek bir dize için en hızlı arama algoritmasıdır.

Zorluk, tek bir dize yerine regex'i aramaya adapte etmenin, düşündüğünüz regex'in özelliklerine bağlı olarak çok narin göründüğü ve işe yaramayabileceğidir. Bu daha sonra ayrıştırdığınız belgelerin sözdizimine bağlı olabilir. Ancak bulduğum belgelerin dikkatlice okunması için zamanım olmadığından bana bu konuda fazla güvenme.

Sizi web’de bulduğum bir ya da iki işaretle bırakıyorum , görünüşe göre hakemli bir araştırma makalesi de dahil olmak üzere , ancak bunu yalnızca güçlü performans problemleriniz varsa düşünülmeli, daha spekülatif, muhtemelen araştırma olarak düşünmelisiniz. Ve muhtemelen bunu yapacak raf programlarından da yok.


-2

Tarif ettiğiniz şey SAX ve SOM olarak tanımlanabilir.

SAX - (XML için Basit API), XML belgeleri için XML-DEV posta listesi tarafından geliştirilen olay sıralı erişim ayrıştırıcı API'dir.

SOM - (XML Şema Nesne Modeli), bir XML dosyasının hafıza temsilinde rastgele erişim

Her iki tipte de C # ve Java ile uygulamalar var ve muhtemelen daha birçok şey var. Genellikle bir XSD veya DTD isteğe bağlıdır.

SAX'ın sevinci, büyük XML dosyaları için harika olan düşük bellek yükü olmasıdır. Takas, SAX kullanarak yapılan rasgele erişimin ya varolmayan ya da yavaştır olması ve geliştirme zamanının daha da kötüsü SOM'dan daha fazla olması. SOM ile bariz bir problem, potansiyel olarak büyük RAM gereksinimleridir.

Bu cevap tüm platformlar ve tüm diller için geçerli değildir.


1
OP'nin neden XML'yi ayrıştırdığını düşünüyorsunuz?
Dan Pichelman,

1
Bu soruya cevap vermiyor.

@Snowman Neredeyse şu ana kadar hiçbir şey, kabul edilen cevabın ilk yarısı da dahil olmak üzere soruyu yanıtlamadı. Kimseyi seçmenin anlamı yok. Sorunun dikkatli bir şekilde okunması gerekiyor.
babou

@babou Kimseyi seçmiyordum, olumsuz oyumu açıklıyordum.

@ Downman oyumu açıklayan snowman . Bu adil ve daha fazla kullanıcının bunu yapmasını diliyorum. Ben anadili değilim: onu seçmek çok güçlü bir ifade olabilir. Sadece herkes sınırsız varsayımlarda bulunuyor. Bu yüzden fark etmeye bile değmez. Bunun diğerlerinden biraz daha uzakta olduğu doğru.
babou
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.