Contoh php rekursi. Fungsi rekursif dalam bahasa PHP. Panggilan fungsi rekursif

Hari ini saya akan memberitahu anda bagaimana untuk MySQL mencipta pokok hierarki.

Pokok sedemikian digunakan apabila membina kategori tapak dinamik, contohnya, dalam kedai dalam talian atau apabila memaparkan ulasan pada siaran.

Secara umum, ia dibina di mana mungkin. Perkara utama ialah membina dan menerapkannya dengan betul.

Perkara yang paling penting apabila membina pokok hierarki ialah struktur pangkalan data yang betul! Sebagai contoh, pertimbangkan struktur pangkalan data tempat kategori tapak disimpan. Untuk contoh mudah, jadual akan mempunyai 3 medan:

  1. ID- kunci kategori
  2. parent_id— id kategori induk
  3. nama– nama bahagian

Mari buat jadual dengan melaksanakan pertanyaan SQL dalam PHPMyAdmin:

CREATE TABLE `categories` (`id` INT NOT NULL AUTO_INCREMENT , `parent_id` INT NOT NULL , `name` VARCHAR(50) NOT NULL , PRIMARY KEY (`id`));

Sekarang kita perlu mengisi jadual kita dengan rekod. Akibatnya, anda sepatutnya mendapat jadual seperti ini:

Anda boleh mengisi jadual ujian dengan pertanyaan berikut:

INSERT INTO `categories` (`id`, `parent_id`, `name`) NILAI (1, 0, "Bahagian 1"), (2, 0, "Bahagian 2"), (3, 0, "Bahagian 3" ), (4, 1, "Seksyen 1.1"), (5, 1, "Seksyen 1.2"), (6, 4, "Seksyen 1.1.1"), (7, 2, "Seksyen 2.1"), (8 , 2, "Seksyen 2.2"), (9, 3, "Seksyen 3.1");

Dan sekarang perhatian! Seterusnya, secara logiknya, anda perlu membuat pilihan daripada pangkalan data dalam satu gelung untuk memilih setiap kategori dan subkategorinya. TAPI! Tidak mengapa jika terdapat beberapa kategori dalam pangkalan data, yang juga tidak betul pada dasarnya. Bagaimana jika tapak itu ialah kedai dalam talian dan ia mempunyai seratus kategori dan bilangan subkategori yang sama? Kemudian ada masalah! Bilangan pertanyaan yang tidak diketahui ke pangkalan data akan membawa kepada kelembapan tapak atau ranap lengkap pelayan mysql.

Anda boleh menggunakan hanya satu pertanyaan pangkalan data untuk memilih semua kategori dan subkategorinya.

Mari buat permintaan dan buat tatasusunan yang mudah untuk kerja selanjutnya.

//Pilih data daripada pangkalan data $result=mysql_query("SELECT * FROM categories"); //Jika terdapat rekod dalam pangkalan data, kita membentuk tatasusunan if (mysql_num_rows($result) > 0)( $cats = array(); //Dalam gelung kita membentuk tatasusunan bahagian, kuncinya ialah id daripada kategori induk, serta tatasusunan bahagian, kuncinya ialah id kategori manakala($cat = mysql_fetch_assoc($result))( $cats_ID[$cat["id"]] = $cat; $cats[$ kucing["id_ibu bapa"]][$cat["id"]] = $cat;

Memilih semua data daripada jadual kategori dan membentuk tatasusunan bersekutu $kucing, kuncinya ialah id bagi kategori induk.

Sekarang kita akan membina pokok. Untuk membina kita akan gunakan fungsi rekursif.

Pokok hierarki akan mempunyai struktur berikut:

  • Seksyen 1
    • Bahagian 1.1
      • Bahagian 1.1.1
    • Bahagian 1.2
  • Bahagian 2
    • Bahagian 1.1
    • Bahagian 1.2
  • Bahagian 3
    • Bahagian 3.1

Mari buat fungsi rekursif build_tree() . Ia akan membina pokok hierarki kita sama sekali dari sebarang sarang.

