web-dev-qa-db-ja.com

Java 8を使用して再帰的なラムダ関数を実装する

Java 8はラムダ関数を導入し、階乗のようなものを実装したい:

 IntToDoubleFunction fact = x -> x == 0 ? 1 : x * fact.applyAsDouble(x-1);

コンパイルが戻る

  error: variable fact might not have been initialized

関数自体を参照するにはどうすればよいですか。クラスは匿名ですが、インスタンスは存在します:factと呼ばれます。

53
user2678835

私は通常、機能インターフェース型の変数をラップする汎用ヘルパークラス(一度だけ定義されたすべての機能インターフェース)を使用します。このアプローチにより、ローカル変数の初期化に関する問題が解決され、コードがより明確に見えるようになります。

この質問の場合、コードは次のようになります。

// Recursive.Java
// @param <I> - Functional Interface Type
public class Recursive<I> {
    public I func;
}

// Test.Java
public double factorial(int n) {

    Recursive<IntToDoubleFunction> recursive = new Recursive<>();
    recursive.func = x -> (x == 0) ? 1 : x * recursive.func.applyAsDouble(x - 1);

    return recursive.func.applyAsDouble(n);
}
44
Andrey Morozov

1つの方法は、2番目の関数helperを記述します。これは、関数と引数を引数として受け取り、実際に必要な関数fact = helper(helper,x)を記述します。

そのようです:

BiFunction<BiFunction, Double, Double> factHelper =
        (f, x) -> (x == 0) ? 1.0 : x*(double)f.apply(f,x-1);
Function<Double, Double> fact =
        x -> factHelper.apply(factHelper, x);

これは、可変構造への参照をキャプチャするクロージャーのようなコーナーケースセマンティクスに依存したり、「初期化されない可能性がある」という警告を伴う自己参照を許可したりするよりも、わずかにエレガントに思えます。

それでも、Javaの型システムのため、完全なソリューションではありません。ジェネリックは、fの引数であるfactHelperfactHelperと同じ型(つまり、同じ入力型)であることを保証できませんおよび出力タイプ)、これは無限にネストされたジェネリックになるためです。

したがって、代わりに、より安全なソリューションがあります:

Function<Double, Double> fact = x -> {
    BiFunction<BiFunction, Double, Double> factHelper =
        (f, d) -> (d == 0) ? 1.0 : d*(double)f.apply(f,d-1);
    return factHelper.apply(factHelper, x);
};

factHelperの不完全なジェネリック型から発生するコードの匂いは、ラムダ内に含まれている(またはあえて言うと、カプセル化されている)ため、factHelperが知らないうちに呼び出されることはありません。

18
rationalis

ローカルクラスと匿名クラス、およびラムダは、ロ​​ーカル変数値によるが作成されるときにそれらをキャプチャします。したがって、ローカル変数をキャプチャして自分自身を参照することはできません。自分自身を指す値は、作成された時点ではまだ存在しないためです。

ローカルクラスと匿名クラスのコードは、thisを使用して自分自身を参照できます。ただし、ラムダ内のthisはラムダを参照しません。外部スコープのthisを参照します。

代わりに、配列のような可変データ構造をキャプチャできます。

IntToDoubleFunction[] foo = { null };
foo[0] = x -> { return  ( x == 0)?1:x* foo[0].applyAsDouble(x-1);};

ほとんどエレガントなソリューションではありません。

13
newacct

このようなことを頻繁に行う必要がある場合は、ヘルパーインターフェイスとメソッドを作成することもできます。

public static interface Recursable<T, U> {
    U apply(T t, Recursable<T, U> r);
}

public static <T, U> Function<T, U> recurse(Recursable<T, U> f) {
    return t -> f.apply(t, f);
}

そして次のように書きます:

Function<Integer, Double> fact = recurse(
    (i, f) -> 0 == i ? 1 : i * f.apply(i - 1, f));

(これを参照型で一般的に行いましたが、プリミティブ固有のバージョンを作成することもできます)。

これは、名前のない関数を作成するためのリトルリスパーの古いトリックから借用しています。

これを本番コードで実行するかどうかはわかりませんが、興味深いです...

8
Ian Robertson

