ลำดับความสำคัญของฟังก์ชั่นในอัลกอริทึม Shunting-yard


10

ฉันกำลังทำงานผ่านอัลกอริทึม Shunting-yardดังที่วิกิพีเดียอธิบายไว้

คำอธิบายของอัลกอริทึมเมื่อจัดการกับตัวดำเนินการเป็นดังนี้:

ถ้าโทเค็นเป็นตัวดำเนินการ o1 ดังนั้น:

ในขณะที่มีโทเค็นของโอเปอเรเตอร์ o2 ที่ด้านบนสุดของโอเปอเรเตอร์สแต็กและอย่างใดอย่างหนึ่ง

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,

จากนั้นป๊อป o2 จากตัวดำเนินการสแต็กลงบนเอาต์พุตคิว

กด o1 ลงบนสแต็กโอเปอเรเตอร์

อย่างไรก็ตามพวกเขาให้ตัวอย่างต่อไปนี้:

การป้อนข้อมูล: sin max 2 3 / 3 * 3.1415

เมื่ออัลกอริทึมเข้าสู่/โทเค็นคำอธิบายของสิ่งที่ควรเกิดขึ้นมีดังนี้:

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

พวกเขาจะ popping โทเค็นฟังก์ชั่นmaxปิดและวางลงในstack queueตามอัลกอริทึมของพวกเขาสิ่งนี้ดูเหมือนว่าหมายถึงโทเค็นของฟังก์ชั่นเป็นทั้งโอเปอเรเตอร์และมีความสำคัญน้อยกว่า/โอเปอเรเตอร์

ไม่มีคำอธิบายว่ากรณีนี้เป็นจริงหรือไม่ ดังนั้นสำหรับShunting-yardอัลกอริธึมความสำคัญของฟังก์ชั่นคืออะไร? ฟังก์ชั่นมีความสัมพันธ์ด้านซ้ายหรือขวา? หรือวิกิพีเดียไม่สมบูรณ์ / ไม่ถูกต้อง?

คำตอบ:


5

ฉันเชื่อว่าคำตอบโดยตรงคือฟังก์ชั่นไม่ใช่ตัวดำเนินการ จากหน้าที่คุณลิงก์:

หากโทเค็นเป็นโทเค็นของฟังก์ชั่นให้กดลงบนสแต็ก

นี่คือทั้งหมดที่จำเป็นต้องพูดเนื่องจาก case case (prefix to postfix) นั้นง่ายกว่า case operator (infix to postfix)

สำหรับคำถามที่ตามมา: ความคิดของความสำคัญและความสัมพันธ์เป็นสิ่งจำเป็นเท่านั้นเนื่องจากความกำกวมที่สืบทอดในการแสดงออกใด ๆ ที่มีตัวดำเนินการมัดหลาย ฟังก์ชันโทเค็นกำลังใช้เครื่องหมายคำนำหน้าอยู่แล้วดังนั้นจึงไม่มีปัญหานั้น คุณไม่จำเป็นต้องรู้ว่ามี "ลำดับความสำคัญสูงกว่า" sinหรือไม่maxเพื่อพิจารณาว่าmaxจำเป็นต้องประเมินก่อนหรือไม่ ชัดเจนแล้วจากคำสั่งของโทเค็น นั่นเป็นสาเหตุที่คอมพิวเตอร์ต้องการให้มีเครื่องหมาย pre / postfix และทำไมเราจึงมีอัลกอริธึมนี้สำหรับการแปลงมัดเป็น pre / postfix

คุณจำเป็นต้องมีกฎบางอย่างสำหรับที่ข้อโต้แย้งของฟังก์ชั่นเริ่มต้นและสิ้นสุดเมื่อไม่มีวงเล็บอยู่ดังนั้นคุณสามารถพูดได้ว่าฟังก์ชั่น "มีความสำคัญ" เหนือโอเปอเรเตอร์หรือในทางกลับกัน แต่แตกต่างจากผู้ประกอบการมัดกฎที่สอดคล้องกันเพียงครั้งเดียวสำหรับฟังก์ชั่นทั้งหมดก็เพียงพอที่จะทำให้การจัดองค์ประกอบของพวกเขาอย่างสมบูรณ์ชัดเจน


อัลกอริทึมของพวกเขาถูกต้องแล้ว; มันเป็นตัวอย่างของพวกเขาที่ไม่ถูกต้อง สัญกรณ์ผูกควรรวมวงเล็บที่ห่อฟังก์ชั่น:sin( max( 2 3) / 3 * 3.1415)
MirroredFate

ฉันไม่แน่ใจว่าฉันจะเรียกมันไม่ถูกต้อง แต่นี่เป็นข้อโต้แย้งที่แข็งแกร่งในความโปรดปรานของภาษาที่ต้องใช้วงเล็บและเครื่องหมายจุลภาคในการเรียกใช้ฟังก์ชันทั้งหมด
Ixrec

ฉันคิดว่ามันไม่ถูกต้องเพราะเป็นไปไม่ได้ที่จะแยกวิเคราะห์มัดโดยใช้อัลกอริทึมตามที่พวกเขาอธิบาย
MirroredFate