Fungsi build_tree($cats,$parent_id,$only_parent = false)( if(is_array($cats) and isset($cats[$parent_id]))( $tree = "

    "; if($only_parent==false)( foreach($cats[$parent_id] as $cat)( $tree .= ""; ) )elseif(is_numeric($only_parent))( $cat = $cats[$parent_id ][$only_parent]; $tree .="
  • ".$cat["nama"]." #".$cat["id"]; $tree .= build_tree($cats,$cat["id"]); $tree .= "
  • "; ) $pokok .="
"; ) else return null; return $tree; )

Fungsi ini mengambil pelbagai bahagian dan id bahagian. Dalam gelung kita melalui subkategori dan jika mereka mempunyai lebih banyak bahagian, maka fungsi itu dilancarkan semula dengan parameter baharu (tatasusunan bahagian baharu dan id bahagian yang perlu dibina). Ini adalah bagaimana pokok apa-apa sarang terbentuk!

Untuk membina pokok, kami menulis dalam kod:

Echo build_tree($cats,0);

Jadi, dalam dua langkah kami mencipta pepohon hierarki bahagian tapak web dan tidak kira berapa banyak bahagian yang ada!

UPD Jika anda memerlukan pepohon kategori dalam susunan terbalik mengetahui id kategori, maka anda perlu menggunakan fungsi:

Fungsi find_parent ($tmp, $cur_id)( if($tmp[$cur_id]["parent_id"]!=0)( return find_parent($tmp,$tmp[$cur_id]["parent_id"]); ) return ( int)$tmp[$cur_id]["id"];

Fungsi ini mengambil tatasusunan kategori, kuncinya ialah id kategori dan id kategori yang anda perlukan untuk naik.

Untuk membina pokok sedemikian, jalankan fungsi build_tree dengan parameter berikut:

Echo build_tree($cats,0,find_parent($cats_ID,YOUR_CATEGORY_ID));

Ada soalan? Tanya dalam komen

Setuju bahawa selalunya apabila anda menulis tapak web anda memerlukan blok kod tertentu untuk diulang beberapa kali (lebih daripada sekali :-))

Sehubungan itu, peraturan pengaturcaraan memberitahu kami: "Jika sesetengah kod, tidak kira apa - satu baris atau banyak, diulang lebih daripada sekali, ANDA MESTI MENULIS FUNGSI DI BAWAHNYA!"

Itu. Fungsi ialah "sekeping" kod program yang akan dilaksanakan apabila ia "diminta". Dalam erti kata lain, kami memanggil kod tertentu berulang kali apabila kami memerlukannya.

Contoh fungsi ialah, sebagai pilihan, beberapa jenis penyunting grafik, contohnya Corel. Di sana, program itu sendiri mempunyai berat yang sangat kecil, dan kod program tidak melebihi beberapa halaman (mungkin :-)). Segala-galanya di dalamnya dan hakikat bahawa ia boleh bertaburan di seluruh fungsi - mereka menekan satu butang, dipanggil fungsi, ia berfungsi, selesai. Menekan butang lain - fungsi lain telah dilakukan, dsb.

Jadi, bagaimanakah fungsi diterangkan dalam php?

Di mana ujian ialah nama fungsi

Contoh paling mudah:

Sememangnya, kita perlu memanggil TEST() ini entah bagaimana. Ini dilakukan dengan sangat mudah:

Ambil perhatian bahawa ujian() kami tidak boleh diisytiharkan dua kali. Itu. kod berikut akan mengakibatkan ralat

Terdapat beberapa ribu fungsi terbina dalam dalam php dan ia secara semula jadi telah diisytiharkan, yang bermaksud bahawa jika kita tiba-tiba ingin mengisytiharkan kita sendiri, sebagai contoh
GetType(), maka kod tersebut akan menghasilkan ralat.

Jadi, sebelum mencipta fungsi kita sendiri, kita perlu menyemak sama ada nama sedemikian sudah wujud?

Ini dilakukan menggunakan fungsi terbina dalam seperti ini:

Itu. jika ujian () wujud - mengembalikan benar, jika tidak - palsu

Parameter fungsi.

Seperti yang anda lihat, selepas nama fungsi terdapat kurungan, jadi ia diperlukan untuk sesuatu :-)

