Özyinelemeli php işlevi. PHP'de özyineleme

Bu ders PHP'de özyinelemeyle ilgilidir. Bir fonksiyondan başka bir fonksiyonu çağırabilirsiniz. Bir program f1() öğesini çağırabilir, o da f2() öğesini çağırır ve bu şekilde devam eder. Kendini çağıran bir fonksiyona özyinelemeli denir. Bu tür yinelemeye açık özyineleme denir. Bir f1() işlevi başka bir f2() işlevini çağırırsa, o da f1() işlevini çağırır, bu durumda bu işlevler de özyinelemelidir. Bu tür yinelemeye örtülü özyineleme denir. Açıkçası, örtülü özyinelemenin daha karmaşık biçimleri mümkündür.

Belirli bir sorunu çözmek için özyinelemeli bir işlev oluşturmanız gerektiğini varsayalım. Bu derste bir problemi özyinelemeli fonksiyon kullanarak çözmek için iyi bilinen stratejilerden birini anlatacağız. Bir problemi yinelemeli olarak çözme süreci aşamalara ayrılmıştır. İlk adımda problemi çözmek için özyinelemeli bir fonksiyon çağrılır. Bu fonksiyonda problem en basit durum için çözülür. Bu problemin en basit durumuna temel problem denir. Bir fonksiyon temel bir problemi çözmek için kullanılıyorsa, bir çözüm veya sonuç döndürür.

Temel problemden daha karmaşık bir sorunu çözmek için bir işlev çağrılırsa, işlev sorunu iki parçaya böler:

  • fonksiyonun çözebileceği bölüm 1;
  • fonksiyonun çözemediği bölüm 2.

Özyinelemenin uygulanabilmesi için Bölüm 2'nin başlangıçtaki probleme benzer olması, ancak nispeten daha küçük veya daha basit olması gerekir.

Bölüm 2'de oluşturulan görev orijinal göreve benzer olduğundan, işlev yeni görevi işlemek için kendisinin yeni bir örneğini çağırır. Bu eyleme yinelemeli çağrı veya yineleme adımı denir.

Her yineleme adımında (her yinelemeli çağrıda) görev iki parçaya bölündüğünden, bu yineleme adımlarının sayısı oldukça fazla olabilir.

Özyinelemeyi tamamlamak için özyinelemeli işlevin, temel soruna yaklaşan bir dizi basitleştirici (indirgeyici) sorun oluşturması gerekir. Bazı özyineleme adımlarında fonksiyon temel bir sorunla karşılaştığında, temel sorunun çözümünü (sonucunu) önceki çağrıya (önceki özyineleme adımı) döndürür. Bu çağrı da elde edilen sonucu fonksiyonun çözebileceği kısımla birleştirir. Bu şekilde oluşturulan çözüm bir adım daha yükseğe döndürülür ve bu şekilde devam eder. Bu, özyinelemeli işlev çağrısının başlangıç ​​noktasına döndürülen son sonucu üretir. Yani, bir geri dönüşün mümkün olabilmesi için, fonksiyonun her adımının, ayrılmış dönüş kelimesini içerecek şekilde yapılandırılması gerekir.

Yukarıdaki fikri göstermek için burada özyinelemeli bir işlevin kullanımına ilişkin bir örnek verilmiştir.

Örnek 1. Faktöriyel. Negatif olmayan bir n tam sayısının faktöriyeli, n*(n-1)*(n-2)*...2*1'e eşittir ve n! ile gösterilir. 1!=1 ve 0!=1 kabul edilir. Örneğin, 7!=7*6*5*4*3*2*1=5040.

Faktöryeli hesaplamak için yinelemeli (özyinelemeli olmayan) bir çözüm aşağıdaki gibidir (factorial1.php dosyası):



Faktöriyel



Faktöriyel hesaplama



Doğal Ada (n>=0):





";
çıkış;
}
$f=1;
for($i=$n;$i>=1;$i--)
$f*=$i;
echo "$n!=$f";
?>

f(n)=n! olsun, o zaman
f(n)=n!=n*(n-1)!=n*f(n-1),
yani faktöriyelin hesaplanması için yinelemeli formül şöyledir:
f(n)=n*f(n-1).

Örnek:
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.

Özyinelemeli formüle dayanarak, faktöriyelin hesaplanması için özyinelemeli bir fonksiyon oluşturacağız (factorial2.php dosyası):



Faktöriyel



Faktöriyel hesaplama



Doğal Ada (n>=0):




