php rekursif Rekursi di PHP

Pelajaran ini adalah tentang rekursi dalam PHP. Anda dapat memanggil fungsi lain dari suatu fungsi. Program dapat memanggil fungsi f1(), yang memanggil fungsi f2(), dan seterusnya. Sebuah fungsi yang memanggil dirinya sendiri disebut rekursif. Jenis rekursi ini disebut rekursi eksplisit. Jika fungsi f1() memanggil fungsi lain f2(), yang pada gilirannya memanggil fungsi f1(), maka fungsi ini juga rekursif. Jenis rekursi ini disebut rekursi implisit. Jelas, bentuk rekursi implisit yang lebih kompleks dimungkinkan.

Mari kita asumsikan bahwa untuk solusi beberapa tugas perlu membuat fungsi rekursif. Dalam pelajaran ini, kami akan menjelaskan salah satu strategi terkenal untuk memecahkan masalah menggunakan fungsi rekursif. Proses pemecahan masalah rekursif dibagi menjadi beberapa tahap. Pada langkah pertama, fungsi rekursif dipanggil untuk menyelesaikan masalah. Dalam fungsi ini, masalah diselesaikan untuk situasi yang paling sederhana. Situasi paling sederhana dari masalah ini disebut masalah dasar. Jika fungsi tersebut digunakan untuk menyelesaikan masalah dasar, maka ia mengembalikan solusi atau hasil.

Jika fungsi dipanggil untuk memecahkan masalah yang lebih kompleks daripada masalah dasar, maka fungsi membagi masalah menjadi dua bagian:

  • bagian 1 yang dapat diselesaikan oleh fungsi tersebut;
  • bagian 2 yang tidak dapat diselesaikan oleh fungsi tersebut.

Untuk menggunakan rekursi, bagian 2 harus serupa dengan masalah awal, tetapi relatif lebih kecil atau lebih sederhana.

Karena tugas yang dibuat di Bagian 2 mirip dengan tugas awal, fungsi memanggil instance baru dari dirinya sendiri untuk menangani tugas baru. Tindakan ini disebut panggilan rekursif atau langkah rekursi.

Karena pada setiap langkah rekursi (dengan setiap panggilan rekursif) tugas dibagi menjadi dua bagian, oleh karena itu, jumlah langkah rekursi ini bisa sangat besar.

Untuk menyelesaikan rekursi, fungsi rekursif harus membentuk urutan penyederhanaan (penurunan) masalah yang mendekati masalah dasar. Ketika, pada beberapa langkah rekursi, fungsi menemukan masalah dasar, ia mengembalikan solusi (hasil) dari masalah dasar ke panggilan sebelumnya (langkah rekursi sebelumnya). Dalam panggilan ini, pada gilirannya, hasilnya digabungkan dengan bagian yang dapat dipecahkan oleh fungsi tersebut. Solusi yang terbentuk dengan cara ini kembali ke langkah di atas, dan seterusnya. Ini menghasilkan hasil terakhir, yang dikembalikan ke titik awal pemanggilan fungsi rekursif. Artinya, agar dapat kembali, setiap langkah fungsi harus dibangun sedemikian rupa sehingga mengandung kata kembali yang dicadangkan.

Untuk mendemonstrasikan ide di atas, kami akan memberikan contoh penggunaan fungsi rekursif.

Contoh 1. Faktorial. Faktorial dari bilangan bulat non-negatif n sama dengan n*(n-1)*(n-2)*...2*1 dan dilambangkan dengan n!. Diasumsikan bahwa 1!=1 dan 0!=1. Misalnya, 7!=7*6*5*4*3*2*1=5040.

Solusi iteratif (non-rekursif) untuk perhitungan faktorial adalah sebagai berikut (file factorial1.php):



Faktorial



Perhitungan faktorial



Adadi naturali (n>=0):





";
keluar;
}
$f=1;
untuk($i=$n;$i>=1;$i--)
$f*=$i;
echo "$n!=$f";
?>

Misal f(n)=n!, maka
f(n)=n!=n*(n-1)!=n*f(n-1),
yaitu, rumus rekursif untuk menghitung faktorial adalah:
f(n)=n*f(n-1).

