67

Java 8 ではラムダ関数が導入され、階乗のようなものを実装したいと考えています。

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

コンパイルが返されます

  error: variable fact might not have been initialized

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

4

25 に答える 25

51

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

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

// 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);
}
于 2014-08-24T16:35:10.150 に答える
21

1 つの方法はhelper、関数と数値を引数として受け取る 2 次関数 を作成してから、実際に必要な関数 を作成することです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、 への引数がfactHelperと同じ型factHelper(つまり、同じ入力型と出力型) であることを保証できません。これは、ジェネリックが無限にネストされるためです。

したがって、代わりに、より安全な解決策は次のようになります。

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知らないうちに が呼び出されることはありません。

于 2014-04-09T05:11:25.683 に答える
14

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

ローカルおよび匿名クラスのコードは、 を使用して自身を参照できますthis。ただし、thisラムダではラムダを参照しません。それはthis外側のスコープから を参照します。

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

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

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

于 2013-10-18T09:18:26.827 に答える
10

この種のことを頻繁に行う必要がある場合は、別のオプションとして、ヘルパー インターフェイスとメソッドを作成します。

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));

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

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

プロダクションコードでこれを行うかどうかはわかりませんが、興味深いです...

于 2016-03-14T20:14:37.350 に答える
2

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));
    }
}
于 2013-10-17T14:38:31.993 に答える
2
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);
  }
}

私のテスト中、これはローカル再帰ラムダに対して達成できる最高のものです。それらはストリームでも使用できますが、ターゲットの入力の容易さは失われます。

于 2016-06-10T09:02:02.313 に答える
2
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));
    }
}
于 2014-04-03T21:09:12.063 に答える
2

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

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));
}
}
于 2013-10-17T14:58:49.283 に答える
2

以下は機能しますが、難解に見えます。

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
于 2014-04-22T14:47:41.833 に答える
2

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

public static Function<Integer,Double> factorial;

static {
    factorial = n -> {
        assert n >= 0;
        return (n == 0) ? 1.0 : n * factorial.apply(n - 1);
    };
}
于 2014-06-01T12:54:33.130 に答える
1

今年の JAX で、「ラムバッドは再帰をサポートしていない」と聞きました。このステートメントが意味することは、ラムダ内の「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」に保存し、「javac Recursion.java」と「java Recursion」の2つのコマンドを使用するとうまくいきました。

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

于 2014-05-16T20:19:48.590 に答える
1

このように一般的な固定小数点コンビネータを定義できます。

public static <T, R> Function<T, R> fixedPointCombinator(Function<Function<T, R>, Function<T, R>> f) {
    return new Function<T, R>() {
        @Override
        public R apply(T n) {
            return f.apply(this).apply(n);
        }
    };
}

Function<Function<Integer, Double>, Function<Integer, Double>> fact =
    self -> n -> n == 0 ? 1 : n * self.apply(n - 1);

System.out.println(fixedPointCombinator(fact).apply(10));

出力:

3628800.0
于 2020-08-04T00:27:01.130 に答える
0

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

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

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

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);
    }
}
于 2015-09-08T09:54:39.543 に答える