@Ixrec ฉันไม่เห็นบรรทัด "หากโทเค็นเป็นฟังก์ชันโทเค็นให้กดลงบนสแต็ก" บนหน้า Wikipedia อาจแก้ไขได้ในขณะนี้ แต่คุณหมายถึงฉันสามารถปฏิบัติหน้าที่เหมือนกับตัวเลขในอัลกอริทึมได้หรือไม่?
Abhinav

ฉันเชื่อว่ามีข้อผิดพลาดในคำอธิบายอัลกอริทึมในบทความ Wikipedia (จนถึงปัจจุบัน) หลังจากวลีif there is a left parenthesis at the top of the operator stack, then: pop the operator from the operator stack and discard it, สายอื่นควรจะเพิ่ม: ถ้ามีชื่อฟังก์ชั่นที่ด้านบนของสแต็คในขณะนี้แล้ว pop ได้จากกองและผลักดันเข้าสู่การส่งออก กล่าวอีกนัยหนึ่งทางซ้าย '(' ควรจะโผล่ขึ้นมาพร้อมกับชื่อฟังก์ชั่นเสมอถ้ามันถูกนำมารวมกันเป็นไวยากรณ์ของฟังก์ชั่นหมายถึง: " name(".
Stan

3

การพิจารณามีสองกรณีที่แตกต่างกันขึ้นอยู่กับไวยากรณ์ภาษาของคุณ หากภาษาของคุณใช้วงเล็บเพื่อระบุแอปพลิเคชันฟังก์ชั่น (เช่นf(2+1)) การมีลำดับความสำคัญนั้นไม่เกี่ยวข้อง ฟังก์ชั่นควรจะถูกผลักลงบนสแต็กและโผล่ออกมาหลังจาก (ตัวอย่างข้างต้นผลลัพธ์คือ2 1 + f) หรือคุณสามารถรักษาฟังก์ชั่นเป็นค่าและการส่งออกได้ทันทีและเอาท์พุทการดำเนินงานฟังก์ชั่นการภาวนาหลังจากที่วงเล็บปิด (ซึ่งควรจะเป็นอย่างอื่นได้รับการปฏิบัติเช่นเดียวกับวงเล็บอื่น ๆ ) เช่นf 2 1 + $ที่$เป็นงานฟังก์ชั่นการภาวนา

หากภาษาของคุณไม่ใช้วงเล็บเพื่อระบุการเรียกใช้ฟังก์ชั่น แต่แทนที่อาร์กิวเมนต์โดยตรงหลังจากฟังก์ชั่นโดยไม่มีเครื่องหมายวรรคตอนพิเศษ (เช่นf 2 + 1) ตามที่เห็นได้ชัดสำหรับตัวอย่างของวิกิพีเดียสิ่งต่าง ๆ มีความซับซ้อนมากขึ้นเล็กน้อย โปรดทราบว่าการแสดงออกที่ฉันเพิ่งให้ตัวอย่างเป็นที่ไม่ชัดเจน: fถูกนำไปใช้กับ 2 และ 1 ถูกเพิ่มเข้ากับผลลัพธ์หรือเราจะเพิ่ม 2 และ 1 เข้าด้วยกันแล้วโทรfพร้อมผลลัพธ์หรือไม่

อีกครั้งมีสองวิธี คุณสามารถกดฟังก์ชั่นไปยังตัวดำเนินการสแต็กเมื่อคุณพบมันและกำหนดสิ่งที่คุณต้องการ นี่เป็นวิธีที่ง่ายที่สุดและเห็นได้ชัดว่าเป็นตัวอย่างที่ยกมา อย่างไรก็ตามยังมีปัญหาในทางปฏิบัติ ประการแรกคุณจะระบุฟังก์ชั่นได้อย่างไร หากคุณมีชุด จำกัด มันเป็นเรื่องง่าย แต่ถ้าคุณมีฟังก์ชั่นที่ผู้ใช้กำหนดหมายความว่าโปรแกรมแยกวิเคราะห์ของคุณต้องการฟีดกลับเข้าไปในสภาพแวดล้อมของคุณมากเกินไปซึ่งอาจทำให้ยุ่งได้อย่างรวดเร็ว และคุณจัดการฟังก์ชันที่มีอาร์กิวเมนต์หลายตัวได้อย่างไร

ความรู้สึกของฉันคือว่าสำหรับรูปแบบของไวยากรณ์นี้การใช้ฟังก์ชั่นเป็นค่าที่ใช้งานง่ายกว่าโดยผู้ประกอบการแอปพลิเคชั่นฟังก์ชั่นที่เหมาะสมกว่า จากนั้นคุณสามารถฉีดโอเปอเรเตอร์แอปพลิเคชันเมื่อใดก็ตามที่คุณอ่านค่าและสิ่งสุดท้ายที่คุณอ่านคือค่าดังนั้นคุณไม่จำเป็นต้องมีวิธีพิเศษในการบอกตัวระบุว่าฟังก์ชั่นใดเป็นพิเศษ นอกจากนี้คุณยังสามารถทำงานกับนิพจน์ที่คืนค่าฟังก์ชัน (ซึ่งเป็นเรื่องยากหรือเป็นไปไม่ได้ในรูปแบบฟังก์ชั่นการทำงานตามการใช้งาน) และนี่หมายความว่าคุณสามารถใช้การแกงเพื่อจัดการฟังก์ชันอาร์กิวเมนต์หลายตัวซึ่งเป็นการทำให้เข้าใจได้ง่ายกว่าการพยายามจัดการมันโดยตรง