Contoh:
f(5)=5*f(4)=5*4*f(3)=5*4*3*f(2)= 5*4*3*2*f(1)= 5*4*3 *2*1=120.

Berdasarkan rumus rekursif, kita akan membuat fungsi rekursif untuk menghitung faktorial (file factorial2.php):



Faktorial



Perhitungan faktorial



Adadi naturali (n>=0):




if(!isset($_GET["n"]) || ($n = $_GET["n"])=="") (
echo "Masukkan bilangan asli!
";
keluar;
}
$f=faktorial($n);
echo "$n!=$f";

fungsi faktorial($i) (
jika($i<2) return 1;
$k=$i*faktorial($i-1);
kembali $k;
}

?>

Jika Anda memasukkan angka 5 ke dalam program, maka untuk menghitung 5! program bekerja sebagai berikut:

Ketika fungsi faktorial() dipanggil untuk pertama kalinya, akan diperiksa apakah angka yang dikirim ke fungsi tersebut kurang dari 2. Jika angka yang diterima oleh fungsi tersebut tidak kurang dari 2, maka tugasnya lebih besar atau lebih rumit dari tugas dasar, oleh karena itu, tugas dibagi menjadi dua bagian:

  1. $k=$i* - bagian 1 yang dapat dipecahkan oleh fungsi;
  2. faktorial($n-1) adalah bagian 2 yang tidak dapat diselesaikan oleh fungsi tersebut.

