web-dev-qa-db-fra.com

Implémenter une fonction lambda récursive avec Java 8

Java 8 a introduit les fonctions lambda et je souhaite implémenter quelque chose comme factorial:

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

Retours de compilation

  error: variable fact might not have been initialized

Comment puis-je référencer la fonction elle-même. La classe est anonyme mais une instance existe: Elle s'appelle fact.

47
user2678835

J'utilise généralement une classe d'assistance générique (une fois pour toutes les interfaces fonctionnelles définies) qui englobe la variable du type d'interface fonctionnelle . Cette approche résout le problème de l'initialisation de la variable locale et permet au code de paraître plus clairement.

Dans le cas de cette question, le code ressemblera à ceci:

// 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);
}
38
Andrey Morozov

Une solution consiste à écrire une fonction secondaire, helper, qui prend comme argument une fonction et un nombre, puis à écrire la fonction que vous souhaitez réellement, fact = helper(helper,x).

Ainsi:

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

Cela me semble un peu plus élégant que de s’appuyer sur la sémantique des majuscules, comme une fermeture qui capture une référence à une structure mutable, ou de permettre une référence automatique avec l’avertissement de la possibilité de "ne pas être initialisé".

Néanmoins, ce n'est pas une solution parfaite à cause du système de types de Java - les génériques ne peuvent pas garantir que f, l'argument de factHelper, est du même type que factHelper (c.-à-d. Mêmes types d'entrée et types de sortie), car ce serait un imbriqué infiniment générique.

Ainsi, au lieu de cela, une solution plus sûre pourrait être:

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

L'odeur de code engendrée par le type générique moins que parfait de factHelper est maintenant contenue (ou, si j'ose dire, encapsulée) dans le lambda, garantissant que factHelper ne sera jamais appelé sans le savoir.

16
rationalis

Les classes locales et anonymes, ainsi que les lambdas, capturent les variables locales par valeur lors de leur création. Par conséquent, il leur est impossible de se référer à eux-mêmes en capturant une variable locale, car la valeur permettant de se désigner eux-mêmes n'existe pas encore au moment de leur création.

Le code dans les classes locales et anonymes peut toujours se référer à lui-même en utilisant this. Cependant, this dans un lambda ne fait pas référence au lambda; il fait référence à this à partir de la portée extérieure.

Vous pouvez capturer une structure de données modifiable, comme un tableau, à la place:

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

bien qu’à peine une solution élégante.

13
newacct

Si vous vous trouvez souvent obligé de faire ce genre de chose, vous pouvez également créer une interface et une méthode d'assistance:

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

Et puis écris:

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

(Bien que je l’ai fait de manière générique avec les types de référence, vous pouvez également créer des versions spécifiques aux primitives).

Ceci emprunte à un vieux truc du Petit Lisper pour créer des fonctions non nommées.

Je ne suis pas sûr de l'avoir jamais fait dans le code de production, mais c'est intéressant ...

5
Ian Robertson

Vous pouvez définir un lambda récursif en tant qu'instance ou variable de classe:

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

par exemple:

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

imprime 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

Un peu comme la toute première réponse ... 

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.

Une autre version utilisant accumulateur pour optimiser la récursivité . Déplacé vers la définition d'interface générique.

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

Ce qui suit fonctionne, mais cela semble mystérieux. 

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

Une solution consiste à définir cette fonction en tant qu'attribut 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

Vous pouvez également le définir en tant que variable locale en créant un tableau final de taille un (de type Function []), puis en affectant la fonction à l'élément 0. Indiquez-moi si vous avez besoin de la syntaxe exacte.

1
Victor Grazi

Je suis tombé sur cette question lors d'une conférence sur Lambdas qui utilisait Fibonacci comme cas d'utilisation possible.

Vous pouvez faire un lambda récursif comme ceci:

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

Qu'est-ce que vous devez garder à l'esprit?

  • Les lambda sont évalués à l'exécution -> ils peuvent être récursifs

  • Utiliser une variable lambda dans un autre lambda nécessite que la variable Soit initialisée -> avant de définir un lambda récursif, vous devez le définir avec une valeur foo

  • utiliser une variable lambda locale dans un lambda nécessite que la variable soit finale, elle ne peut donc pas être redéfinie -> utilise une classe/objet variable pour le lambda car il est initialisé avec une valeur par défaut

1

J'ai entendu au JAX cette année que "les lambads ne supportent pas la récursion". Ce que l'on entend par cette déclaration est que le "ceci" à l'intérieur du lambda se réfère toujours à la classe environnante.

Mais j’ai réussi à définir - au moins comment je comprends le terme "récursion" - un lambda récursif et cela se passe comme ça:

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

Enregistrez cela dans un fichier "Recursion.Java" et avec les deux commandes "javac Recursion.Java" et "Java Recursion", cela a fonctionné pour moi.

Le clou consiste à conserver l'interface que le lambda doit implémenter en tant que variable de champ dans la classe environnante. Le lambda peut faire référence à ce champ et le champ ne sera pas implicitement définitif.

1
beck

La réponse est: vous devez utiliser une variable this before name appelant la fonction applyAsDouble: -

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

si vous rendez le fait final, cela fonctionnera aussi

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

Nous pouvons utiliser l'interface fonctionnelle UnaryOperator ici. Un opérateur unaire qui retourne toujours son argument d'entrée.

1) Ajoutez simplement this . avant le nom de la fonction, comme dans:

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

Cela évitera "Impossible de référencer un champ avant qu’il soit défini" .

2) Si vous préférez un champ static , remplacez-le simplement par le nom de la classe:

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

Etant donné que "this" dans le lambda fait référence à la classe qui le contient, les éléments suivants sont compilés sans erreur (avec des dépendances ajoutées bien sûr):

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 Bien fait, vous pouvez en fait déplacer la 'fabrique' statique dans le corps même de l'interface, l'encapsulant ainsi entièrement:

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

C’est la solution/réponse la plus propre que j’ai vue jusqu’à présent ... d’autant plus que l’invocation de "fait" s’écrit "naturellement": fac.apply (n), ce que vous vous attendriez à voir pour une fonction unaire comme fac )

1
Larry Cable

Le problème, c'est que les fonctions lambda veulent opérer sur les variables final, alors que nous avons besoin d'une référence Function- mutable pouvant être remplacée par notre lambda.

L'astuce la plus simple semble être de définir la variable en tant que variable membre et le compilateur ne s'en plaindra pas.

J'ai changé mon exemple pour utiliser IntUnaryOperator au lieu de IntToDoubleFunction, car nous n'utilisons que Integers de toute façon ici.

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

Pendant mes tests, c’est le meilleur choix que je puisse faire pour les lambdas récursifs locaux . Ils peuvent également être utilisés dans les flux, mais nous perdons la facilité de frappe.

0
leonel dossantos

Vous pouvez créer une fonction récursive en utilisant cette classe:

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

Et ensuite, vous pouvez utiliser n’importe quelle interface fonctionnelle en une seule ligne avec un lambda et définir votre interface fonctionnelle comme suit:

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

Je l'ai trouvé très intuitif et facile à utiliser.

0
Jose Da Silva

Voici une solution qui ne repose pas sur un effet secondaire. Pour rendre le but intéressant, supposons que vous vouliez faire un résumé sur la récursivité (sinon, la solution du champ d'instance est parfaitement valide) . L'astuce consiste à utiliser une classe anonyme pour obtenir la référence '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

Une autre factorielle récursive avec Java 8

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