Kita boleh menghantar parameter tertentu kepada fungsi kita. Ini adalah pembolehubah mudah, contohnya kita lulus pembolehubah $name:

Oleh kerana kita mempunyai parameter, kita mesti menghantarnya ke fungsi. Ini dilakukan dalam beberapa cara:

Mungkin ada lebih banyak hujah. Mereka dipisahkan dengan koma

Kita mendapatkan:

Terdapat satu lagi cara untuk memanggil fungsi, yang digunakan sangat jarang, TETAPI anda perlu tahu mengenainya.

Sebagai contoh:

Apa yang kita lihat di sini? Apabila php melihat pembolehubah dan serta-merta kurungan pembukaan, ia serta-merta memahami bahawa ia diperlukan untuk memanggil fungsi yang namanya terletak dalam pembolehubah ini. Ia jelas?

Terdapat juga parameter yang diperlukan dan pilihan. Parameter yang diperlukan diluluskan terlebih dahulu!

Ingat jadual pendaraban kita?

Mari kita tulis fungsi untuknya, memodenkan sedikit jadual supaya ia boleh diserlahkan dalam warna dan tebal:

Pada baris 18 kami memanggil fungsi dengan parameter lalai, dan kemudian memanggilnya dengan parameter yang berbeza. Mari lihat apa yang berlaku:

Itu semua keajaiban! :-)

Mengembalikan nilai daripada fungsi.

Kadang-kadang kita memerlukan fungsi untuk tidak melaksanakan sesuatu, mengeluarkan sesuatu, dll. Dan ia mengembalikan beberapa nilai (nombor), contohnya, seperti fungsi terbina dalam strlen() yang mengembalikan panjang rentetan. Mari lihat

Sila ambil perhatian bahawa pulangan bukan sahaja mengembalikan nilai fungsi, tetapi juga menamatkan pelaksanaannya - sama seperti pecah dalam gelung. Itu. segala-galanya di bawah kembali dalam fungsi tidak akan selesai!

Panggilan fungsi rekursif.

Prinsip operasi ialah fungsi memanggil dirinya sendiri, i.e. rekursi. Mari lihat contoh fungsi yang mengira faktorial (siapa terlupa - sebagai contoh, faktorial lima ialah 1*2*3*4*5 dan mengingati bahawa faktorial sifar ialah 1)

