Рекурсивная лямбда-функция с использованием Java 8

Java 8 представила лямбда-функции, и я хочу реализовать что-то вроде factorial:

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

Компиляция возвращает

  error: variable fact might not have been initialized

Как я могу ссылаться на функцию. Класс анонимный, но экземпляр существует: он называется fact.

Ответ 1

Обычно я использую (один раз для всех функциональных интерфейсов) общий вспомогательный класс, который обертывает переменную типа функционального интерфейса. Этот подход решает проблему с локальной инициализацией переменных и позволяет коду выглядеть более четко.

В случае возникновения этого вопроса код будет выглядеть следующим образом:

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

Ответ 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, аргумент 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 никогда не будет вызван неосознанно.

Ответ 3

Локальные и анонимные классы, а также lambdas, фиксируют локальные переменные по значению при их создании. Следовательно, им невозможно ссылаться на себя путем захвата локальной переменной, потому что значение для указания на себя не существует еще в момент их создания.

Код в локальных и анонимных классах может по-прежнему ссылаться на себя с помощью this. Однако this в лямбда не относится к лямбда; он ссылается на this из внешней области.

Вместо этого вы можете захватить изменяемую структуру данных, например массив:

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

хотя вряд ли это изящное решение.

Ответ 4

Если вам нужно часто делать такие вещи, другой вариант заключается в создании вспомогательного интерфейса и метода:

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

(Хотя я сделал это в общем случае с ссылочными типами, вы также можете создавать примитивные версии).

Это заимствует из старого трюка в Little Lisper для создания неназванных функций.

Я не уверен, что когда-либо сделаю это в производственном коде, но это интересно...

Ответ 5

Ответ: Вы должны использовать переменную this перед именем, вызывающую функцию applyAsDouble: -

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

Мы можем использовать функциональный интерфейс UnaryOperator здесь. Унарный оператор, который всегда возвращает свой входной аргумент.

1) Просто добавьте это. перед именем функции, как в:

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

Это поможет избежать "Невозможно сослаться на поле, пока оно не определено".

2) Если вы предпочитаете статическое поле, просто замените " this " именем класса:

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

Ответ 6

Одним из решений является определение этой функции как атрибута 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));
    }
}

Ответ 7

Другая версия с использованием аккумулятора, так что рекурсия может быть оптимизирована. Переведено в определение общего интерфейса.

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

Ответ 8

Вы можете определить рекурсивную лямбду в качестве переменной экземпляра или класса:

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.

Ответ 9

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

Ответ 10

Следующие действия, но это кажется тайным.

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

Ответ 11

Немного похоже на самый первый ответ...

public static Function<Integer,Double> factorial;

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

Ответ 12

Я слышал в 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" и с двумя командами "javac Recursion.java" и "java Recursion" это сработало для меня.

Clou должен поддерживать интерфейс, который лямбда должна реализовать как переменная поля в окружающем классе. Лямбда может ссылаться на это поле, и поле не будет неявно окончательным.

Ответ 13

Вы также можете определить его как локальную переменную, создав последний массив размера один (например, функцию []), а затем назначьте функцию элементу 0. Сообщите мне, если вам нужен точный синтаксис

Ответ 14

Учитывая тот факт, что "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;
    };
}

Ответ 15

Другой рекурсивный факториал с Java 8

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

Ответ 16

Пришел к этому вопросу во время лекции по Лямбдасу, которая использовала Фибоначчи в качестве возможного варианта использования.

Вы можете сделать рекурсивную лямбду следующим образом:

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

Что вы должны иметь в виду?

  • Lambdas оцениваются при выполнении → они могут быть рекурсивными

  • Использование лямбда-переменной внутри другой лямбды требует переменная для инициализации → перед определением рекурсивной лямбды вас должен определить его с помощью foo-value

  • с использованием локальной лямбда-переменной внутри лямбды требуется переменная быть окончательным, поэтому его нельзя переопределить → использовать класс/объект переменная для лямбда, поскольку она инициализируется значением по умолчанию

Ответ 17

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

Это самое чистое решение/ответ, который я видел до сих пор... тем более, что вызов "fact" написан "естественно": fac.apply(n), что вы и ожидаете увидеть для унарной функции, такой как fac ( )

Ответ 18

Проблема заключается в том, что лямбда-функции хотят работать с переменными final, тогда как нам нужна изменчивая Function -референция, которая может быть заменена нашей lambda.

Самый простой трюк, по-видимому, состоит в том, чтобы определить переменную как переменную-член, а компилятор не будет жаловаться.

Я изменил свой пример, чтобы использовать 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);
    }
}

Ответ 19

Вот решение, которое не полагается на побочный эффект. Чтобы сделать цель интересной, скажем, что вы хотите абстрагироваться от рекурсии (в противном случае решение поля экземпляра вполне допустимо). Хитрость заключается в использовании анонимного класса для получения ссылки '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
}

Ответ 20

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

Во время моих тестов это лучшее, что я мог бы достичь для локальных рекурсивных лямбдов. Они также могут использоваться в потоках, но мы теряем легкость ввода цели.

Ответ 21

Вы можете создать рекурсивную функцию, используя этот класс:

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

Я нашел его очень интуитивно понятным и простым в использовании.

Ответ 22

Подходя к общей теме ответов здесь, можно сказать, что лямбды МОГУТ быть рекурсивными, при условии, что они имеют фиксированную контрольную точку (отсюда и ответы на основе классов/интерфейсов, такие как @assylias, @Andrey Morozov, @Ian Robertson и т.д.).

Мне действительно понравился ответ от @000000000000000000000 с обходом переменной-члена, но у меня есть опасения, если предполагаемая лямбда-функция хочет ссылаться на другие переменные из области действия содержащей ее функции. Конечно, он будет оценивать эти локальные ссылки при присваивании и помещать полученную функцию в переменную-член, где к ней могли бы обращаться другие методы в классе. Это не звучит... правильно (и может стать довольно интересным, если сам содержащий метод вызывается рекурсивно).

Ниже представлен вариант решений на основе классов, выраженный в форме, близкой к оригинальной однострочной лямбде OP, но на Eclipse не жалуются.

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

{}, Конечно, создает анонимный класс и, таким образом, новую область видимости с контрольными точками для лямбда-оценки с дополнительными преимуществами, заключающимися в том, что они находятся внутри собственной области действия функции и, таким образом, "родственных" переменных.

Ответ 23

У меня нет компилятора Java8, поэтому я не могу проверить свой ответ. Но будет ли это работать, если вы определили переменную "факт" как окончательную?

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