it-swarm.com.ru

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

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

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

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

  error: variable fact might not have been initialized

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

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

Один из способов - написать вторичную функцию 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 никогда не будет вызываться по незнанию.

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

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

Это заимствовано из старого трюка в «Маленьком Лиспере» для создания неназванных функций.

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

5
Ian Robertson

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

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

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

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

1
Victor Grazi

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

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

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-значения

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

1
000000000000000000000

Я слышал на JAX в этом году, что «лямбады не поддерживают рекурсию». Под этим утверждением подразумевается то, что «это» внутри лямбды всегда относится к окружающему классу.

Но мне удалось определить - по крайней мере, как я понимаю термин «рекурсия» - рекурсивную лямбду, и это выглядит так:

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 должен сохранить интерфейс, который лямбда должен реализовать как переменную поля в окружающем классе. Лямбда может ссылаться на это поле, и поле не будет неявно окончательным.

1
beck

Ответ: Вы должны использовать 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) Если вы предпочитаете static field, просто замените его именем класса:

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

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

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

1
Larry Cable

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

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

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

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

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

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

Еще один рекурсивный факториал с 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