答えは:名前変数がapplyAsDouble関数を呼び出す前にthisを使用する必要があります:-

IntToDoubleFunction fact = x -> x == 0 ? 1 : x * this.fact.applyAsDouble(x-1);

あなたが事実を最終的にすれば、それも機能します

final IntToDoubleFunction fact = x -> x == 0 ? 1 : x * this.fact.applyAsDouble(x-1);

ここでは、機能的なインターフェイスnaryOperatorを使用できます。入力引数を常に返す単項演算子。

1)thisを追加するだけです。次のように、関数名の前に:

UnaryOperator<Long> fact = x -> x == 0 ? 1  : x * this.fact.apply(x - 1 );

これにより回避されます「定義される前にフィールドを参照できません」

2)staticフィールドを使用する場合は、単に 'this'をクラスの名前に置き換えます。

static final UnaryOperator<Long> fact = x -> x== 0? 1: x * MyFactorial.fact.apply(x - 1 );
4
Arundev

再帰的なラムダをインスタンス変数またはクラス変数として定義できます。

static DoubleUnaryOperator factorial = x -> x == 0 ? 1
                                          : x * factorial.applyAsDouble(x - 1);

例えば:

class Test {
    static DoubleUnaryOperator factorial = x -> x == 0 ? 1
                                             : x * factorial.applyAsDouble(x - 1));
    public static void main(String[] args) {
        System.out.println(factorial.applyAsDouble(5));
    }
}

120.0

2
assylias
public class Main {
    static class Wrapper {
        Function<Integer, Integer> f;
    }

    public static void main(String[] args) {
        final Wrapper w = new Wrapper();
        w.f = x -> x == 0 ? 1 : x * w.f.apply(x - 1);
        System.out.println(w.f.apply(10));
    }
}
2
Danil Gaponov

最初の返信に少し似ています...

public static Function<Integer,Double> factorial;

static {
    factorial = n -> {
        assert n >= 0;
        return (n == 0) ? 1.0 : n * factorial.apply(n - 1);
    };
}
2
Rene.v.P.

再帰を最適化できるように、アキュムレーターを使用する別のバージョン。汎用インターフェイス定義に移動しました。

Function<Integer,Double> facts = x -> { return  ( x == 0)?1:x* facts.apply(x-1);};
BiFunction<Integer,Double,Double> factAcc= (x,acc) -> { return (x == 0)?acc:factAcc.apply(x- 1,acc*x);};
Function<Integer,Double> fact = x -> factAcc.apply(x,1.0) ;

public static void main(String[] args) {
   Test test = new Test();
   test.doIt();
}

 public void doIt(){
int val=70;
System.out.println("fact(" + val + ")=" + fact.apply(val));
}
}
2
user2678835

以下は動作しますが、不可解に見えます。

import Java.util.function.Function;

class recursion{

Function<Integer,Integer>  factorial_lambda;  // The positions of the lambda declaration and initialization must be as is.

public static void main(String[] args) {  new recursion();}

public recursion() {
 factorial_lambda=(i)->{
        if(i==1)
            return 1;
        else
            return i*(factorial_lambda.apply(i-1));
    };
    System.out.println(factorial_lambda.apply(5));
 }
}

// Output 120
2
Jerrolds

1つの解決策は、この関数をINSTANCE属性として定義することです。

import Java.util.function.*;
public class Test{

    IntToDoubleFunction fact = x -> { return  ( x == 0)?1:x* fact.applyAsDouble(x-1);};

    public static void main(String[] args) {
      Test test = new Test();
      test.doIt();
    }

    public void doIt(){
       System.out.println("fact(3)=" + fact.applyAsDouble(3));
    }
}
2
user2678835

サイズ1(たとえばFunction [])の最終配列を作成してローカル変数として定義し、関数を要素0に割り当てることもできます。正確な構文が必要かどうかを教えてください

1
Victor Grazi

今年のJAXで、「lambadsは再帰をサポートしていません」と聞きました。このステートメントの意味するところは、ラムダ内の「this」は常に周囲のクラスを参照するということです。

しかし、少なくとも「再帰」という用語をどのように理解するのかを定義することができました-再帰ラムダとそれは次のようになります:

interface FacInterface {
  int fac(int i);
}
public class Recursion {
  static FacInterface f;
  public static void main(String[] args)
  {
    int j = (args.length == 1) ? new Integer(args[0]) : 10;
    f = (i) -> { if ( i == 1) return 1;
      else return i*f.fac( i-1 ); };
    System.out.println( j+ "! = " + f.fac(j));
  }
}

これをファイル「Recursion.Java」内に保存し、2つのコマンド「javac Recursion.Java」と「Java Recursion」を使用して、それが機能しました。

Clouは、ラムダが周囲のクラスのフィールド変数として実装する必要があるインターフェイスを保持することです。ラムダはそのフィールドを参照でき、フィールドは暗黙的に最終的なものにはなりません。

1
beck

Java 8の別の再帰的階乗

public static int factorial(int i) {
    final UnaryOperator<Integer> func = x -> x == 0 ? 1 : x * factorial(x - 1);
    return func.apply(i);
}
1
Dmytro Chopenko

ラムダの「this」が包含クラスを参照しているという事実を考えると、次のコードはエラーなしでコンパイルされます(もちろん依存関係が追加されています)。

public class MyClass {
    Function<Map, CustomStruct> sourceToStruct = source -> {
        CustomStruct result;
        Object value;

        for (String key : source.keySet()) {
            value = source.get(key);

            if (value instanceof Map) {
                value = this.sourceToStruct.apply((Map) value);
            }

            result.setValue(key, value);
        }

        return result;
    };
}
1
Rebel Geek

@IanRobertsonうまくできました。実際、静的な「ファクトリー」をインターフェイス自体の本体に移動して、完全にカプセル化できます。

public static interface Recursable<T, U> {
        U apply(T t, Recursable<T, U> r);

        public static <T, U> Function<T, U> recurseable(Recursable<T, U> f) {
            return t -> f.apply(t, f);
        }
}

これは私が今まで見た中で最もクリーンな解決策/答えです...特に「ファクト」の呼び出しは「自然に」書かれているので:fac.apply(n)これは、fac( )

1
Larry Cable

問題は、ラムダ関数がfinal変数を操作したいのに対し、ラムダで置き換えることができる可変のFunction- referenceが必要なことです。

最も簡単なトリックは、変数をメンバー変数として定義することであり、コンパイラーは文句を言いません。

ここでIntUnaryOperatorを操作しているだけなので、IntToDoubleFunctionの代わりにIntegersを使用するように例を変更しました。

import org.junit.Test;
import Java.util.function.IntUnaryOperator;
import static org.junit.Assert.assertEquals;

public class RecursiveTest {
    private IntUnaryOperator operator;

    @Test
    public void factorialOfFive(){
        IntUnaryOperator factorial = factorial();
        assertEquals(factorial.applyAsInt(5), 120); // passes
    }

    public IntUnaryOperator factorial() {
        return operator = x -> (x == 0) ? 1 : x * operator.applyAsInt(x - 1);
    }
}
0
tomaj
public class LambdaExperiments {

  @FunctionalInterface
  public interface RFunction<T, R> extends Function<T, R> {
    R recursiveCall(Function<? super T, ? extends R> func, T in);

    default R apply(T in) {
      return recursiveCall(this, in);
    }
  }

  @FunctionalInterface
  public interface RConsumer<T> extends Consumer<T> {
    void recursiveCall(Consumer<? super T> func, T in);

    default void accept(T in) {
      recursiveCall(this, in);
    }
  }

  @FunctionalInterface
  public interface RBiConsumer<T, U> extends BiConsumer<T, U> {
    void recursiveCall(BiConsumer<T, U> func, T t, U u);

    default void accept(T t, U u) {
      recursiveCall(this, t, u);
    }
  }

  public static void main(String[] args) {
    RFunction<Integer, Integer> fibo = (f, x) -> x > 1 ? f.apply(x - 1) + f.apply(x - 2) : x;

    RConsumer<Integer> decreasingPrint = (f, x) -> {
      System.out.println(x);
      if (x > 0) f.accept(x - 1);
    };

    System.out.println("Fibonnaci(15):" + fibo.apply(15));

    decreasingPrint.accept(5);
  }
}