Saya harap semuanya jelas di sini.

  • Algoritma
  • Penulisan artikel ini didorong oleh pemikiran dan percubaan berjam-jam dalam bidang membina senarai hierarki. Pada mulanya, logik telah diuji pada pertanyaan SQL, tetapi kemudian saya memutuskan untuk melaksanakannya dalam PHP untuk menghapuskan pergantungan pada DBMS. Menggunakan contoh mudah, saya akan menunjukkan bagaimana anda boleh pergi dari akar hierarki ke setiap elemen akhir dan belakang, maklumatnya lebih kepada pemula.

    Jadi, hierarki ujian yang perlu kita kerjakan:

    Pangkalan data mempunyai jadual paling ringkas pada pelayan MSSQL yang paling mudah, kami akan melangkau kehalusan sambungan, matlamat kami adalah untuk memahami hierarki dan rekursi.

    Mari buat jadual:

    BUAT JADUAL .( IDENTITI(1,1) BUKAN NULL, -- medan unik, auto-increment NULL, -- medan ini menghala ke elemen pada tahap yang lebih tinggi, mengandungi uid induk (255) NULL, (50) NULL, -- hak akses) HIDUP
    Jom isi maklumat:

    Perihalan medan ada dalam ulasan, sedikit lagi tentang medan akses:

    Secara lalai, dalam sistem saya, untuk setiap dokumen baharu, mewarisi, iaitu warisan daripada ibu bapa. Untuk percubaan kami, kami akan menulis kumpulan domain untuk beberapa elemen. Dalam kumpulan Pengguna Domain akaun saya tersedia, tetapi dalam Rahsia Kumpulan AD Saya tiada di sini.

    Apa lagi yang kita ada. Tatasusunan yang mengandungi senarai kumpulan domain saya. Ia diperolehi dengan mudah, pengesahan Windows didayakan pada IIS, semuanya berfungsi dengan telus, dalam PHP log masuk pengguna berada dalam pembolehubah $_SERVER[“AUTH_USER”], kemudian menggunakan permintaan LDAP kami mendapat senarai kumpulan.

    Sekarang saya bercadang untuk mendapatkan data yang diperlukan dan terus ke intinya:

    $stmt = $PDO->query("PILIH * DARI Ujian"); $table = $stmt->fetchAll(); //Dapatkan jadual daripada pangkalan data $groups = LDAP::getGroups("$login"); //Dapatkan kumpulan ActiveDirectory

    Tugasan No 1

    Anda perlu belajar untuk bekerja dengan hierarki sebagai pokok dan bukan senarai. Tahap bersarang tidak diketahui terlebih dahulu dan boleh menjadi apa-apa, oleh itu mesti ada alat universal yang membolehkan anda melintasi pokok kedua-dua dari atas ke bawah dan ke arah yang bertentangan.

    Tugasan No. 2

    Ia adalah perlu untuk mengurus akses secara fleksibel, iaitu, untuk memberikan hak kepada kumpulan, dokumen individu, dsb., dengan analogi dengan sistem fail NTFS, anda boleh menutup hak ke seluruh folder, tetapi untuk satu dokumen dalam folder ini anda boleh memotong akses - perkara yang sama sepatutnya berlaku yang kita ada.

    Tugasan No. 3

    Ia adalah perlu untuk menyembunyikan daripada sumber pengguna yang mereka tidak mempunyai akses, tetapi yang paling penting, jika anda mempunyai hak untuk sekurang-kurangnya satu dokumen di suatu tempat di kedalaman cawangan yang tertutup kepadanya, nyatakan elemen yang membawa kepada dokumen ini (jika tidak, bagaimanakah pengguna akan mendapatkannya?)

    Berikut ialah fungsi asas sebenar:

    $array = array(); //fungsi tatasusunan keluaran rekursif($data, $pid = 0, $level = 0)( $array global; foreach ($data sebagai $baris) ( //lelarkan baris jika ($baris["pid"] == $ pid) ( // Mulakan dengan baris yang pidnya dihantar ke fungsi, bagi kami ia adalah 0, iaitu punca tapak // Kumpulkan baris ke dalam tatasusunan bersekutu $_row["uid"] = $row[ "uid"]; $ _row["pid"] = $row["pid"]; $_row["name"] = $_row["name"] = str_pad("", $level*3, "." ).$row[" name"]; //Gunakan fungsi str_pad untuk menambah mata $_row["level"] = $level; //Tambah tahap $array = $_row; //Barisan telah diproses, sekarang mari kita jalankan fungsi yang sama untuk uid semasa, iaitu // baris anak akan diterbalikkan (yang mana uid ini ialah pid) rekursif($data, $row["uid"], $level + 1) ) ) rekursif($table); //Lancarkan
    Penerangan kebanyakannya diberikan dalam ulasan, tetapi secara ringkas - selepas gelung foreach melalui baris dan melakukan sesuatu dengan data (dalam kes kami, ia hanya menyalin data ke tatasusunan lain, menambah medan tahap dan titik ke nama), ia menjalankan fungsi yang sama, menghantarnya uid rentetan, dan kerana dalam keadaan if kita membandingkannya dengan pid, maka larian seterusnya pasti akan merebut elemen anak. Gelung foreach berulang melalui semua baris yang uid induknya sepadan dengan nilai yang diluluskan, jadi dengan memulakan semula dirinya, fungsi akan berfungsi pada setiap elemen setiap peringkat. Untuk kejelasan, kami juga melepasi tahap dengan meningkatkannya satu. Hasilnya, kita akan melihat dokumen mana yang mempunyai tahap sarang yang mana.

    Mengeluarkan tatasusunan $array ke penyemak imbas:

    Tak teruk lagi kan?

    Sekarang mari kita rumitkan fungsi kita sedikit:

    $array = array(); //array keluaran $array_idx_lvl = array(); //indeks mengikut fungsi tahap medan rekursif($data, $pid = 0, $level = 0, $path = "", $access_parent = "inherit")( global $array; global $array_idx_lvl; //Indeks mengikut tahap global $groups; //kumpulan domain //lelang baris di hadapan ($data sebagai $row) ( //Mulakan dengan baris yang pidnya dihantar ke fungsi, bagi kami ini adalah 0, iaitu punca tapak jika ($ row["pid "] == $pid) ( //Kumpulkan rentetan ke dalam tatasusunan bersekutu $_row["uid"] = $row["uid"]; $_row["pid"] = $row["pid "]; $_row[ "nama"] = str_pad("", $level*3, ".").$row["name"]; $_row["level"] = $level; //Tambah tahap $ _row["path"] = $path."/".$row["name"]; //Tambahkan nama pada laluan $_row["view"] = ""; //Resolve accesses if($row[ "akses"] == "warisi ") ($_row["akses"] = $access_parent; //Jika ada warisan, lakukan seperti induk) else ($_row["akses"] = (in_array($row ["akses"], $kumpulan)) ? "benarkan" : "menafikan"; ) $array[$row["uid"]] = $_row; buat indeks $array_idx_lvl[$level][$ row["uid"]] = $row["uid"]; //Baris telah diproses, sekarang mari kita jalankan fungsi yang sama untuk uid semasa, iaitu, //baris anak (yang mana uid ini ialah pid) akan diterbalikkan) rekursif($data, $row["uid "], $level + 1, $_row["path"], $_row["access"]); ) ) ) rekursif($table); //Lancarkan
    Mari kita lihat mengikut urutan:

    1. Menambah medan laluan- untuk membentuk laluan, kami menambah "/" dan nama rentetan kepada nilai, kemudian kami menghantar nilai yang terhasil kepada fungsi, di mana cerita diulang dan output adalah laluan dari akar ke elemen.

    2. Tatasusunan yang terhasil kini dibentuk bukan mengikut tertib, bermula dari sifar, tetapi dengan merujuk kepada uid - $array[$row["uid"]] = $_row;. Dalam kes ini, ini tidak dalam apa-apa cara menjejaskan operasi skrip, tetapi kami memerlukan keupayaan untuk mengakses baris mengikut indeks, dan bukan dengan kekerasan dalam gelung, kemudian, apabila kami menganalisis laluan melalui pokok dalam arah yang bertentangan.

    3. Ditambah indeks $array_idx_lvl = array();. Kami juga akan memerlukan indeks ini kemudian, maksudnya ialah ini - set hasil tidak ditambah kepada satu timbunan, tetapi dipecahkan kepada tatasusunan yang diindeks mengikut tahap.

    4. Padang Akses. Apabila fungsi berjalan sendiri, bersama-sama dengan parameter lain ia melepasi tetapan kebenarannya $_row["akses"] anak perempuan, dan kemudian perkara berikut berlaku: hak diperiksa - jika warisan ditetapkan, maka hak ibu bapa digunakan, jika tidak, melalui in_array Kami menyemak sama ada kumpulan domain yang dinyatakan dalam akses adalah antara kumpulan pengguna yang log masuk. Jika ada, tambahkan benarkan pada baris, jika tidak, tolak.

    Keputusan akhir:

    Baiklah, kami telah menyelesaikan keturunan, kini yang tinggal hanyalah untuk menangani pendakian dan mengisi medan terakhir pandangan, yang menentukan keterlihatan unsur. Pada permulaan artikel, saya berkata mengapa ini diperlukan, tetapi kita boleh menganggap situasi yang berbeza. Katakan anda memutuskan untuk memautkan senarai pokok ke menu navigasi tapak, dibuat dalam bentuk senarai lungsur turun berbilang peringkat dengan sekumpulan item dan anda tidak mahu pengguna yang mempunyai akses kepada hanya satu dokumen untuk bergerak melalui keseluruhan tatasusunan ini dan mencari itemnya dalam menu yang banyak, kerana sebenarnya, dia perlu menunjukkan hanya satu cawangan yang menuju ke butang yang dikehendaki.

    Mengapakah terdapat keperluan untuk laluan terbalik di sini? Katakan pengguna mempunyai akses yang dinafikan kepada semua kandungan kecuali satu, dokumen yang paling jauh (pada peringkat terakhir), jika anda memikirkannya, adalah logik untuk bermula dari apa yang tersedia dan membawanya ke akar pokok, menunjukkan hanya elemen yang diperlukan.

    Mari kita mulakan:

    //Fungsi naik ke atas pokok satu tahap daripada uid yang diberikan, menetapkan //sifat keterlihatan untuk dirinya sendiri dan induk bergantung pada akses atau keterlihatan yang ditetapkan sebelumnya... function backRecursive($uid, $view = null, $ ident = 0) ( global $array; //Jika anda naik tidak lebih daripada satu tahap if($ident<= 1) { //Если видимость уже есть - не меняем текущую строку, иначе //проверяем доступ и то что пришло от дочки if($array[$uid]["view"] != "show") { $array[$uid]["view"] = ($array[$uid]["access"] == "allow" or $view == "show") ? "show" : "hide"; } backRecursive($array[$uid]["pid"], $array[$uid]["view"], $ident+1); } }
    Apa yang dilakukan oleh fungsi ini ialah ia mengambil sebagai parameter uid talian yang perlu diambil tindakan, mengakses baris itu dan menyemak keterlihatan. Jika medan paparan tidak ditunjukkan (iaitu tunjukkan), tetapi sesuatu yang lain, ia menyemak perkara yang selamat, dan jika terdapat benarkan(akses dibuka), menjadikan elemen kelihatan, sebaliknya tersembunyi ( bersembunyi), kemudian melancarkan dirinya, melepasinya pid dan tetapan keterlihatan, serta pembolehubah $ident meningkat sebanyak 1, dengan itu menyekat permulaan kendiri berikutnya. Pada pas kedua, menurut yang dihantar pid elemen induk terletak, pemeriksaan yang sama dilakukan, kecuali satu perkara, jika dari anak dalam pembolehubah $pandangan dipindahkan" tunjuk", maka tidak kira apa, elemen semasa juga akan diberikan tunjuk, iaitu kelihatan.

    Pada pendapat saya, bekerja dengan penghad adalah pilihan terbaik, kerana bayangkan keadaan, pada tahap 10 kita mempunyai 100 dokumen, untuk melintasi keseluruhan pokok, kita perlu menjalankan fungsi ini pada setiap elemen, kerana jika pada tahap terakhir kita menjalankan fungsi 100 kali, kemudian melakukan permulaan kendiri, carian 100 kali akan mencapai akar. Jika anda mendarab dengan 10 tahap, anda sudah mendapat 1000 kitaran, yang tidak baik, jadi kenaikan mesti dilakukan secara sama rata, tahap demi tahap.

    Kod berikut menjalankan fungsi ini:

    Fungsi startBack())( global $array_idx_lvl; $levels = array_keys($array_idx_lvl); //dapatkan tatasusunan tahap $maxLevel = max($levels); //Cari tahap terdalam pokok //Gelung melalui setiap tahap bermula dengan yang terbesar untuk ($i = $maxLevel; $i > 0; $i--) ($uids = array_keys($array_idx_lvl[$i]); //Pada tahap semasa kita melalui semua elemen dan untuk setiap kita memulakan pemprosesan dan pergerakan pada 1 tahap foreach ($uids as $uid) ( backRecursive($uid); ) ) )
    Di sinilah indeks tahap diperlukan. Di sini kita bergerak dari tahap yang paling jauh, memasuki setiap satu, memproses setiap elemen di dalamnya.

    Dan inilah gambarnya:

    Sebelum melancarkan, saya sengaja menentukan kumpulan kebenaran untuk item "Laporan Cukai" untuk menunjukkan dengan jelas bahawa kod itu berfungsi dengan betul. Walaupun akses kepada bahagian "Laporan Perakaunan" ditutup, ia boleh dilihat.

    Itu sahaja, saya fikir kami telah menyelesaikan tugas, asas telah diperoleh, algoritma berfungsi, dan boleh digunakan dalam sistem sebenar.