4

LINQ を使用して再帰呼び出しを制限する方法を理解しようとしています。

次のコードでの私の意図は、数値のリストを実行し ( num)、数値ごとに設定された量 ( 6) まで再帰的にカウント/出力することです。

newnum私が取得しようとしているシーケンスは次のとおりです: 3 4 5 1 2 3 4 5 5 2 3 4 5

しかし当然のことながら、代わりに無限ループに陥っています。.Where私が思っていたように、述語はループを止めていません。私の基本的なケースがオフになっている可能性があります。これを設定する適切な方法についての洞察はありますか? ありがとうございました。

var num = new[] {3, 1, 8, 5, 2};

    Func<int, int> writeString = delegate(int count)
                        {                       
                            Func<int, int> recursiveWrite = null;
                            recursiveWrite = n => 
                                                {
                                                    Console.WriteLine("string " + n); 

                                                    recursiveWrite(n+1);
                                                    return n;
                                                };
                            return recursiveWrite(count);
                        };

    var newnum = num.Where(n => writeString(n) < 6);   // is this possible?
    newnum.ToList().ForEach( w => Console.WriteLine(w));

次のサンプル コードで同様の停止パターンが発生していることに気付きました。これに.Whereは 7 未満の階乗のみが含まれます。何が欠けていますか?

var numbers = new[] { 5,1,3,7,2,6,4};

Func<int, int> factorial = delegate(int num) {
        Func<int, int> locFactorial = null;
        locFactorial = n => n == 1 ? 1 : n * locFactorial(n - 1);
        return locFactorial(num);
};

var smallnums = numbers.Where(n => factorial(n) < 7);
4

4 に答える 4

4

答えは、基本的なケースがないということです。再帰関数が実行されると、それを止めるものは何もありません。LINQ は、別の関数の内部ロジックを変更できる魔法のようなものを実行しません。

この例では、再帰を停止するこの重要なコードが欠落しています - 基本ケース:

locFactorial = n => n == 1 ? 1 : n * locFactorial(n - 1);

三項演算子はチェックを行いn==1、そうである場合は 1 を返します。これは、関数に欠けている基本的なケースです。

LINQ のみを使用して関数に基本ケースを提供する方法はありません。これを再帰関数に組み込む必要があります。

さらに、単一の数値から数値のリストを返したい場合、再帰関数から間違った型を返しています。これは、単一の数値Factorialを指定して単一の数値を返す関数とは基本的に異なるケースです。

再帰を使用せずに必要なことを行う関数を次に示します。

void Main()
{
    var numbers = new[] {3, 1, 8, 5, 2};

    numbers.SelectMany(x => GetIncreasing(x).TakeWhile(y => y < 6));
}

IEnumerable<int> GetIncreasing(int x)
{
   while (true)
       yield return x++;
}
于 2013-03-14T10:26:28.773 に答える
2

次のように、要件に合ったシーケンスを生成することに固執することができます。

var num = new[] { 3, 1, 8, 5, 2 };
var limit = 6;

var query = from n in num
            where n < limit // sanity check
            from pn in Enumerable.Range(n, limit - n)
            select pn;

まともなパフォーマンスとクリーンなコード

于 2013-03-14T10:41:26.880 に答える
1

あなたが達成しようとしていることは完全にはわかりませんが、これが役立つことを願っています. 再帰ラムダに停止条件が必要です (階乗では n==1 として)。ネストされた関数を使用すると、この制限を「動的に」挿入できます。

class Program
{
    static void Main(string[] args)
    {
        var num = new[] { 3, 1, 8, 5, 2 };
        Func<int, Func<int, IEnumerable<int>>> writeString = 
            delegate(int maxcount)
            {
                Func<int, IEnumerable<int>> recursiveWrite = null;
                recursiveWrite = (n) =>
                    {
                        if (n < maxcount)
                        {
                            Console.WriteLine("string " + n);
                            var rec = recursiveWrite(n + 1);
                            return new List<int>(){n}.Concat(rec);
                        }
                        return new List<int>();
                    };
                return recursiveWrite;
            };

        var newnum = num.SelectMany(n => writeString(6)(n));   // is this possible?
        newnum.ToList().ForEach(w => Console.WriteLine(w));
        Console.ReadLine();
    }
}
于 2013-03-14T10:34:31.783 に答える