私のテスト中、これはローカル再帰ラムダで達成できる最高の方法です。ストリームでも使用できますが、ターゲットのタイピングの容易さを失います。

0

ここで答えの共通のテーマを取り上げることは、ラムダが再帰的であり、固定された参照点を持っていることです(したがって、クラス/インターフェイスベースの答えは @ assylias@ Andrey Morozov@ Ian Robertson など)。

メンバー変数の回避策で @ 0000000000000000000 からの答えが本当に好きでしたが、目的のラムダ関数が包含関数のスコープから他の変数を参照したいかどうか心配です。割り当て時にローカル参照を評価し、結果の関数をメンバー変数に入れて、クラス内の他のメソッドからアクセスできるようにします。それは聞こえません...right(そして、含むメソッド自体が再帰的に呼び出された場合、非常に興味深いものになるでしょう)。

以下は、OPの元の1行ラムダに近い形式で表現されたクラスベースのソリューションのバリエーションですが、Eclipseは文句を言いません。

IntToDoubleFunction fact = new IntToDoubleFunction() {
    @Override
    public double applyAsDouble(int x) {
        return x == 0 ? 1 : x * this.applyAsDouble(x-1);
    }
};

{}は、もちろん匿名クラスを作成し、ラムダの評価のための参照ポイントを持つ新しいスコープを作成します。さらに、関数を含むスコープと「兄弟」変数を含むという利点があります。

0
A Paul Anthony

フィボナッチを可能なユースケースとして使用したラムダに関する講義中に、この質問に出くわしました。

次のような再帰的なラムダを作成できます。

import Java.util.function.Function;

public class Fib {

   static Function<Integer, Integer> fib;

   public static void main(String[] args) {
       fib = (n) -> { return n > 1 ? fib.apply(n-1) + fib.apply(n-2) : n; };

       for(int i = 0; i < 10; i++){
           System.out.println("fib(" + i + ") = " + fib.apply(i));
       }
   }
}

何を心に留めておく必要がありますか?

  • ラムダは実行時に評価されます->再帰的かもしれません

  • 別のラムダ内でラムダ変数を使用するには、変数を初期化する必要があります->再帰ラムダを定義する前に、foo-valueで定義する必要があります

  • ラムダ内でローカルのラムダ変数を使用するには、変数が最終である必要があるため、再定義できません-> ラムダにクラス/オブジェクト変数を使用デフォルト値で初期化されるため

このクラスを使用して、再帰関数を作成できます。

public class Recursive<I> {
    private Recursive() {

    }
    private I i;
    public static <I> I of(Function<RecursiveSupplier<I>, I> f) {
        Recursive<I> rec = new Recursive<>();
        RecursiveSupplier<I> sup = new RecursiveSupplier<>();
        rec.i = f.apply(sup);
        sup.i = rec.i;
        return rec.i;
    }
    public static class RecursiveSupplier<I> {
        private I i;
        public I call() {
            return i;
        }
    }
}

そして、ラムダと次のような機能インターフェイスの定義を使用して、1行だけで任意の機能インターフェイスを使用できます。

Function<Integer, Integer> factorial = Recursive.of(recursive ->
        x -> x == 0 ? 1 : x * recursive.call().apply(x - 1));
System.out.println(factorial.apply(5));

非常に直感的で使いやすいことがわかりました。

0
Jose Da Silva

副作用に依存しないソリューションを次に示します。目的を面白くするために、再帰を抽象化したいとしましょう(そうでなければ、インスタンスフィールドソリューションは完全に有効です)。秘Theは、匿名クラスを使用して「this」参照を取得することです。

public static IntToLongFunction reduce(int zeroCase, LongBinaryOperator reduce) {
  return new Object() {
    IntToLongFunction f = x -> x == 0
                               ? zeroCase
                               : reduce.applyAsLong(x, this.f.applyAsLong(x - 1));
  }.f;
}

public static void main(String[] args) {
  IntToLongFunction fact = reduce(1, (a, b) -> a * b);
  IntToLongFunction sum = reduce(0, (a, b) -> a + b);
  System.out.println(fact.applyAsLong(5)); // 120
  System.out.println(sum.applyAsLong(5)); // 15
}
0
JbGi