Şönt yard algoritmasında fonksiyonun önceliği


10

Wikipedia tarafından tarif edildiği gibi , Shunting yard algoritması ile çalışıyorum .

Operatörlerle uğraşırken algoritmanın açıklaması aşağıdaki gibidir:

Jeton bir operatörse, o1, o zaman:

operatör yığınının üstünde bir operatör belirteci (o2) ve

o1 is left-associative and its precedence is less than or equal to
that of o2, or

o1 is right associative, and has precedence less than that of o2,

daha sonra operatör yığınından çıkış kuyruğuna o2 açın;

o1'i operatör yığınının üzerine itin.

Ancak, aşağıdaki örneği veriyorlar:

Giriş: sin max 2 3 / 3 * 3.1415

Algoritma /jetona çarptığında, ne olması gerektiği açıklaması aşağıdaki gibidir:

Token |        Action       |   Output (in RPN) |   Operator Stack
...
/     | Pop token to output | 2 3 max           | / sin 
...

Fonksiyon jetonunu maxçıkarıyor stackve içine koyuyorlar queue. Algoritmalarına göre, bu, fonksiyon belirtecinin hem bir operatör olduğu hem de öncekinden daha az önceliğe sahip olduğu anlamına gelir /.

Durumun böyle olup olmadığı konusunda bir açıklama yoktur. Peki, Shunting-yardalgoritma için bir işlevin önceliği nedir? İşlevler sağ veya sol ilişkilendirici midir? Yoksa wikipedia sadece eksik / yanlış mı?

Yanıtlar:


5

Doğrudan cevabın basitçe fonksiyonların operatör olmadığını düşünüyorum. Bağladığınız sayfadan:

Simge bir işlev belirteciyse, yığının üzerine itin.