if(!isset($_GET["n"]) || ($n = $_GET["n"])=="") (
echo "Doğal bir sayı girin!
";
çıkış;
}
$f=faktoriyel($n);
echo "$n!=$f";

fonksiyon faktöriyel($i) (
eğer($i<2) return 1;
$k=$i*faktöriyel($i-1);
$k'ı döndür;
}

?>

Programa 5 sayısını girerseniz, 5'i hesaplayın! program şu şekilde çalışır:

Factorial() fonksiyonu ilk çağrıldığında fonksiyona gönderilen sayının 2'den küçük olup olmadığı kontrol edilir. Eğer fonksiyonun aldığı sayı 2'den az değilse görev temel görevden daha büyük veya daha karmaşık demektir. bu nedenle görev iki bölüme ayrılmıştır:

  1. $k=$i* - işlevin çözebileceği bölüm 1;
  2. faktöriyel($n-1) – fonksiyonun çözemediği bölüm 2.

Bu eylem temel problem alınana kadar yani faktöriyel(1) çağrılana kadar tekrarlanır. Sayı 2'den (1 veya 0) küçükse, faktörial() işlevi 1 sayısını ($k=1) döndürür, yani temel problem çözülür. Factorial() işlevinin bu örneği daha önce faktörial() işlevinin başka bir örneği tarafından çağrılmışsa sonuç, işlevin çağıran örneğine döndürülür. Burada, döndürülen sonuç $k, işleve iletilen ve $k'ye atanan $n parametresiyle çarpılır. Factorial() fonksiyonunun bu örneği daha önce faktörial() fonksiyonunun başka bir örneği tarafından çağrılmışsa, sonuç, fonksiyonun çağıran örneğine döndürülür ve açıklanan süreç tekrarlanır. Eğer faktörial() fonksiyonunun bu örneği programın ana kısmından çağrılmışsa, bu kısma $k döndürülür, burada sonuç ekrana yazdırılır ve programdan çıkar.

  • Algoritmalar
  • Bu makalenin yazılması, hiyerarşik listelerin oluşturulması alanında saatlerce süren düşünce ve deneyler sonucunda ortaya çıktı. Başlangıçta mantık SQL sorguları üzerinde test edildi, ancak daha sonra DBMS'ye olan bağımlılığı ortadan kaldırmak için onu PHP'de uygulamaya karar verdim. Basit bir örnek kullanarak, hiyerarşinin kökünden her bir uç öğeye nasıl gidebileceğinizi ve geriye doğru nasıl gidebileceğinizi göstereceğim; bilgiler yeni başlayanlar için daha olasıdır.

    Yani, üzerinde çalışmamız gereken test hiyerarşisi:

    Veritabanı en basit MSSQL sunucusundaki en basit tabloya sahiptir, bağlantının inceliklerini atlayacağız, amacımız hiyerarşiyi ve özyinelemeyi anlamaktır.

    Bir tablo oluşturalım:

    CREATE TABLE .( IDENTITY(1,1) NOT NULL, -- benzersiz alan, otomatik olarak artan NULL, -- bu alan daha yüksek düzeydeki bir öğeye işaret eder, ebeveynin kullanıcı kimliğini içerir (255) NULL, (50) BOŞ, -- erişim hakları) AÇIK
    Bilgileri dolduralım:

    Alanların açıklamaları yorumlarda, alan hakkında biraz daha fazlası erişim:

    Varsayılan olarak sistemimde her yeni belge için, miras almak yani ebeveynden miras. Denememiz için bazı elementler için alan grupları yazacağız. Grup içinde Etki Alanı Kullanıcıları hesabım mevcut ancak AD Grup Sırrı Burada değilim.

    Başka neyimiz var? Etki alanı gruplarımın listesini içeren bir dizi. Oldukça basit bir şekilde elde edilir, IIS'de Windows kimlik doğrulaması etkinleştirilir, her şey şeffaf bir şekilde çalışır, PHP'de kullanıcının oturumu $_SERVER[“AUTH_USER”] değişkenindedir, ardından bir LDAP isteği kullanarak grupların bir listesini alırız.

    Şimdi gerekli verileri almayı ve doğrudan konuya geçmeyi öneriyorum:

    $stmt = $PDO->query("SELECT * FROM Test"); $tablo = $stmt->fetchAll(); //Tabloyu veritabanından alın $groups = LDAP::getGroups("$login"); //ActiveDirectory gruplarını al

    Görev No.1

    Hiyerarşiyle bir liste olarak değil, bir ağaç olarak çalışmayı öğrenmeniz gerekir. Yuvalama seviyesi önceden bilinmez ve herhangi bir olabilir, bu nedenle ağacı hem yukarıdan aşağıya hem de ters yönde hareket ettirmenize olanak tanıyan evrensel bir araç bulunmalıdır.

    Görev No.2

    Erişimi esnek bir şekilde yönetmek, yani NTFS dosya sistemine benzer şekilde gruplara, bireysel belgelere vb. haklar vermek gerekir, tüm klasörün haklarını kapatabilirsiniz, ancak bu klasördeki bir belge için kesebilirsiniz. erişim - bizde de aynı şey olmalı.

    Görev No.3

    Kullanıcıların erişemediği kaynakları gizlemek gerekiyor ama en önemlisi, eğer kendisine kapalı bir şubenin derinliklerinde bir yerde en az bir belge üzerinde haklarınız varsa, bu belgeye giden unsurları görünür hale getirin. (aksi halde kullanıcı buna nasıl ulaşacak?)

    İşte gerçek temel işlev:

    $dizi = dizi(); //çıkış dizisi işlevi recursive($veri, $pid = 0, $seviye = 0)( global $dizi; foreach ($veri $satır olarak) ( //satırları yineleyin if ($satır["pid"] == $ pid) ( //Pid'i fonksiyona iletilen satırlarla başlayın, bizim için 0'dır, yani sitenin kökü //Satırı ilişkisel bir dizide toplayın $_row["uid"] = $row[ "uid"]; $ _row["pid"] = $row["pid"]; $_row["name"] = $_row["name"] = str_pad("", $level*3, "." ).$row[" name"]; //Nokta eklemek için str_pad fonksiyonunu kullanın $_row["level"] = $level; //Bir seviye ekleyin $dizi = $_row; //Her satırı çıktı dizisine ekleyin //Satır işlendi, şimdi aynı işlevi geçerli kullanıcı kimliği için çalıştıralım, yani // alt satır tersine çevrilecek (bu kullanıcı kimliği piddir) recursive($data, $row["uid"], $seviye + 1); ) )) ) özyinelemeli($tablo); //Öğle yemeği
    Açıklama çoğunlukla yorumlarda verilmiştir, ancak basitçe söylemek gerekirse - foreach döngüsü satırdan geçip verilerle bir şeyler yaptıktan sonra (bizim durumumuzda, verileri başka bir diziye kopyalar, seviye alanını ve noktaları ekler) adı), aynı işlevi çalıştırır, ona dizenin kullanıcı kimliğini iletir ve if koşulunda onu pid ile karşılaştırdığımız için, bir sonraki çalıştırmada kesinlikle alt öğeleri yakalayacaktır. Foreach döngüsü, ana kullanıcı kimliği iletilen değerle eşleşen tüm satırlar boyunca yinelenir; böylece kendini yeniden başlatarak işlev, her düzeydeki her öğe üzerinde çalışacaktır. Netlik sağlamak için seviyeyi birer birer artırarak da geçiyoruz. Sonuç olarak hangi belgenin hangi seviyede iç içe geçmiş olduğunu göreceğiz.

    Dizinin çıktısı $dizi tarayıcıya:

    Artık fena değil, değil mi?

    Şimdi fonksiyonumuzu biraz karmaşıklaştıralım:

    $dizi = dizi(); //çıkış dizisi $dizi_idx_lvl = dizi(); //alan düzeyi fonksiyonuna göre indeksleme recursive($veri, $pid = 0, $seviye = 0, $yol = "", $access_parent = "inherit")( global $dizi; global $dizi_idx_lvl; //düzeye göre indeks global $gruplar; //etki alanı grupları //satırlar boyunca yineleyin foreach ($veri $satır olarak) ( //Pid'i fonksiyona aktarılan satırlarla başlayın, bizim için bu 0'dır, yani sitenin kökü if ( $row["pid "] == $pid) ( //Dizeyi bir ilişkisel dizide toplayın $_row["uid"] = $row["uid"]; $_row["pid"] = $row[" pid"]; $_row[ "name"] = str_pad("", $level*3, ".").$row["name"]; $_row["level"] = $level; //Bir ekle seviye $_row["path"] = $path."/".$row["name"]; //Yola bir ad ekleyin $_row["view"] = ""; //Erişimleri çözümleyin if($ row["access"] == "inherit ") ( $_row["access"] = $access_parent; //Kalıtım varsa, bunu ebeveyn gibi yapın) else ( $_row["access"] = (in_array( $row["access"], $groups)) ? "allow" : "reddet"; ) $array[$row["uid"]] = $_row; //Uid tarafından indekslenen sonuçtaki dizi //Hızlı örnekleme için düzeyde, bir dizin oluşturun $array_idx_lvl[$level][$ row["uid"]] = $row["uid"]; //Satır işlendi, şimdi aynı işlevi geçerli kullanıcı kimliği için çalıştıralım, yani, //alt satır (bu kullanıcı kimliğinin pid olduğu) tersine çevrilecek) recursive($data, $row["uid "], $seviye + 1, $_row["yol"], $_row["erişim"]); )) ) ) özyinelemeli($tablo); //Öğle yemeği
    Sırasıyla bakalım:

    1. Alan eklendi yol- yolu oluşturmak için değere “/” ve dizenin adını ekliyoruz, ardından elde edilen değeri hikayenin tekrarlandığı ve çıktının kökten öğeye giden yol olduğu fonksiyona aktarıyoruz.

    2. Ortaya çıkan dizi artık sıfırdan başlayarak sırayla değil, kullanıcı kimliğine göre oluşturuluyor - $dizi[$satır["uid"]] = $_satır;. Bu durumda, bu hiçbir şekilde betiğin çalışmasını etkilemez, ancak daha sonra ağaçtan geçişi analiz ettiğimizde, bir döngüdeki kaba kuvvetle değil, dizine göre satıra erişme yeteneğine ihtiyacımız olacak. karşı yön.

    3. Dizin eklendi $array_idx_lvl = dizi();. Bu dizine daha sonra da ihtiyacımız olacak, anlamı şu: sonuç kümesi tek bir yığına eklenmez, ancak düzeye göre dizine eklenen dizilere bölünür.

    4. Alan Erişim. Bir işlev kendi kendine çalıştığında diğer parametrelerle birlikte izin ayarlarını da iletir $_row["erişim"] kızları ve sonra aşağıdakiler olur: haklar kontrol edilir - eğer miras ayarlanmışsa, o zaman ebeveynin hakları, eğer değilse, aracılığıyla uygulanır. dizi içi Erişimde belirtilen etki alanı grubunun oturum açan kullanıcının grupları arasında olup olmadığını kontrol ediyoruz. Varsa satıra izin ver, yoksa reddet ekle.

    Son sonuç:

    İnişi hallettik, şimdi geriye sadece yükselişle uğraşmak ve son alanı doldurmak kalıyor görüşöğelerin görünürlüğünü belirler. Yazının başında buna neden ihtiyaç duyulduğunu söylemiştim ama farklı bir durum varsayabiliriz. Diyelim ki, bir dizi öğe içeren çok düzeyli bir açılır liste biçiminde yapılmış bir ağaç listesini sitenin gezinme menüsüne bağlamaya karar verdiniz ve yalnızca tek bir belgeye erişimi olan bir kullanıcı istemiyorsunuz. tüm bu dizide gezinmek ve geniş bir menüde kendi öğesini aramak için, çünkü aslında istenen düğmeye giden yalnızca bir dalı göstermesi gerekiyor.

    Burada neden ters bir geçide ihtiyaç duyuldu? Kullanıcının, en uzaktaki (son düzeydeki) belge dışındaki tüm içeriğe erişiminin reddedildiğini varsayalım; eğer düşünürseniz, mevcut olandan başlayıp onu ağacın köküne kadar götürmek mantıklı olacaktır. sadece gerekli unsurlar.

    Başlayalım:

    //Fonksiyon, ağaçta verilen kullanıcı kimliğinden bir seviye yukarı çıkar, erişime veya önceden ayarlanan görünürlüğe bağlı olarak kendisi ve ebeveyn için görünürlük özelliğini // ayarlar... function backRecursive($uid, $view = null, $ ident = 0) ( global $dizi; //Eğer bir seviyeden fazla yukarı çıkmazsanız 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); } }
    Bu fonksiyonun yaptığı şey parametre olarak almaktır kullanıcı kimliğiİşlem yapılması gereken hat, o hatta erişir ve görünürlüğü kontrol eder. Görünüm alanı göster (yani göster) değil de başka bir şeyse, neyin güvenli olduğunu kontrol eder ve eğer varsa izin vermek(erişim açıktır), öğeyi görünür yapar, aksi halde gizli yapar( saklamak), ardından kendini fırlatıp, ödeme ve görünürlük ayarlarının yanı sıra bir değişken $tanım 1 artırıldı, böylece sonraki otomatik başlatmalar engellendi. Aktarılan bilgiye göre ikinci geçişte ödeme ebeveyn öğenin bulunduğu yerde, değişkendeki çocuktan kaynaklanıyorsa tek bir şey dışında aynı kontrol gerçekleştirilir. $görüntüleme aktarıldı" göstermek", o zaman ne olursa olsun mevcut öğe de atanacak göstermek yani görülebilir.

    Benim düşünceme göre, bir sınırlayıcıyla çalışmak en iyi seçenektir, çünkü durumu hayal edin, 10. seviyede 100 belgemiz var, tüm ağacı tamamen geçmek için bu işlevi her öğe üzerinde çalıştırmamız gerekiyor, çünkü son seviyede işlevi 100 kez çalıştırırsak, kendi kendine başlatma gerçekleştirerek 100 kez arama yaparak köke ulaşacağız. 10 seviyeyle çarparsanız, zaten 1000 döngü elde edersiniz ki bu iyi değildir, bu nedenle yükselişin seviye seviye eşit şekilde yapılması gerekir.

    Aşağıdaki kod bu işlevi çalıştırır:

    Function startBack())( global $array_idx_lvl; $levels = array_keys($array_idx_lvl); //seviyelerin bir dizisini alın $maxLevel = max($levels); //Ağacın en derin seviyesini bulun //Her seviyede döngü yapın en büyüğünden başlayarak for ($i = $maxLevel; $i > 0; $i--) ( $uids = array_keys($array_idx_lvl[$i]); //Geçerli seviyede tüm elemanların üzerinden geçiyoruz ve için her biri foreach için 1 düzeyde işleme ve hareket başlatırız ($uids as $uid) ( backRecursive($uid); )) )
    Seviye endeksine ihtiyaç duyulan yer burasıdır. Burada en ileri seviyeden hareket ederek her birine giriyoruz, içindeki her unsuru işliyoruz.

    Ve işte resim:

    Başlamadan önce, kodun doğru çalıştığını açıkça göstermek için “Vergi Raporu” öğesi için kasıtlı olarak bir izin grubu belirledim. “Muhasebe Raporları” bölümüne erişim kapalı olmasına rağmen görülebilmektedir.

    Hepsi bu, sanırım görevi tamamladık, temel elde edildi, algoritma çalışıyor ve gerçek bir sisteme uygulanabilir.

    Bugün size nasıl yapılacağını anlatacağım MySQL hiyerarşik bir ağaç oluşturun.

    Bu tür ağaçlar, örneğin bir çevrimiçi mağazada dinamik bir sitenin kategorilerini oluştururken veya bir gönderideki yorumları görüntülerken kullanılır.

    Genel olarak mümkün olan her yere inşa edilirler. Önemli olan onu doğru bir şekilde oluşturmak ve uygulamaktır.

    Hiyerarşik bir ağaç oluştururken en önemli şey doğru veritabanı yapısıdır! Örneğin, site kategorilerinin depolandığı veritabanının yapısını düşünün. Basit bir örnek olarak tablonun 3 alanı olacaktır:

    1. İD- kategori anahtarı
    2. ebeveyn_kimliği— üst kategorinin kimliği
    3. isim- Bölüm adı

    PHPMyAdmin'de bir SQL sorgusu çalıştırarak bir tablo oluşturalım:

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

    Şimdi tablomuzu kayıtlarla doldurmamız gerekiyor. Sonuç olarak şöyle bir tablo elde etmelisiniz:

    Bir test tablosunu aşağıdaki sorguyla doldurabilirsiniz:

    `Kategorilere` (`kimlik`, `ebeveyn_kimliği`, `ad`) EKLEYİN DEĞERLER (1, 0, "Bölüm 1"), (2, 0, "Bölüm 2"), (3, 0, "Bölüm 3" ), (4, 1, "Bölüm 1.1"), (5, 1, "Bölüm 1.2"), (6, 4, "Bölüm 1.1.1"), (7, 2, "Bölüm 2.1"), (8 , 2, "Bölüm 2.2"), (9, 3, "Bölüm 3.1");

    Ve şimdi dikkat! Daha sonra, mantıksal olarak, her bir kategoriyi ve onun alt kategorisini seçmek için veritabanından bir döngü içinde seçimler yapmanız gerekir. ANCAK! Veritabanında birkaç kategorinin olması sorun değil, bu da prensipte doğru değil. Site bir çevrimiçi mağazaysa ve yüz kategorisi ve aynı sayıda alt kategorisi varsa ne olur? O zaman bela! Veritabanına bilinmeyen sayıda sorgu yapılması sitenin yavaşlamasına veya MySQL sunucusunun tamamen çökmesine neden olacaktır.

    Tüm kategorileri ve bunların alt kategorilerini seçmek için yalnızca bir veritabanı sorgusu kullanabilirsiniz.

    Bir istekte bulunalım ve daha fazla çalışma için uygun bir dizi oluşturalım.

    //Veritabanından veri seçin $result=mysql_query("SELECT * FROMcategory"); //Veritabanında kayıtlar varsa, bir dizi oluştururuz if (mysql_num_rows($result) > 0)( $cats = array(); //Döngüde bölümlerden oluşan bir dizi oluştururuz, anahtar id olacaktır ana kategorinin yanı sıra bir dizi bölüm için anahtar kategori kimliği olacaktır while($cat = mysql_fetch_assoc($result))( $cats_ID[$cat["id"]] = $cat; $cats[$ cat["parent_id"]][$cat["id"]] = $cat; ))

    Tablodan tüm verilerin seçilmesi kategoriler ve ilişkisel bir dizi oluşturun $kediler anahtar, üst kategorilerin kimliği olacaktır.

    Şimdi bir ağaç yapacağız. Oluşturmak için kullanacağız özyinelemeli işlev.

    Hiyerarşik ağaç aşağıdaki yapıya sahip olacaktır:

    • Bölüm 1
      • Bölüm 1.1
        • Bölüm 1.1.1
      • Bölüm 1.2
    • Bölüm 2
      • Bölüm 1.1
      • Bölüm 1.2
    • Bölüm 3
      • Bölüm 3.1

    Özyinelemeli bir fonksiyon oluşturalım build_tree() . Kesinlikle herhangi bir yuvalamanın hiyerarşik ağacımızı oluşturacaktır.

    Function 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 ][$yalnızca_ebeveyn]; $ağaç .= "
    • ".$cat["isim"]." #".$cat["id"]; $tree .= build_tree($cats,$cat["id"]); $tree .= "
    • "; ) $ağaç .= "
    "; ) yoksa null değerini döndürür; $ağaç değerini döndürür; )

    İşlev bir dizi bölüm ve bölüm kimliğini alır. Döngüde alt kategoriler üzerinden geçiyoruz ve eğer daha fazla bölümleri varsa, işlev yeni parametrelerle (yeni bir bölüm dizisi ve oluşturulması gereken bölümün kimliği) yeniden başlatılır. Herhangi bir yuvalama ağacı bu şekilde oluşur!

    Bir ağaç oluşturmak için koda şunu yazarız:

    Echo build_tree($cats,0);

    Böylece, iki adımda web sitesi bölümlerinin hiyerarşik bir ağacını oluşturduk ve kaç bölüm olduğu önemli değil!

    GÜNCELLEME Kategori kimliğini bilerek ters sırada bir kategori ağacına ihtiyacınız varsa, o zaman işlevi kullanmanız gerekir:

    Function 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"]; )

    Bu işlev, anahtarı kategorinin kimliği ve yukarı çıkmanız gereken kategorinin kimliği olan bir dizi kategoriyi alır.

    Böyle bir ağaç oluşturmak için build_tree işlevini aşağıdaki parametrelerle çalıştırın:

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

    Sorularım var? Yorumlarda sorun

    Çoğu programlama dili özyinelemeli işlevleri, yani kendini çağıran işlevleri destekler. Bu, oldukça zarif ve işlevsel programlar oluşturmanıza olanak tanıyan çok güçlü bir araçtır, ancak oldukça nadiren kullanılır - görünüşe göre acemi programcılar için karmaşıklığı ve hoş olmayan hatalara neden olma yeteneği nedeniyle. Bu nedenle bugünkü yazımızda PHP'de özyinelemeli işlevler oluşturma ve kullanma hakkında konuşacağız.

    Teknik olarak özyinelemeli işlevler normal işlevlerden farklı değildir. Tek fark, fonksiyonun kodunun bir yerinde fonksiyonun kendisine yapılan bir çağrının bulunmasıdır. Örneğin, yazarsanız

    Fonksiyon testi() (
    farklı operatörler
    Ölçek();
    farklı operatörler
    }
    o zaman bu özyinelemeli bir fonksiyon olacaktır.

    Özyinelemeli işlevlerle çalışırken ana zorluk, bunların döngüye alınmasının oldukça kolay olmasıdır - bunun olmasını önlemek için, özyinelemeli bir çağrı olmadan işlevden çıkma olasılığı konusunda çok dikkatli olmanız gerekir, aksi takdirde işlev kendini çağırmaya başlayacaktır. "sonsuzca" ve bilgisayar kaynaklarını hızla tüketecek.

    Özyinelemeli fonksiyonların en yaygın kullanımlarından biri ağaçların üzerinden geçmektir ve bugün birkaç örneğe bakmaya çalışacağız. İlk akla gelen çok boyutlu bir dizinin çıktısıdır. Elbette PHP'nin print_r() işlevi vardır, ancak sonucu daha güzel ve tarayıcı üzerinden görüntülenmeye uygun hale getirmeye çalışacağız.

    function print_array($ar) (
    statik $sayımı;

    $sayısı = (isset($sayım)) ? +$sayısı: 0;

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

    if ($count > count($renkler)) (
    echo "Maksimum dalış derinliğine ulaşıldı!";
    $count--;
    geri dönmek;
    }

    if (!is_array($ar)) (

    ";
    geri dönmek; )

    Eko "

    ";
    Eko " \N";
    if (is_array($v)) (
    Eko " \N";
    }
    }
    Eko "
    bin dolar$v
    ";
    print_array($v);
    Eko "
    ";
    $count--;
    }

    Bu işlev, işlevin "daldırma derinliğini" içerecek olan $count statik değişkenini kullanır; bunu, iç içe geçme derinliğine bağlı olarak iç içe geçmiş dizileri farklı renklerde renklendirmek için kullanacağız. Statik değişkenler fonksiyondan çıkıldığında değerlerini korurlar ve özyineleme sırasında bunların kullanılması oldukça uygundur. Elbette $count değişkenini de parametre olarak kolayca iletebiliriz, ancak statik değişkenlerin kullanımı daha uygundur...

    $colors dizisi, renklendirme için kullanılacak farklı renklerin bir listesini içerir. Aynı zamanda, bunu maksimum özyineleme düzeyini sınırlamak için kullanırız - tüm renkler biter bitmez (($count >= count($colors) olup olmadığını kontrol edin), işlev maksimum derinliği belirten bir mesaj görüntüleyecektir. ulaşıldı.

    Her ihtimale karşı (bu arada, hoş olmayan sonuçlardan kaçınmak için bunu her zaman yapmanız önerilir), dizinin argüman olarak iletildiğini de kontrol ederiz - eğer durum böyle değilse, o zaman sadece bir hata mesajı görüntüleriz ve işlevi sonlandırın:

    If (!is_array($ar)) (
    echo "Geçilen argüman bir dizi değil!

    ";
    geri dönmek;
    }

    Sonra tabloyu "açarız" (echo "

    ";) ve argüman olarak iletilen diziye sırayla bakmaya başlayın.

    While(list($k, $v) = her biri($ar)) (
    ...
    }

    Bir dizide ilerlemenin standart yollarından biridir - döngünün her geçişinde, $k ve $v değişkenlerine aşağıdaki indeks değerleri (veya aynı zamanda anahtar olarak da adlandırılır) ve değerler atanır. dizi öğesinin.

    Bir anahtar/değer çiftimiz olduğunda bunu bir tablo satırında görüntüleriz:

    Eko"

    \N";
    Bu elemanın değeri bir dizi ise "dizi" kelimesinin yazdırılacağını unutmayın. Şimdi değerin bir dizi olup olmadığını kontrol ediyoruz:
    if (is_array($v))
    ve eğer öyleyse, dizini atlayarak tablonun başka bir satırını (tamamen değil!) yazdırırız (zaten önceki satırdadır):
    Eko " \N";
    Bu, geçerli anahtar/değer çiftinin işlenmesini sonlandırır ve while döngüsü bir sonraki çifte geçer. Dizinin tamamı geçildiğinde tek yapmamız gereken tabloyu kapatmak:
    Eko "
    bin dolar$v
    ";
    ve iç içe diziyi argüman olarak belirterek dizi yazdırma işlevimizi çağırın:
    print_array($v);
    Daha sonra (özyinelemeli olarak çağrılan fonksiyonun çalışması bittikten sonra) tablo satırını "kapatırız" (fonksiyonumuzun "tam tabloyu" yazdırdığını unutmayın - itibaren önce
    , - iç içe geçmiş tabloyu kapatmamıza gerek yok - işlev bunu kendisi halledecektir - ancak yalnızca geçerli tablonun hücresini ve satırını kapatmamız gerekir).
    Eko "
    ";
    ve derinlik değerini azaltın
    $count--; Yukarıda yazılanlardan, fonksiyonumuzun özyinelemeli olarak çağrılıp çağrılmayacağı hakkında hiçbir şey bilmediği ve bunu umursamadığı açıktır - asıl mesele, bir dizinin argüman olarak iletilmesidir. Benzer şekilde, iç içe geçmiş bir diziyi işlemek için kendisini çağırırken, bunun özyinelemeli bir çağrı olması umurunda değildir; başka bir işlevi çağırmaktan farklı değildir. Ve programcının kaygısı, özyinelemeli çağrıların bir yerde sona ermesini, yani fonksiyonun kendisini tekrar çağırmadan işini bitirebilmesini sağlamaktır. Bu gerçekleştiğinde, iç içe geçmiş çağrıların derinliği azalmaya başlayacak ve sonunda işlev "yüzeye çıkacak".

    Böyle bir fonksiyonun sonucu şunun gibi görünecektir (örnekte maksimum derinlik üçüncü seviyeyle sınırlıdır, netlik sağlamak için $count değişkeninin çıktısı eklenmiştir ve yazdırma dizisi array(1) olarak ayarlanmıştır. , dizi(1.1, 1.2, 1.3), 2, 3 , dizi(3.1, 3.2, dizi(3.21, 3.22, 3.23, dizi(3.231, 3.232), 3.24))), 4)

    saymakanahtardeğer
    0 0 1
    0 1 Sıralamak
    0
    saymakanahtardeğer
    1 0 1.1
    1 1 1.2
    1 2 1.3
    0 2 2
    0 3 3
    0 4 Sıralamak
    0
    0 5 4 Bir diziyi yazdırmak, belki de komut dosyalarınızı test etmek dışında, "gerçek hayatta" nadiren gereklidir, ancak özyineleme oldukça sık kullanışlı olabilir. Örneğin, daha hayati bir ihtiyaç, alt dizinler de dahil olmak üzere bir dizindeki tüm dosyaların işlenmesidir. Örneğin, dosyaları silmek için yinelemeli dd() fonksiyonunu kullanmak uygundur (Dizin Silme'nin kısaltması): function dd($file) (
    if (dosya_var($dosya)) (
    chmod($dosya,0777);
    if (is_dir($dosya)) (
    $tanıtıcı = opendir($dosya);
    while($dosyaadı = readdir($tanımlayıcı))
    if ($dosyaadı != "." && $dosyaadı != "..") dd($dosya."/".$dosyaadı);
    closedir($tanımlayıcı);
    rmdir($dosya);
    ) başka (
    bağlantıyı kaldır($dosya);
    }
    }
    }

    Tam olarak aynı prensipte çalışır - eğer bir dosya argüman olarak iletilirse silinir (bağlantıyı kaldır($dosya);) ve eğer bir dizin açılırsa, sırayla aranır ve her dosya için (alt dizinler dahil) aynı işleve dd() denir...

    Özyinelemeli işlevlerin kullanımının çok uygun olduğu başka bir örnek, dosyaları yeniden yönlendirme izlemeyle HTTP protokolü aracılığıyla okumaktır - bir bağlantı açmanız, dosyayı (veya yalnızca başlığı) istemeniz ve sunucu yanıtını kontrol etmeniz gerekir - eğer başlık içeriyorsa

    Daha sonra bağlantıyı kapatmanız ve dosya okuma işlevini tekrar çağırmanız gerekir, ancak yeni bir adresle. Böyle bir işlevi yazmak “ev ödevi” olarak önerilebilir, ancak buna dikkatli yaklaşmanız gerektiğini unutmayın - kesinlikle tüm yönlendirmelerin bir listesini kaydetmeli (bunun için statik bir dizi kullanmak uygundur) ve adresleri karşılaştırmalısınız, aksi takdirde "yuvarlak" yönlendirmeler varsa işlev çok kolay bir döngüye girebilir.

    Muhtemelen hepsi bu. Özyinelemeli işlevlerin kullanımının "programcının cephaneliğine" başka bir güçlü araç ekleyeceğini umuyorum...

    Görüntüleme