Saya memiliki ide ini berkeliaran di kepala saya, untuk menghasilkan dan mengevaluasi ekspresi matematika acak. Jadi, saya memutuskan untuk mencobanya dan menguraikan algoritma, sebelum mengkodekannya untuk mengujinya.
Contoh:
Berikut adalah beberapa contoh ekspresi yang ingin saya hasilkan secara acak:
4 + 2 [easy]
3 * 6 - 7 + 2 [medium]
6 * 2 + (5 - 3) * 3 - 8 [hard]
(3 + 4) + 7 * 2 - 1 - 9 [hard]
5 - 2 + 4 * (8 - (5 + 1)) + 9 [harder]
(8 - 1 + 3) * 6 - ((3 + 7) * 2) [harder]
Yang mudah dan sedang cukup mudah. Acak int
dipisahkan oleh operator acak, tidak ada yang gila di sini. Tapi aku mengalami beberapa masalah memulai dengan sesuatu yang bisa membuat salah satu keras dan lebih keras contoh. Saya bahkan tidak yakin algoritma tunggal dapat memberi saya dua yang terakhir.
Apa yang saya pertimbangkan:
Saya tidak bisa mengatakan saya mencoba ide-ide itu, karena saya tidak benar-benar ingin membuang banyak waktu untuk pergi ke arah yang tidak memiliki kesempatan untuk bekerja di tempat pertama. Tapi tetap saja, saya memikirkan beberapa solusi:
- Menggunakan pohon
- Menggunakan ekspresi reguler
- Menggunakan loop "for-type" yang gila (pasti yang terburuk)
Apa yang saya cari:
Saya ingin tahu ke arah mana Anda percaya yang terbaik, antara solusi yang saya pertimbangkan, dan ide-ide Anda sendiri.
Jika Anda melihat cara yang baik untuk memulai, saya akan menghargai petunjuk di arah yang benar, misalnya dengan awal algoritma, atau struktur umum dari itu.
Perhatikan juga bahwa saya harus mengevaluasi ekspresi itu. Ini dapat dilakukan baik setelah ekspresi dihasilkan, atau selama pembuatannya. Jika Anda mempertimbangkannya dalam jawaban Anda, itu bagus.
Saya tidak mencari apa pun yang berhubungan dengan bahasa, tetapi sebagai catatan, saya berpikir untuk mengimplementasikannya di Objective-C, karena itulah bahasa yang paling saya gunakan saat ini.
Contoh-contoh itu tidak termasuk :
operator, karena saya hanya ingin memanipulasi int
, dan operator ini menambahkan banyak verifikasi. Jika jawaban Anda memberikan solusi menangani yang ini, itu bagus.
Jika pertanyaan saya membutuhkan klarifikasi, silakan tanyakan di komentar. Terima kasih atas bantuan Anda.
sumber
Jawaban:
Berikut ini adalah interpretasi teoretis dari masalah Anda.
Anda mencari untuk menghasilkan kata-kata secara acak (ekspresi aljabar) dari bahasa tertentu (rangkaian tak terbatas dari semua ekspresi aljabar yang benar secara sintaksis). Berikut adalah deskripsi formal tata bahasa aljabar sederhana yang hanya mendukung penambahan dan perkalian:
Di sini,
E
adalah ekspresi (yaitu, kata dari bahasa Anda) danI
merupakan simbol terminal (yaitu, itu tidak diperluas lebih jauh) yang mewakili integer. Definisi di atas untukE
memiliki tiga aturan produksi . Berdasarkan definisi ini, kita dapat secara acak membangun aritmatika yang valid sebagai berikut:E
sebagai simbol tunggal dari kata keluaran.I
dengan bilangan bulat acak.Berikut ini contoh penerapan algoritma ini:
Saya berasumsi Anda akan memilih untuk mewakili ekspresi dengan antarmuka
Expression
yang diimplementasikan oleh kelasIntExpression
,AddExpression
danMultiplyExpression
. Dua yang terakhir kemudian akan memilikileftExpression
danrightExpression
. SemuaExpression
subclass diharuskan untuk mengimplementasikan suatuevaluate
metode, yang bekerja secara rekursif pada struktur pohon yang ditentukan oleh objek-objek ini dan secara efektif mengimplementasikan pola komposit .Perhatikan bahwa untuk tata bahasa dan algoritma di atas, probabilitas memperluas ekspresi
E
ke simbol terminalI
hanyap = 1/3
, sedangkan probabilitas untuk memperluas ekspresi menjadi dua ekspresi lebih lanjut adalah1-p = 2/3
. Oleh karena itu, jumlah bilangan bulat yang diharapkan dalam rumus yang dihasilkan oleh algoritma di atas sebenarnya tidak terbatas. Panjang yang diharapkan dari suatu ekspresi tunduk pada relasi yang berulangdi mana
l(n)
menunjukkan panjang yang diharapkan dari ekspresi aritmatika setelahn
penerapan aturan produksi. Karena itu saya menyarankan agar Anda menetapkan probabilitas yang agak tinggip
untuk aturanE -> I
sehingga Anda berakhir dengan ekspresi yang cukup kecil dengan probabilitas tinggi.EDIT : Jika Anda khawatir bahwa tata bahasa di atas menghasilkan terlalu banyak tanda kurung, lihat jawaban Sebastian Negraszus , yang tata bahasanya menghindari masalah ini dengan sangat elegan.
sumber
m
iterasi 2-4, Anda 'mengabaikan' aturan produksi rekursif. Ini akan mengarah pada ekspresi ukuran yang diharapkanl(m)
. Namun perlu dicatat, bahwa ini (secara teoritis) tidak perlu, karena probabilitas menghasilkan ekspresi tak terbatas adalah nol, meskipun ukuran yang diharapkan tak terbatas. Namun, pendekatan Anda menguntungkan karena dalam praktiknya, memori tidak hanya terbatas, tetapi juga kecil :)pertama-tama saya benar-benar menghasilkan ekspresi dalam notasi postfix , Anda dapat dengan mudah mengkonversi ke infix atau mengevaluasi setelah menghasilkan ekspresi acak Anda, tetapi melakukannya dalam postfix berarti Anda tidak perlu khawatir tentang tanda kurung atau prioritas.
Saya juga akan terus menjalankan total jumlah istilah yang tersedia untuk operator berikutnya dalam ekspresi Anda (dengan asumsi Anda ingin menghindari menghasilkan ekspresi yang cacat) yaitu sesuatu seperti ini:
jelas ini adalah kode semu sehingga tidak diuji / mungkin mengandung kesalahan dan Anda mungkin tidak akan menggunakan string tetapi tumpukan beberapa jenis serikat seperti
sumber
2+4*6-3+7
akan dikonversi menjadi post-fix stack+ 7 - 3 + 2 * 4 6
(bagian atas stack paling kanan). Anda menekan 4 dan 6 dan menerapkan operator*
, kemudian Anda mendorong 24 kembali. Anda kemudian pop 24 dan 2 dan menerapkan operator +, kemudian Anda mendorong 26 kembali. Anda melanjutkan dengan cara ini dan Anda akan menemukan jawaban yang tepat. Perhatikan bahwa* 4 6
ini adalah istilah pertama pada stack. Itu berarti ini dilakukan terlebih dahulu karena Anda sudah menentukan prioritas tanpa memerlukan tanda kurung.jawaban blubb adalah awal yang baik, tetapi tata bahasa formalnya menciptakan terlalu banyak paranthes.
Inilah pendapat saya:
E
adalah ekspresi,I
integer danM
ekspresi yang merupakan argumen untuk operasi multiplikasi.sumber
Tanda kurung dalam ekspresi "keras" mewakili urutan evaluasi. Alih-alih mencoba membuat formulir yang ditampilkan secara langsung, buat saja daftar operator dalam urutan acak, dan turunkan bentuk tampilan dari ekspresi itu.
Angka:
1 3 3 9 7 2
Operator:
+ * / + *
Hasil:
((1 + 3) * 3 / 9 + 7) * 2
Turunkan bentuk tampilan adalah algoritma rekursif yang relatif sederhana.
Pembaruan: di sini adalah algoritma di Perl untuk menghasilkan formulir tampilan. Karena
+
dan*
bersifat distributif, itu mengacak urutan ketentuan untuk operator tersebut. Itu membantu menjaga tanda kurung dari semua membangun di satu sisi.sumber
(A + B) * (C + D)
tidak pernah disajikan dalam ekspresi yang dihasilkan, dan ada juga banyak parenting bersarang.Untuk memperluas pendekatan pohon, katakanlah setiap node adalah daun atau ekspresi biner:
Perhatikan bahwa daun hanyalah bilangan bulat yang dihasilkan secara acak di sini.
Sekarang, kita dapat menghasilkan pohon secara acak. Menentukan probabilitas setiap node menjadi daun memungkinkan kami untuk mengontrol kedalaman yang diharapkan, meskipun Anda mungkin menginginkan kedalaman maks absolut juga:
Kemudian, aturan paling sederhana untuk mencetak pohon adalah membungkus
()
setiap ekspresi non-daun dan menghindari kekhawatiran tentang prioritas operator.Misalnya, jika saya mengurung ekspresi sampel terakhir Anda:
Anda dapat membaca dari pohon yang akan menghasilkannya:
sumber
Saya akan menggunakan pohon. Mereka dapat memberi Anda kendali besar pada generasi ekspresi. Misalnya, Anda dapat membatasi kedalaman per cabang dan lebar setiap tingkat secara terpisah. Generasi berbasis pohon juga sudah memberikan jawaban selama generasi, yang berguna jika Anda ingin memastikan bahwa juga hasilnya (dan sub-hasil) cukup sulit dan / atau tidak terlalu sulit untuk dipecahkan. Terutama jika Anda menambahkan operator divisi di beberapa titik, Anda dapat menghasilkan ekspresi yang mengevaluasi ke bilangan bulat.
sumber
Inilah pendapat yang sedikit berbeda dari jawaban Blubb yang sangat baik:
Apa yang Anda coba bangun di sini pada dasarnya adalah parser yang beroperasi secara terbalik. Kesamaan yang dimiliki masalah dan pengurai adalah tata bahasa bebas konteks , yang ini dalam bentuk Backus-Naur :
Parser mulai dengan aliran terminal (token literal suka
5
atau*
) dan mencoba untuk merakitnya menjadi nonterminals (hal-hal yang terdiri dari terminal dan nonterminals lainnya, sepertinumber
atauop
). Masalah Anda dimulai dengan nonterminals dan bekerja secara terbalik, memilih apa pun di antara simbol "atau" (pipa) secara acak ketika seseorang ditemui dan mengulangi proses secara berulang sampai mencapai terminal.Beberapa jawaban lain menyatakan bahwa ini adalah masalah pohon, yaitu untuk kelas kasus tertentu yang sempit dimana tidak ada nonterminal yang merujuk diri secara langsung atau tidak langsung melalui nonterminal lain. Karena tata bahasa mengizinkannya, masalah ini benar-benar grafik terarah . (Referensi tidak langsung melalui nonterminals lain juga diperhitungkan.)
Ada sebuah program bernama Spew yang diterbitkan di Usenet pada akhir 1980-an yang pada awalnya dirancang untuk menghasilkan tajuk berita tabloid acak dan juga merupakan sarana yang hebat untuk bereksperimen dengan "tata bahasa terbalik" ini. Ini beroperasi dengan membaca templat yang mengarahkan produksi aliran acak terminal. Di luar nilai hiburannya (tajuk utama, lagu country, omong kosong bahasa Inggris yang dapat diucapkan), saya telah menulis banyak templat yang berguna untuk menghasilkan data uji yang berkisar dari teks biasa ke XML hingga C. secara sintaksis benar-benar-tetapi-tidak dapat dikompilasi. Meskipun berusia 26 tahun dan ditulis dalam K&R C dan memiliki format template yang jelek, itu mengkompilasi dengan baik dan berfungsi seperti yang diiklankan. Saya membuat templat yang memecahkan masalah Anda dan mempostingnya di pastebin karena menambahkan banyak teks di sini sepertinya tidak tepat.
sumber