web-dev-qa-db-ja.com

素人の言葉では、PHP=

誰かが私に再帰関数を説明してくれますかPHP(フィボナッチを使用せずに)素人言語で、例を使用して?私は例を見ていたが、フィボナッチは完全に私を失った!

事前に感謝します;-)また、どのくらいの頻度でWeb開発に使用しますか?

72
Imran

ライメンの用語:

再帰関数は、itselfを呼び出す関数です

もう少し詳しく:

関数がそれ自体を呼び出し続けている場合、いつ停止するかをどのように知るのですか?ベースケースと呼ばれる条件を設定します。基本ケースは、再帰呼び出しをいつ停止するかを指示します。そうしないと、無限にループします。

私にとって数学の良いバックグラウンドを持っているので、私にとって良い学習例は factorial でした。以下のコメントから、階乗関数は少し多すぎるように思われます。必要に応じてここに残しておきます。

function fact($n) {
  if ($n === 0) { // our base case
     return 1;
  }
  else {
     return $n * fact($n-1); // <--calling itself.
  }
}

Web開発で再帰関数を使用することに関して、私は個人的に再帰呼び出しを使用することに頼りません。再帰に頼るのは悪い習慣だと思うわけではありませんが、再帰を最初の選択肢とすべきではありません。適切に使用しないと、致命的になる可能性があります。

ディレクトリの例と競合することはできませんが、これがいくらか役立つことを願っています。

(4/20/10)更新:

受け入れられた答えが、再帰関数がどのように機能するかを素人の言葉で実証しているこの質問をチェックすることも役立ちます。 OPの質問はJavaを扱っていましたが、概念は同じですが、

87

例は、特定のディレクトリのサブディレクトリにあるすべてのファイルを印刷することです(これらのディレクトリ内にシンボリックリンクがなく、何らかの形で機能が壊れる場合があります)。すべてのファイルを印刷する擬似コードは次のようになります。

_function printAllFiles($dir) {
    foreach (getAllDirectories($dir) as $f) {
        printAllFiles($f); // here is the recursive call
    }
    foreach (getAllFiles($dir) as $f) {
        echo $f;
    }
}
_

最初にすべてのサブディレクトリを印刷し、次に現在のディレクトリのファイルを印刷します。この考え方はすべてのサブディレクトリに適用され、それがすべてのサブディレクトリに対してこの関数を再帰的に呼び出す理由です。

この例を試してみたい場合は、特別なディレクトリ_._および_.._を確認する必要があります。そうしないと、常にprintAllFiles(".")を呼び出すことにこだわってしまいます。さらに、印刷する内容と現在の作業ディレクトリを確認する必要があります(opendir()getcwd()、...を参照)。

31
Progman

再帰は繰り返されるものです。内部から自身を呼び出す関数のように。少し擬似的な例を示しましょう:

友だちがビールを飲んでいるのに、真夜中までに家に帰らなければ妻が地獄に行くと想像してください。この目的のために、orderAndDrinkBeer($ time)関数を作成しましょう。$ timeは、午前0時から現在の飲み物を飲み終えて家に帰るまでの時間を引いたものです。

だから、バーに到着したら、最初のビールを注文して飲み始めます:

$timeToGoHome = '23';  // Let's give ourselves an hour for last call and getting home

function orderAndDrinkBeer($timeToGoHome) {  // Let's create the function that's going to call itself.
    $beer = New Beer();  // Let's grab ourselves a new beer
    $currentTime = date('G'); // Current hour in 24-hour format

    while ($beer->status != 'empty') {  // Time to commence the drinking loop
        $beer->drink();  // Take a sip or two of the beer(or chug if that's your preference)
    }

    // Now we're out of the drinking loop and ready for a new beer

    if ($currentTime < $timeToGoHome) { // BUT only if we got the time
        orderAndDrinkBeer($timeToGoHome);  // So we make the function call itself again!
    } else {  // Aw, snap!  It is time :S
        break; // Let's go home :(
    }
}

さあ、酔っ払うほどのビールを飲むことができなかったので、時間通りに家にいなくても妻がソファで寝てしまうことを願っています。

しかし、ええ、それは再帰がどのように行われるかということです。

22
Freyr

自身を呼び出す関数です。木など、繰り返される特定のデータ構造を歩くのに便利です。 HTML DOMは典型的な例です。

Javascriptのツリー構造と、ツリーを「歩く」再帰関数の例。

    1
   / \
  2   3
     / \
    4   5