สิ่งเดียวที่คุณต้องตัดสินใจก็คือความสำคัญของการใช้ฟังก์ชั่น ตัวเลือกนั้นขึ้นอยู่กับคุณ แต่ในทุกภาษาที่ฉันใช้มันทำงานได้ดีมันเป็นตัวดำเนินการที่มีผลผูกพันมากที่สุดในภาษาและมีความสัมพันธ์ที่ถูกต้อง (รูปแบบที่น่าสนใจเพียงอย่างเดียวคือ Haskell ซึ่งรวมถึงเวอร์ชั่นที่มีผลผูกพันอย่างยิ่ง) ยังมีคำพ้องความหมายด้วยสัญลักษณ์$ซึ่งเป็นโอเปอเรเตอร์ที่มีผลผูกพันน้อยที่สุดในภาษาทำให้การแสดงออกเหมือนf 2 + 1ใช้ f ถึง 2 และf $ 2 + 1นำไปใช้ ตลอดการแสดงออกที่เหลือทั้งหมด)


3

ฉันใช้คำสั่ง "ฟังก์ชั่นในการหลบหลีกลานจอดรถ" หลังจากอ่านความคิดดั้งเดิมของ Dijkstra (หน้า 7-11 ในกระดาษคอมไพเลอร์ Algol 60, https://ir.cwi.nl/pub/9251 ) และต้องการโซลูชันที่มีประสิทธิภาพฉัน ทำต่อไปนี้:

แยก:

  • กดตัวอธิบายฟังก์ชั่น
  • กดวงเล็บเริ่มจากซ้าย "[" เหมือนกับวงเล็บเริ่มต้นของนิพจน์ย่อย
  • อ่านลำดับรายการอาร์กิวเมนต์ที่สมดุล ("ถึง") "จากอินพุต
  • พุชสิ่งนี้กับสตรีมโทเค็นเอาต์พุต
  • กดวงเล็บปิดท้ายขวา "]" เช่นเดียวกับ "ชดเชยฉากปิด"

ใส่ไป postfix (หลาแบ่ง):

  • เพิ่มสแต็กอื่นฟังก์ชั่นสแต็คเช่นเดียวกับผู้ประกอบการสแต็ค
  • เมื่อสแกนชื่อฟังก์ชั่นให้กดข้อมูลฟังก์ชั่นไปยังฟังก์ชั่นสแต็ก
  • เมื่อเห็นวงเล็บเหลี่ยมที่อยู่ท้ายสุดให้ป๊อปอัพสแต็กไปยังเอาต์พุต

ทำงานอย่างสมบูรณ์แบบในการทดสอบที่แข็งแกร่งและสถานการณ์ที่ซับซ้อน ในแอปพลิเคชันของฉัน (ตัวขยายบรรทัดคำสั่งที่ประกอบด้วยอาร์กิวเมนต์ของนิพจน์) ฉันสนับสนุนฟังก์ชันอาร์กิวเมนต์หลายตัวและเครื่องหมายจุลภาค "," โทเค็นเพื่อแยกพวกมันออกและไหลผ่านกระบวนการทั้งหมด

ตัวอย่างเช่น "sqrt (3 ^ 2 + 4 ^ 2)" ซึ่งกลายเป็น "3 2 ^ 4 2 ^ + sqrt" และท้ายที่สุด "5" ซึ่งเป็นสิ่งที่โปรแกรมคิดว่าเป็นอาร์กิวเมนต์ มันเป็นบิกคัมดังนั้น "" ทวินาม (64, 32) / gcd (ทวินาม (64, 32), ทวินาม (63, 31)) "==> สิ่งที่ยิ่งใหญ่ ==>" 2 "มีประโยชน์" 123456 ^ 789 " คือ 40,173 หลักและเวลาแสดง "การประเมิน = 0.000390 วินาที" ใน MacBookPro ของฉันเร็วมาก

ฉันใช้สิ่งนี้เพื่อขยายข้อมูลในตารางและพบว่ามีประโยชน์ ยังไงก็ตามนี่เป็นคำแนะนำของฉันในการจัดการฟังก์ชั่นการโทรข้อโต้แย้งหลายข้อและการวางซ้อนอย่างลึกในบริบทการหลบหลีกของ Dijkstra เพียงแค่ทำในวันนี้จากการคิดอย่างอิสระ ไม่รู้ว่าอาจจะมีวิธีที่ดีกว่านี้หรือไม่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.