Tindakan ini diulang sampai masalah dasar diterima, yaitu sampai faktorial (1) dipanggil. Jika angkanya kurang dari 2 (1 atau 0), maka fungsi faktorial() mengembalikan angka 1 ($k=1), yaitu, masalah dasar terpecahkan. Jika instance fungsi factorial() ini sebelumnya dipanggil oleh instance lain dari fungsi factorial(), maka hasilnya dikembalikan ke instance pemanggilan fungsi tersebut. Di sana, hasil yang dikembalikan $k dikalikan dengan parameter $n yang diteruskan ke fungsi dan ditetapkan ke $k. Jika instance dari fungsi factorial() ini sebelumnya dipanggil oleh instance lain dari fungsi factorial(), maka hasilnya dikembalikan ke instance pemanggilan fungsi tersebut dan proses yang dijelaskan akan diulang. Jika instance fungsi faktorial() ini dipanggil dari bagian utama program, maka $k dikembalikan ke bagian itu, di mana hasilnya dicetak ke layar dan program dihentikan.

  • algoritma
  • Berjam-jam berpikir dan bereksperimen di bidang membangun daftar hierarkis mendorong saya untuk menulis artikel ini. Awalnya, logika diuji pada kueri SQL, tetapi kemudian saya memutuskan untuk mengimplementasikannya dalam PHP untuk menghilangkan ketergantungan pada DBMS. Menggunakan contoh sederhana, saya akan menunjukkan bagaimana Anda bisa pergi dari akar hierarki ke setiap elemen akhir dan kembali, informasinya lebih untuk pemula.

    Jadi, hierarki pengujian yang harus kita kerjakan:

    Basis data memiliki tabel paling sederhana di server MSSQL paling sederhana, kami akan menghilangkan seluk-beluk koneksi, tujuan kami adalah menangani hierarki dan rekursi.

    Mari kita buat tabel:

    CREATE TABLE .( IDENTITY(1,1) NOT NULL, -- unique field, auto-incrementing NULL, -- field ini menunjuk ke elemen satu tingkat ke atas, berisi parent's uid (255) NULL, (50) NULL, - - izin) AKTIF
    Yuk isi informasinya:

    Deskripsi bidang ada di komentar, sedikit lebih banyak tentang bidang mengakses:

    Secara default di sistem saya, untuk setiap dokumen baru, mewarisi, yaitu warisan dari orang tua. Untuk percobaan kami, kami akan menulis grup domain untuk beberapa elemen. Di Grup Pengguna Domain akun saya ada, tapi di Rahasia Grup AD Aku pergi.

    Apa lagi yang kita miliki. Array yang berisi daftar grup domain saya. Ini diperoleh dengan cukup sederhana, otentikasi Windows diaktifkan di IIS, semuanya bekerja secara transparan, di PHP login pengguna ada di variabel $_SERVER["AUTH_USER"], lalu kami mendapatkan daftar grup dengan permintaan LDAP.

    Sekarang saya mengusulkan untuk mendapatkan data yang diperlukan dan langsung ke intinya:

    $stmt = $PDO->query("PILIH * DARI Tes"); $tabel = $stmt->fetchAll(); //Dapatkan tabel dari database $groups = LDAP::getGroups("$login"); //Dapatkan grup ActiveDirectory

    Tugas 1

    Penting untuk mempelajari cara bekerja dengan hierarki sebagai pohon dan bukan daftar. Level bersarang tidak diketahui sebelumnya dan dapat berupa apa saja, oleh karena itu harus ada alat universal yang memungkinkan Anda melintasi pohon baik dari atas ke bawah maupun ke arah yang berlawanan.

    Tugas #2

    Penting untuk mengelola akses secara fleksibel, yaitu, untuk memberikan hak kepada grup, dokumen individual, dll., Dengan analogi dengan sistem file NTFS, Anda dapat menutup hak ke seluruh folder, tetapi untuk satu dokumen di folder ini, potong akses - hal yang sama harus terjadi dan kita miliki.

    Tugas #3

    Hal ini diperlukan untuk menyembunyikan dari sumber daya pengguna yang mereka tidak memiliki akses, tetapi yang paling penting, jika Anda memiliki hak untuk setidaknya satu dokumen di suatu tempat di kedalaman cabang yang tertutup untuk itu, buat elemen yang mengarah ke dokumen ini terlihat (jika tidak bagaimana pengguna akan mendapatkannya?)

    Berikut adalah fungsi dasar itu sendiri:

    $array = array(); //output fungsi array rekursif($data, $pid = 0, $level = 0)( global $array; foreach ($data sebagai $baris) ( //loop melalui baris if ($row["pid"] == $ pid) ( //Mulai dengan baris yang pidnya diteruskan ke fungsi, kita memilikinya 0, yaitu akar situs //Kumpulkan baris ke dalam array asosiatif $_row["uid"] = $row[" uid"]; $ _row["pid"] = $row["pid"]; $_row["name"] = $_row["name"] = str_pad("", $level*3, ".") .$row[" name"]; //Menambahkan titik menggunakan fungsi str_pad $_row["level"] = $level; //Menambahkan level $array = $_row; //Menambahkan setiap baris ke array output // Baris telah diproses, sekarang jalankan fungsi yang sama untuk uid saat ini, yaitu //baris anak (di mana uid ini adalah pid) akan dibalik rekursif($data, $row["uid"], $level + 1); ) ) ) rekursif($tabel); //Meluncurkan
    Deskripsi sebagian besar diberikan dalam komentar, tetapi sederhananya - setelah loop foreach melewati garis dan melakukan sesuatu dengan data (dalam kasus kami, itu hanya menyalin data ke array lain, menambahkan bidang level dan titik ke nama), itu menjalankan fungsi yang sama dengan meneruskan uid dari string, dan karena kami membandingkannya dengan pid dalam kondisi if, proses berikutnya pasti akan mengambil elemen anak. Loop foreach mengulangi semua baris yang uid induknya cocok dengan nilai yang diteruskan, jadi dengan memulai ulang dirinya sendiri, fungsi akan berjalan pada setiap elemen di setiap level. Untuk kejelasan, kami juga melewati level dengan meningkatkannya satu per satu. Hasilnya, kita akan melihat dokumen mana yang memiliki level nesting yang mana.

    Array keluaran $array ke peramban:

    Tidak buruk lagi, kan?

    Sekarang mari kita sedikit memperumit fungsi kita:

    $array = array(); //output array $array_idx_lvl = array(); //indeks berdasarkan fungsi level bidang rekursif($data, $pid = 0, $level = 0, $path = "", $access_parent = "inherit")( global $array; global $array_idx_lvl; //Indeks menurut level global $groups; //domain groups //loot through the rows foreach ($data as $row) ( //Kita mulai dengan baris yang pidnya diteruskan ke fungsi, kita memilikinya 0, yaitu root dari situs jika ( $row["pid "] == $pid) ( //Mengumpulkan string ke dalam array asosiatif $_row["uid"] = $row["uid"]; $_row["pid"] = $row[" pid"]; $_row[ "nama"] = str_pad("", $level*3, ".").$row["name"]; $_row["level"] = $level; //Menambahkan a level $_row["path"] = $path."/".$row["name"]; //Menambahkan nama ke path $_row["view"] = ""; //Hancurkan akses if($ row["access"] == "inherit ") ( $_row["access"] = $access_parent; //Jika ada warisan, lakukan seperti induknya ) else ( $_row["access"] = (in_array( $row["access"], $groups)) ? "allow" : "deny"; ) $array[$row["uid"]] = $_row; //Array yang dihasilkan diindeks oleh uid //Untuk pengambilan cepat oleh tingkat, bentuk m indeks $array_idx_lvl[$level][$row["uid"]] = $row["uid"]; //Baris telah diproses, sekarang jalankan fungsi yang sama untuk uid saat ini, yaitu //baris anak (di mana uid ini adalah pidnya) akan dibalik recursive($data, $row["uid"], $ tingkat + 1, $_row["jalur"], $_row["akses"]); ) ) ) rekursif($tabel); //Meluncurkan
    Mari kita urutkan secara berurutan:

    1. Menambahkan bidang jalur- untuk membentuk jalur, tambahkan "/" dan nama string ke nilainya, lalu berikan nilai yang dihasilkan ke fungsi, tempat riwayat berulang dan outputnya adalah jalur dari root ke elemen.

    2. Array yang dihasilkan sekarang dibentuk tidak secara berurutan, mulai dari nol, tetapi dengan mengikat ke uid - $array[$row["uid"]] = $_row;. Dalam hal ini, ini tidak mempengaruhi operasi skrip dengan cara apa pun, tetapi kita akan memerlukan kemampuan untuk mengakses string dengan indeks, dan bukan dengan iterasi dalam satu lingkaran, ketika kita menganalisis lintasan pohon ke arah yang berlawanan.

    3. Ditambahkan indeks $array_idx_lvl = array();. Kita juga akan membutuhkan indeks ini nanti, artinya himpunan yang dihasilkan tidak ditambahkan menjadi satu heap, tetapi dibagi menjadi array yang diindeks berdasarkan level.

    4. Lapangan Mengakses. Ketika suatu fungsi berjalan sendiri, bersama dengan parameter lainnya, ia melewati pengaturan izinnya $_row["akses"] anak-anak, dan kemudian hal berikut terjadi, hak diperiksa - jika warisan ditetapkan (mewarisi), maka hak orang tua diterapkan, jika tidak - melalui in_array kami memeriksa apakah grup domain yang ditentukan dalam akses termasuk di antara grup pengguna yang masuk. Jika ada - tambahkan ke baris izinkan (izinkan), jika tidak tolak (larangan).

    Hasil akhir:

    Nah, kami menemukan keturunannya, sekarang tinggal berurusan dengan pendakian dan pengisian bidang terakhir melihat A yang menentukan visibilitas elemen. Di awal artikel, saya mengatakan mengapa ini diperlukan, tetapi kita dapat mengasumsikan situasi yang berbeda. Katakanlah Anda memutuskan untuk menautkan daftar seperti pohon ke menu navigasi situs, dibuat dalam bentuk daftar drop-down multi-level dengan banyak item, dan Anda tidak ingin pengguna yang hanya memiliki akses ke satu dokumen untuk memindahkan seluruh larik ini dan mencari itemnya di menu massal, karena sebenarnya, ia hanya perlu menampilkan satu cabang yang mengarah ke tombol yang diinginkan.

    Mengapa jalan pulang diperlukan di sini? Misalkan pengguna memiliki akses ke semua konten kecuali satu, dokumen terjauh (pada tingkat terakhir), jika Anda memikirkannya, akan logis untuk memulai dari yang tersedia dan mengarahkannya ke akar pohon, hanya menunjukkan elemen yang diperlukan.

    Mari kita mulai:

    //Fungsi untuk melintasi pohon satu tingkat dari uid yang diberikan, set //properti visibilitas ke dirinya sendiri dan induknya tergantung pada akses atau visibilitas yang disetel sebelumnya... function backRecursive($uid, $view = null, $ident = 0) ( global $array; //Jika Anda naik tidak lebih dari satu level 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 fungsi ini - mengambil sebagai parameter Indo baris untuk mulai bertindak, mengakses baris ini dan memeriksa visibilitas. Jika bidang tampilan tidak ditampilkan (yaitu tampilkan), tetapi sesuatu yang lain, ia memeriksa apa yang aman, dan jika ada mengizinkan(akses diberikan), membuat elemen terlihat, jika tidak disembunyikan ( bersembunyi), lalu meluncurkan dirinya sendiri, melewati pid dan pengaturan visibilitas, serta variabel $ident meningkat 1, sehingga menghalangi self-start berikutnya. Selama lintasan kedua, menurut yang ditransmisikan pid elemen induk ditemukan, pemeriksaan yang sama dilakukan, kecuali satu, jika dari anak dalam suatu variabel $lihat ditransfer" menunjukkan", maka tidak peduli apa, elemen saat ini juga akan ditugaskan menunjukkan, yang terlihat.

    Menurut pendapat saya, bekerja dengan pembatas adalah pilihan terbaik, karena bayangkan situasinya, pada level 10 kami memiliki 100 dokumen, untuk sepenuhnya melintasi seluruh pohon, kami perlu menjalankan fungsi ini pada setiap elemen, karena jika pada level terakhir kita menjalankan fungsi 100 kali, kemudian melakukan self-start, iterasi akan mencapai root 100 kali. Jika Anda mengalikan dengan 10 level, Anda sudah akan mendapatkan 1000 siklus, yang tidak baik, jadi kenaikannya harus dilakukan secara merata, level demi level.

    Kode berikut memicu fungsi ini:

    Fungsi startBack()( global $array_idx_lvl; $levels = array_keys($array_idx_lvl); //Dapatkan array level $maxLevel = max($levels); //Temukan level terdalam dari pohon //Loop melalui setiap level mulai dari yang terbesar untuk ($i = $maxLevel; $i > 0; $i--) ( $uids = array_keys($array_idx_lvl[$i]); //Pada level saat ini, ulangi semua elemen dan untuk setiap inisiasi pemrosesan dan perpindahan ke lvl 1 foreach ($uids as $uid) ( backRecursive($uid); ) ) )
    Di sinilah indeks berdasarkan level diperlukan. Di sini kita bergerak dari tingkat terjauh, masuk ke masing-masing, memproses setiap elemen di dalamnya.

    Dan inilah gambarnya:

    Sebelum meluncurkan, saya sengaja mengatur grup izin untuk item "Laporkan pajak" untuk menunjukkan dengan jelas bahwa kode tersebut berfungsi dengan benar. Terlepas dari kenyataan bahwa akses ke bagian "Pernyataan akuntansi" ditutup, itu terlihat.

    Itu saja, saya pikir kami mengatasi tugas, dasar diperoleh, algoritme berfungsi, dapat diterapkan dalam sistem nyata.

    Hari ini saya akan memberi tahu Anda caranya MySQL membuat pohon hierarki.

    Pohon seperti itu digunakan saat membangun kategori situs dinamis, misalnya, di toko online atau saat menampilkan komentar di postingan.

    Secara umum, mereka dibangun sedapat mungkin. Hal utama adalah membangun dan menerapkannya dengan benar.

    Hal terpenting ketika membangun hierarki adalah struktur database yang benar! Misalnya, pertimbangkan struktur database tempat kategori situs disimpan. Untuk contoh sederhana, tabel akan memiliki 3 bidang:

    1. Indo- kunci kategori
    2. identitas orang tua— id dari kategori induk
    3. nama- judul bagian

    Mari kita buat tabel dengan mengeksekusi kueri SQL di 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 tabel kita dengan record. Akibatnya, Anda harus mendapatkan sesuatu seperti tabel ini:

    Anda dapat mengisi tabel pengujian dengan kueri:

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

    Dan sekarang perhatian! Selanjutnya, secara logis, Anda perlu membuat pilihan dari database dalam satu lingkaran untuk memilih setiap kategori dan subkategorinya. TETAPI! Nah, jika ada beberapa kategori dalam database, yang pada prinsipnya juga tidak benar. Dan jika situs tersebut adalah toko online dan memiliki seratus kategori dan jumlah subkategori yang sama? Kemudian masalah! Jumlah kueri yang tidak diketahui ke database akan memperlambat situs atau benar-benar membuat server mysql crash.

    Anda hanya dapat menggunakan satu kueri ke database untuk memilih semua kategori dan subkategorinya.

    Mari buat permintaan dan bentuk array yang nyaman untuk pekerjaan lebih lanjut.

    //Memilih data dari database $result=mysql_query("PILIH * DARI kategori"); //Jika ada record dalam database, kita membentuk array if (mysql_num_rows($result) > 0)( $cats = array(); //Dalam loop, kita membentuk array bagian, kuncinya adalah id dari kategori induk, serta larik bagian, kuncinya adalah id kategori while($cat = mysql_fetch_assoc($result))( $cats_ID[$cat["id"]] = $cat; $cats[ $cat["parent_id"]][$cat["id"]] = $cat; ) )

    Memilih semua data dari tabel kategori dan membentuk array asosiatif $kucing, kuncinya akan menjadi id dari kategori induk.

    Sekarang kita akan membangun pohon. Untuk konstruksi kami akan menggunakan fungsi rekursif.

    Pohon hierarki akan memiliki struktur berikut:

    • Bagian 1
      • Bagian 1.1
        • Bagian 1.1.1
      • Bagian 1.2
    • Seksi 2
      • Bagian 1.1
      • Bagian 1.2
    • Bagian 3
      • Bagian 3.1

    Mari kita buat fungsi rekursif bangun_pohon() . Ini akan membangun pohon hierarki kita dari sarang apa pun.

    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] sebagai $cat)( $tree .= ""; ) )elseif(is_numeric($only_parent))( $cat = $cats[$parent_id ][$only_parent]; $pohon .= "
    • ".$cat["nama"]." #".$cat["id"]; $tree .= build_tree($cats,$cat["id"]); $tree .= "
    • "; ) $pohon .= "
    "; ) else return null; return $tree; )

    Fungsi ini mengambil larik bagian dan id bagian. Dalam loop, kami menelusuri subkategori dan jika mereka memiliki lebih banyak bagian, maka fungsi tersebut dijalankan lagi dengan parameter baru (array baru dari bagian dan id bagian yang akan dibangun). Beginilah cara pohon bersarang apa pun terbentuk!

    Untuk membangun pohon, kami menulis dalam kode:

    echo build_tree($kucing,0);

    Jadi, dalam dua langkah, kami membuat hierarki bagian situs dan tidak peduli berapa banyak bagiannya!

    UPD Jika Anda memerlukan pohon kategori dalam urutan terbalik untuk mengetahui id kategori, maka Anda perlu menggunakan fungsinya:

    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 larik kategori, kuncinya adalah id kategori, dan id kategori untuk naik.

    Untuk membangun pohon seperti itu, jalankan fungsi build_tree dengan parameter berikut:

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

    Ada pertanyaan? Tanya di komentar

    Sebagian besar bahasa pemrograman mendukung fungsi rekursif, yaitu fungsi yang memanggil dirinya sendiri. Ini adalah alat yang sangat kuat yang memungkinkan Anda membuat program yang cukup elegan dan fungsional, tetapi jarang digunakan - tampaknya karena kerumitannya untuk pemrogram pemula dan kemampuan untuk menyebabkan kesalahan yang tidak menyenangkan. Oleh karena itu, dalam catatan hari ini, kita akan berbicara tentang membuat dan menggunakan fungsi rekursif di PHP.

    Secara teknis, fungsi rekursif tidak berbeda dengan fungsi biasa. Satu-satunya perbedaan adalah bahwa di suatu tempat dalam kode fungsi ada panggilan untuk dirinya sendiri. Misalnya, jika Anda menulis

    Tes fungsi() (
    operator yang berbeda
    tes();
    operator yang berbeda
    }
    maka itu akan menjadi fungsi rekursif.

    Kesulitan utama ketika bekerja dengan fungsi rekursif adalah bahwa mereka cukup mudah untuk diulang - agar ini tidak terjadi, Anda harus sangat berhati-hati tentang kemungkinan keluar dari fungsi tanpa panggilan rekursif, jika tidak, fungsi akan mulai "tanpa batas" " memanggil dirinya sendiri dan dengan cepat menghabiskan sumber daya komputer.

    Salah satu kegunaan paling umum dari fungsi rekursif adalah traversal pohon, dan hari ini kita akan mencoba menguraikan beberapa contoh. Hal pertama yang terlintas dalam pikiran adalah output dari array multidimensi. Tentu saja, PHP memiliki fungsi print_r(), tetapi kami akan mencoba membuat hasilnya lebih indah dan cocok untuk dilihat melalui browser.

    Fungsi print_array($ar) (
    statis $hitung;

    $hitung = (isset($hitung)) ? ++$hitung: 0;

    $warna = array("#FFCB72", "#FFB072", "#FFE972", "#F1FF72",
    "#92FF69", "#6EF6DA", "#72D9FE");

    if ($count > count($colors)) (
    echo "Kedalaman menyelam maksimum tercapai!";
    $hitung--;
    kembali;
    }

    jika (!is_array($ar)) (

    ";
    kembali; )

    gema "

    ";
    gema " \n";
    if (is_array($v)) (
    gema " \n";
    }
    }
    gema "
    $k$v
    ";
    print_array($v);
    gema "
    ";
    $hitung--;
    }

    Fungsi ini menggunakan variabel statis $count, yang akan berisi "kedalaman penyelaman" dari fungsi - kita akan menggunakannya untuk mewarnai array bersarang dengan warna berbeda, tergantung pada kedalaman sarang. Variabel statis mempertahankan nilainya saat fungsi keluar, dan cukup nyaman untuk menggunakannya selama rekursi. Tentu saja, Anda juga dapat meneruskan variabel $count sebagai parameter, tetapi variabel statis lebih nyaman digunakan...

    Array $colors berisi daftar warna berbeda yang akan digunakan untuk pewarnaan. Pada saat yang sama, kami menggunakannya untuk membatasi tingkat rekursi maksimum - segera setelah semua warna habis (memeriksa apakah ($count >= count($colors))), fungsi akan menampilkan pesan bahwa kedalaman maksimum telah tercapai.

    Untuk berjaga-jaga (omong-omong, selalu disarankan untuk melakukan ini untuk menghindari konsekuensi yang tidak menyenangkan), kami juga memeriksa apakah array diteruskan sebagai argumen - jika ini bukan masalahnya, maka kami cukup menampilkan pesan kesalahan dan keluar fungsi:

    Jika (!is_array($ar)) (
    echo "Argumen yang dilewatkan bukan array!

    ";
    kembali;
    }

    Kami kemudian "membuka" tabel (gema "

    ";) dan mulai secara berurutan melihat larik yang diteruskan sebagai argumen.

    Sementara(daftar($k, $v) = masing-masing($ar)) (
    ...
    }

    Ini adalah salah satu cara standar untuk melangkah melalui array - di setiap lintasan loop, variabel $k dan $v diberi nilai indeks (atau, seperti juga disebut, kunci) berikut dan nilai elemen array .

    Setelah menerima pasangan nilai kunci, kami menampilkannya dalam baris tabel:

    gema"

    \n";
    Perhatikan bahwa jika nilai elemen ini adalah array, maka kata "array" akan dicetak. Sekarang kita periksa apakah nilainya adalah array:
    jika (is_array($v))
    dan jika ya, maka kami mencetak (tidak sepenuhnya!) satu baris tabel lagi, melewatkan indeks (sudah ada di baris sebelumnya):
    gema " \n";
    Ini menyelesaikan pemrosesan pasangan nilai kunci saat ini, dan loop while berpindah ke pasangan berikutnya. Dan ketika seluruh array telah dilewati, maka kita hanya perlu menutup tabel:
    gema "
    $k$v
    ";
    dan panggil fungsi cetak array kami, meneruskan array bersarang sebagai argumen:
    print_array($v);
    Kemudian (setelah fungsi yang dipanggil secara rekursif telah menyelesaikan pekerjaannya) kami "menutup" baris tabel (perhatikan bahwa karena fungsi kami mencetak "tabel lengkap" - dari sebelum
    , - kita tidak perlu menutup tabel bersarang - fungsi akan menanganinya sendiri - tetapi kita hanya perlu menutup sel dan baris tabel saat ini).
    gema "
    ";
    dan mengurangi nilai kedalaman
    $hitung--; Dari apa yang ditulis di atas, jelas bahwa fungsi kita tidak tahu apa-apa tentang apakah itu disebut secara rekursif atau tidak, dan itu tidak masalah - yang utama adalah array dilewatkan sebagai argumen. Demikian pula, ketika memanggil dirinya sendiri untuk memproses array bersarang, itu tidak peduli bahwa itu adalah panggilan rekursif - tidak ada bedanya dengan memanggil beberapa fungsi lain. Dan perhatian pemrogram adalah untuk memastikan bahwa panggilan rekursif berakhir di suatu tempat, yaitu, fungsi dapat menyelesaikan pekerjaannya tanpa memanggil dirinya sendiri lagi. Segera setelah ini terjadi, kedalaman panggilan bersarang akan mulai berkurang, dan akhirnya fungsi akan "muncul".

    Hasil dari fungsi tersebut akan terlihat seperti ini (dalam contoh, kedalaman maksimum dibatasi pada level ketiga, untuk kejelasan, output dari variabel $count ditambahkan, dan larik untuk dicetak disetel ke larik(1 , larik(1.1, 1.2, 1.3), 2, 3 , larik(3.1, 3.2, larik(3.21, 3.22, 3.23, larik(3.231, 3.232), 3.24)), 4)

    menghitungkuncinilai
    0 0 1
    0 1 Himpunan
    0
    menghitungkuncinilai
    1 0 1.1
    1 1 1.2
    1 2 1.3
    0 2 2
    0 3 3
    0 4 Himpunan
    0
    0 5 4 Mencetak array jarang diperlukan "dalam kehidupan nyata", kecuali untuk menguji skrip Anda, tetapi rekursi bisa sangat berguna. Misalnya, kebutuhan yang lebih vital adalah memproses semua file dalam sebuah direktori, termasuk subdirektori. Katakanlah lebih mudah menggunakan fungsi dd() rekursif (kependekan dari Directory Delete) untuk menghapus file: function dd($file) (
    if (file_exists($file)) (
    chmod($file,0777);
    if (is_dir($file)) (
    $pegangan = opendir($file);
    while($namafile = readdir($handle))
    if ($namafile != "." && $namafile != "..") dd($file."/".$namafile);
    tertutup($pegangan);
    rmdir($file);
    ) kalau tidak (
    batalkan tautan($file);
    }
    }
    }

    Ini bekerja dengan prinsip yang persis sama - jika sebuah file dilewatkan sebagai argumen, maka file tersebut akan dihapus (unlink($file);), dan jika sebuah direktori, itu dibuka, dilihat secara berurutan, dan fungsi yang sama dipanggil untuk masing-masing file (termasuk subdirektori) dd()...

    Contoh lain di mana sangat nyaman untuk menggunakan fungsi rekursif adalah membaca file melalui protokol HTTP dengan pelacakan pengalihan - Anda perlu membuka koneksi, meminta file (atau hanya header) dan memeriksa respons server - jika berisi header

    Maka Anda perlu menutup koneksi dan memanggil fungsi membaca file lagi, tetapi dengan alamat baru. Menulis fungsi seperti itu dapat ditawarkan sebagai "pekerjaan rumah", tetapi perlu diingat bahwa itu harus didekati dengan hati-hati - Anda harus menyimpan daftar semua pengalihan (lebih mudah menggunakan array statis untuk ini) dan membandingkan alamat, jika tidak fungsinya dapat dengan mudah macet jika pengalihan "berputar".

    Itu, mungkin, itu saja. Saya harap penggunaan fungsi rekursif akan dapat mengisi kembali "persenjataan programmer" Anda dengan alat canggih lainnya ...

    Tampilan