-

var tree = {
  id: 1,
  left: {
    id: 2,
    left: null,
    right: null
  },
  right: {
    id: 3,
    left: {
      id: 4,
      left: null,
      right: null
    },
    right: {
      id: 5,
      left: null,
      right: null
    }
  }
};

ツリーをたどるには、同じ関数を繰り返し呼び出して、現在のノードの子ノードを同じ関数に渡します。次に、最初に左側のノードで、次に右側で関数を再度呼び出します。

この例では、ツリーの最大深度を取得します

var depth = 0;

function walkTree(node, i) {

  //Increment our depth counter and check
  i++;
  if (i > depth) depth = i;

  //call this function again for each of the branch nodes (recursion!)
  if (node.left != null) walkTree(node.left, i);
  if (node.right != null) walkTree(node.right, i);

  //Decrement our depth counter before going back up the call stack
  i--;
}

最後に、関数を呼び出します

alert('Tree depth:' + walkTree(tree, 0));

再帰を理解する優れた方法は、実行時にコードをステップスルーすることです。

9
James Westgate

簡単に言えば、再帰関数とは、それ自体を呼び出す関数です。

7
Samuel

関数が未定義の有限時間タスクを達成するためにそれ自体を呼び出すとき、それは非常に簡単です。私自身のコードの例、マルチレベルのカテゴリツリーを生成する関数

function category_tree($ parent = 0、$ sep = '')
 {
 $ q = "categoryからid、nameを選択、parent_id ="。$ parent; 
 $ rs = mysql_query($ q); 
 while($ rd = mysql_fetch_object($ rs))
 {
 echo( 'id。' "> '。$ sep。$ rd- > name。 ''); 
 category_tree($ rd-> id、$ sep .'-- '); 
} 
}
5
Imran Naqvi

再帰は、「完了するまでこのことを繰り返してください」と言うのに適した方法です。

持つべき2つの重要なもの:

  1. 基本ケース-到達する目標があります。
  2. テスト-目的地に着いたかどうかを知る方法。

簡単なタスクを想像してください。書籍のスタックをアルファベット順に並べ替えます。簡単なプロセスは、最初の2冊の本を取り出してソートすることです。さて、ここに再帰的な部分が来ます:本がもっとありますか?その場合は、もう一度実行してください。 「もう一度やる」は再帰です。 「これ以上本がありますか」がテストです。そして、「いいえ、これ以上本はありません」が基本ケースです。

4
Shane Castle

自分がここにいることを学んでいたときに見つけた最良の説明: http://www.elated.com/articles/php-recursive-functions/

それは、1つの理由からです:

呼び出されたときの関数はメモリに作成されます(新しいインスタンスが作成されます)

そのため、再帰関数はITSELFを呼び出しませんが、他のインスタンスを呼び出します。いくつかの値を返すメモリ内のインスタンスのカップル-この動作は、たとえば関数aが関数bを呼び出している場合も同じです。 2つのインスタンスと、再帰関数がそれ自体の新しいインスタンスを呼び出す場合があります。

紙の上のインスタンスでメモリを描画してみてください-それは理にかなっています。

2
Ales

基本的にこれ。終了するまで自分自身を呼び出し続けます

void print_folder(string root)
{
    Console.WriteLine(root);
    foreach(var folder in Directory.GetDirectories(root))
    {
        print_folder(folder);
    }
}

ループでも動作します!

void pretend_loop(int c)
{
    if(c==0) return;
    print "hi";
    pretend_loop(c-);
}

グーグルで試すこともできます。 「もしかして」に注意してください(クリックしてください...)。 http://www.google.com/search?q=recursion&spell=1

1
user34537

再帰はループに代わるものであり、コードに明確さや優雅さをもたらすことはめったにありません。良い例を挙げたのは、再帰を使用しない場合、現在のディレクトリ(状態と呼ばれる)を追跡することを強制される再帰を使用しない場合、スタックを使用してブックキーピングを実行できるようにする(変数のある領域)メソッドの戻りアドレスが保存されます)

標準例のfactorialとFibonacciは、ループで簡単に置き換えることができるため、概念の理解には役立ちません。

1
stacker

これが実用的な例です(すでにいくつかの良い例があります)。ほとんどすべての開発者に役立つものを追加したかっただけです。

ある時点で、開発者は、APIまたは何らかのタイプのオブジェクトまたは配列からの応答と同様に、オブジェクトを解析する必要があります。