Söylemesi gereken tek şey bu, çünkü işlev durumu (postfix öneki), operatör senaryosundan (postfix'e ek) çok daha basittir.

Takip eden sorular için: Öncelik ve ilişkilendirilebilirlik kavramlarına sadece çoklu infix operatörleri ile herhangi bir ifadedeki kalıtsal belirsizlik nedeniyle ihtiyaç duyulur. İşlev belirteçleri zaten önek gösterimini kullanıyor, bu yüzden bu sorunu yaşamıyorlar. Bilmen gerekmez olsun sinveya maxanlamaya buna "yüksek öncelik" vardır maxihtiyaçlarını ilk değerlendirilmesi gereken; belirteçlerin sırasından zaten açıktır. Bu nedenle bilgisayarlar, başlangıç ​​/ son düzeltme gösterimini başlamak için tercih eder ve neden infix'i ön / son düzeltmeye dönüştürmek için bu algoritmaya sahibiz.

Herhangi bir parantez olmadığında bir işlevin bağımsız değişkenlerinin başladığı ve bittiği yerde bir tür kuralınız olması gerekir; Ancak, infix operatörlerinin aksine, tüm işlevler için tek bir tutarlı kural, kompozisyonlarını tamamen açık hale getirmek için yeterlidir.


Algoritmaları doğrudur; bu onların yanlış örneğidir. Infix gösterimi, işlevleri saran parantez içermelidir:sin( max( 2 3) / 3 * 3.1415)
MirroredFate

Ben yanlış diyelim emin değilim, ama bu tüm fonksiyon çağrıları çevresinde parantez ve virgül gerektiren diller lehine güçlü bir argüman.
Ixrec

Bence infix'i algoritmayı kullanarak tarif ettikleri gibi ayrıştırmak imkansız.
MirroredFate

@Ixrec "Simge bir işlev belirteciyse, yığının üzerine itin" satırını görmüyorum. Wikipedia sayfasında. Şimdiye kadar düzenlenmiş olabilir. Ama algoritmadaki bir sayı ile aynı işlevi ele alabileceğimi mi söylüyorsun?
Abhinav

Wikipedia makalesinde algoritma açıklamasında bir hata olduğunu düşünüyorum (bugüne kadar). İfadeden sonra if there is a left parenthesis at the top of the operator stack, then: pop the operator from the operator stack and discard itbaşka bir satır eklenmelidir: yığının üstünde şimdi bir işlev adı varsa, yığından açın ve çıktıya itin . Birlikte yerleştirilir IF_FUNCTION sözdizimi anlaşılacağı gibi başka deyişle, sol '(' her zaman, işlev adıyla birlikte attı edilmelidir: " name(".
Stan

3

Dil sözdiziminize bağlı olarak dikkate alınması gereken iki farklı durum vardır. Diliniz işlev uygulamasını (örn. f(2+1)) Belirtmek için parantez kullanıyorsa , öncelik önemsizdir. İşlev yığının üzerine itilmeli ve sonra dışarı çıkarılmalıdır (yukarıdaki örnek için sonuç 2 1 + f). Alternatif olarak, işleve bir değer olarak davranabilir ve hemen çıktısını verebilir ve yakın parantezden sonra (başka bir parantezle aynı şekilde işlem görmesi gerekir), örneğin işlev çağırma işlemi f 2 1 + $nerede bir işlev çağırma işlemi çıktısı alabilirsiniz $.

Bununla birlikte, diliniz işlev çağrışımını belirtmek için parantez kullanmıyorsa, bunun yerine argümanı herhangi bir özel noktalama işareti olmadan doğrudan işlevden sonra yerleştiriyorsa (örneğin f 2 + 1, Wikipedia örneğinde olduğu gibi), işler biraz daha karmaşıktır. Bir örnek verdiğim ifadenin belirsiz olduğunu unutmayın: f , sonuca 2 ve 1'e eklenir mi, yoksa 2 ve 1'i birlikte ekleyip sonuçla birlikte f'yi çağırır mıyız ?

Yine, iki yaklaşım vardır. Karşılaştığınızda işlevi işleç yığınına itebilir ve istediğiniz önceliğe atayabilirsiniz. Bu en basit yaklaşımdır ve görünüşe göre alıntı yapılan örnek budur. Bununla birlikte, pratik konular var. İlk olarak, bir işlevi nasıl tanımlarsınız? Sonlu bir kümeniz varsa bu kolaydır, ancak kullanıcı tanımlı işlevleriniz varsa, ayrıştırıcınızın ortamınıza çok fazla geri beslenmesi gerektiği anlamına gelir, bu da hızlı bir şekilde dağınık hale gelebilir. Ve birden çok argümana sahip işlevleri nasıl ele alırsınız?

Benim düşüncem, bu sözdizimi stili için, işlevleri bir işlev uygulama operatörü tarafından işlenen değerler olarak kullanmak çok daha mantıklıdır. Ardından, bir değeri okuduğunuzda uygulama operatörünü enjekte edebilirsiniz ve okuduğunuz son şey de bir değerdir, bu nedenle hangi tanımlayıcıların işlevler olduğunu söylemenin özel bir yoluna ihtiyacınız yoktur. İşlevleri döndüren ifadelerle de çalışabilirsiniz (işlev olarak işlev stili ile zor veya imkansızdır). Ve bu, birden fazla argüman işlevini işlemek için köriyi kullanabileceğiniz anlamına gelir;

O zaman karar vermeniz gereken tek şey fonksiyon uygulamasının önceliğinin ne olduğudur. Seçim size kalmış, ancak bu şekilde çalışan her dilde, dilde en güçlü bağlayıcı operatör ve doğru ilişkilendirici olmuştur. (Tek ilginç varyasyon Haskell'dir ve güçlü bağlanma versiyonuna sahip olmanın yanı sıra $, dilde en zayıf bağlayıcı operatör olan, f 2 + 1f'ye 2'yi f $ 2 + 1uygulamak ve uygulamak gibi ifadelere izin veren sembol ile eşanlamlıdır. ifadenin geri kalanının tamamına)


3

Dijkstra'nın orijinal düşüncesini (Algol 60 derleyici makalesinde Sayfa 7-11, https://ir.cwi.nl/pub/9251 ) okuduktan ve sağlam bir çözüme ihtiyaç duyduktan sonra "şant sahasındaki fonksiyonlar" için istenenleri uyguladım , aşağıdakileri yaptı:

ayrıştırma:

  • İşlev tanımlayıcısına basın
  • Alt ifade parantezinin başlangıcı gibi "[" bağımsız değişken başlangıcı sol köşeli ayraçını itin.
  • Girişten "(" ila ")" dengeli bağımsız değişken listesi sırasını okuyun
  • Bunu çıkış jetonu akışına aktarın
  • "Dengeleyici kapanış braketi" gibi, bir argüman sonu sağ braketini "]" itin

Infix-postfix (manevra alanı):

  • Operatör yığını gibi başka bir yığın, fonksiyon yığını ekleyin
  • Bir işlev adını tararken, işlev bilgisini işlev yığınına itin
  • Bağımsız değişken sonu sağ köşeli ayraç göründüğünde, çıktı almak için işlev yığınını açın

Sağlam testlerde ve karmaşık senaryolarda mükemmel çalışır. Uygulamamda (komut satırı argümanları içeren ifadeler genişletici), çoklu argüman işlevlerini ve onları ayırmak için virgül "," belirtecini ve bu işlemin tamamı boyunca akmasını destekliyorum.

Örnekler, "3 2 ^ 4 2 ^ + sqrt" olan "sqrt (3 ^ 2 + 4 ^ 2)" ve nihayetinde programın argüman olduğunu düşündüğü "5" şeklindedir. Bu bignum, bu yüzden "" binom (64, 32) / gcd (binom (64, 32), binom (63, 31)) "==> büyük şeyler ==>" 2 "yardımcı olur." 123456 ^ 789 " 40.173 basamaklı ve zamanlama MacBookPro'mda "değerlendirme = 0.000390 sn" gösteriyor, çok hızlı.

Bunu tablolardaki verileri genişletmek ve bunu kullanışlı bulmak için de kullanıyorum. Her neyse, bu bir Dijkstra manevra alanı bağlamında fonksiyon çağrılarını, çoklu argümanları ve derin yuvalamayı dikkatle ele alma yolunda benim ipucum. Bugün bunu bağımsız düşünmeyle yaptım. Daha iyi yollar olup olmadığını bilmiyorum.

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.