この関数は最初にパラメータを含むオブジェクトを解析するために呼び出されますが、オブジェクトに他のオブジェクトまたは配列も含まれている場合はどうなりますか?これに対処する必要があり、ほとんどの場合、基本的な関数はすでにこれを実行しているため、関数は(キーまたは値がオブジェクトまたは配列であることを確認した後)再度自身を呼び出し、この新しいオブジェクトまたは配列を解析します。最終的に返されるのは、読みやすくするために各行に各パラメーターを作成する文字列ですが、値をログファイルに簡単に記録したり、DBなどに挿入したりできます。

$prefixパラメーターを追加して、親要素を使用して終了変数の説明を支援し、値が関係するものを確認できるようにしました。 null値などは含まれていませんが、この例から修正できます。

オブジェクトがある場合:

$apiReturn = new stdClass();
$apiReturn->shippingInfo = new stdClass();
$apiReturn->shippingInfo->fName = "Bill";
$apiReturn->shippingInfo->lName = "Test";
$apiReturn->shippingInfo->address1 = "22 S. Deleware St.";
$apiReturn->shippingInfo->city = "Chandler";
$apiReturn->shippingInfo->state = "AZ";
$apiReturn->shippingInfo->Zip = 85225;
$apiReturn->phone = "602-312-4455";
$apiReturn->transactionDetails = array(
    "totalAmt" => "100.00",
     "currency" => "USD",
     "tax" => "5.00",
     "shipping" => "5.00"
);
$apiReturn->item = new stdClass();
$apiReturn->item->name = "T-shirt";
$apiReturn->item->color = "blue";
$apiReturn->item->itemQty = 1;

そして使用:

var_dump($apiReturn);

戻ります:

object(stdClass)#1(4){["shippingInfo"] => object(stdClass)#2(6){["fName"] => string(4) "Bill" ["lName"] => string( 4) "Test" ["address1"] => string(18) "22 S. Deleware St." ["city"] => string(8) "Chandler" ["state"] => string(2) "AZ" ["Zip"] => int(85225)} ["phone"] => string(12 ) "602-312-4455" ["transactionDetails"] => array(4){["totalAmt"] => string(6) "100.00" ["currency"] => string(3) "USD" [" tax "] => string(4)" 5.00 "[" shipping "] => string(4)" 5.00 "} [" item "] => object(stdClass)#3(3){[" name "] = > string(7) "Tシャツ" ["色"] => string(4) "青" ["itemQty"] => int(1)}}

そして、各パラメーターの改行を含む文字列に解析するコードは次のとおりです。

function parseObj($obj, $prefix = ''){
    $stringRtrn = '';
    foreach($obj as $key=>$value){
        if($prefix){
            switch ($key) {
                case is_array($key):
                    foreach($key as $k=>$v){
                        $stringRtrn .= parseObj($key, $obj);
                    }
                    break;
                case is_object($key):
                    $stringRtrn .= parseObj($key, $obj);
                    break;
                default:
                    switch ($value) {
                        case is_array($value):
                            $stringRtrn .= parseObj($value, $key);
                            break;
                        case is_object($value):
                            $stringRtrn .= parseObj($value, $key);
                            break;
                        default:
                            $stringRtrn .= $prefix ."_". $key ." = ". $value ."<br>";
                            break;
                    }
                    break;
            }
        } else { // end if($prefix)
            switch($key){
                case is_array($key):
                    $stringRtrn .= parseObj($key, $obj);
                    break;
                case is_object($key):

                    $stringRtrn .= parseObj($key, $obj);
                    break;
                default:
                    switch ($value) {
                        case is_array($value):
                            $stringRtrn .= parseObj($value, $key);
                            break;
                        case is_object($value):
                            $stringRtrn .= parseObj($value, $key);
                            break;                      
                        default:
                            $stringRtrn .= $key ." = ". $value ."<br>";
                            break;
                    } // end inner switch 
            } // end outer switch
        } // end else
    } // end foreach($obj as $key=>$value)
    return $stringRtrn;
} // END parseObj()

これにより、次のようにオブジェクトが返されます。

shippingInfo_fName = Bill
shippingInfo_lName = Test
shippingInfo_address1 = 22 S. Deleware St.
shippingInfo_city = Chandler
shippingInfo_state = AZ
shippingInfo_Zip = 85225
phone = 602-312-4455
transactionDetails_totalAmt = 100.00
transactionDetails_currency = USD
transactionDetails_tax = 5.00
transactionDetails_shipping = 5.00
item_name = T-shirt
item_color = blue
item_itemQty = 1

if . . . ifelse . . . elseとの混乱を避けるために入れ子になったスイッチステートメントを実行しましたが、ほぼ同じ長さでした。それが役立つ場合は、if条件を要求するだけで、必要な人のためにそれらを貼り付けることができます。

1
Joel

Anthony Forloneyの例に特定の値(「1」など)を追加すると、すべてが明確になります。

function fact(1) {
  if (1 === 0) { // our base case
  return 1;
  }
  else {
  return 1 * fact(1-1); // <--calling itself.
  }
}

元の:

function fact($n) {
  if ($n === 0) { // our base case
    return 1;
  }
  else {
  return $n * fact($n-1); // <--calling itself.
  }
}
0
Kevin

例も役に立たないと思います。あなたの心が2つの問題の間で切り替わる数学が関係しているとき、あなたは一度に2つの問題を解決することはできません。十分な話

例:

:区切り文字に基づいて文字列を配列に分解する関数があります。

public function explodeString($string)
{
  return explode(":", $string);
}

入力として文字列を取っている別の関数があります

public function doRec()
{
    $strings = [
        'no:go',
        'hello:world',
        'nested' => [
            'iam:good',
            'bad:array',
            'bad:how',
            'bad:about',
        ]
    ];

    $response = [];

    foreach ($strings as $string) {
        array_Push($response,$this->explodeString($string));
    }

    return $response;
}

問題は、入力にネストされた配列があり、explodeString関数がstring型を受け取ることです。これに合わせてexplodeString関数でいくつかのコードを書き換えることができますが、文字列に対して同じ操作を行うには同じ関数が必要です。それは、メソッドrecursivelyを呼び出すことができる場所です。再帰を伴う最後のexplodeString関数です。

public function explodeString($string)
{
    if (is_array($string)) {
       $combine = [];
       foreach ($string as $str) {
           array_Push($combine, $this->explodeString($str));
        }
       return $combine;
    }

    return explode(":", $string);
}
0
fayz

Kaprekarの定数に使用される再帰

function KaprekarsConstant($num, $count = 1) {
    $input = str_split($num);
    sort($input);

    $ascendingInput  = implode($input);
    $descendingInput = implode(array_reverse($input));

    $result = $ascendingInput > $descendingInput 
        ? $ascendingInput - $descendingInput 
        : $descendingInput - $ascendingInput;

    if ($result != 6174) {
        return KaprekarsConstant(sprintf('%04d', $result), $count + 1);
    }

    return $count;

}

この関数は、Kaprekars定数に達するまで計算の結果で自身を呼び出し続け、Kaprekars定数は計算が行われた回数を返します。

/ edit Kaprekars Constantを知らない人は、少なくとも2桁の異なる4桁の入力が必要です。

0
Bart

ディレクトリツリーを歩くのは良い例です。配列を処理するのと同じようなことができます。これは、文字列、文字列の単純な配列、または任意の深さの文字列のネストされた配列を単純に処理し、文字列または配列または任意の値の「hello」のインスタンスを「goodbye」サブ配列:

function replaceHello($a) {
    if (! is_array($a)) {
        $a = str_replace('hello', 'goodbye', $a);
    } else {
        foreach($a as $key => $value) {
            $a[$key] = replaceHello($value);
        }
    }
    return $a
}

ある時点で、処理している「もの」は配列ではないため、いつ終了するかを知っています。たとえば、replaceHello( 'hello')を呼び出すと、 'goodbye'が返されます。文字列の配列を送信すると、配列のすべてのメンバーに対して1回呼び出されますが、処理された配列を返します。

0
Bob Ray

これは、再帰を使用した階乗の非常に単純な例です。

階乗は非常に簡単な数学の概念です。彼らは5のように書かれています!これは5 * 4 * 3 * 2 * 1を意味します。 720と4です! 24です。

function factorial($number) { 

    if ($number < 2) { 
        return 1; 
    } else { 
        return ($number * factorial($number-1)); 
    } 
}

これがあなたの役に立つことを願っています。 :)

0
user4614642

再帰的な単純な例で動作します(Y)

<?php 


function factorial($y,$x) { 

    if ($y < $x) { 
        echo $y; 
    } else { 
        echo $x; 
        factorial($y,$x+1);
    } 
}

$y=10;

$x=0;
factorial($y,$x);

 ?>
0