【问题标题】:Implement recursive lambda function using Java 8使用 Java 8 实现递归 lambda 函数
【发布时间】:2013-10-26 02:48:30
【问题描述】:

Java 8 引入了 lambda 函数,我想实现类似阶乘的东西:

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

编译返回

  error: variable fact might not have been initialized

如何引用函数本身。类是匿名的,但实例存在:它被称为fact

【问题讨论】:

    标签: java recursion lambda java-8


    【解决方案1】:

    以下方法可行,但看起来确实很神秘。

    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】:

      您可以像这样定义通用Fixed-point combinator

      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
      

      【讨论】:

      • 谢谢,我讨厌它。
      • @EthanMcCue,更新了我的答案。
      【解决方案3】:

      您也可以自己定义接口,在调用期间将其本身作为参数传递。例如

      interface MyOwnFunction<T,R>{
          R apply(MyOwnFunction<T,R> self,T arg);
      }
      

      【讨论】:

        【解决方案4】:

        这里回答的共同主题是 lambda 可以递归,只要它们具有固定的参考点(因此基于类/接口的答案,例如 @assylias@Andrey Morozov@Ian Robertson 等) .

        我真的很喜欢@000000000000000000000 的成员变量解决方法的答案,但我担心预期的 lambda 函数是否想要引用包含函数范围内的其他变量。当然,它会在赋值时评估这些局部引用,并将结果函数放入一个成员变量中,类中的其他方法可以访问它。这听起来不... 正确(如果包含方法本身被递归调用,可能会变得非常有趣)。

        以下是基于类的解决方案的变体,其表达形式接近于 OP 的原始单行 lambda,但 Eclipse 不会抱怨。

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

        { } 当然会创建一个匿名类,从而创建一个新的范围,其中包含用于 lambda 评估的参考点,其额外好处是仍然在包含函数自己的范围内,因此是“兄弟”变量。

        【讨论】:

          【解决方案5】:

          答案是:您必须在名称变量调用 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);
          

          我们可以在这里使用函数式接口UnaryOperator。始终返回其输入参数的一元运算符。

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

          【讨论】:

            【解决方案6】:

            @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()

            【讨论】:

              【解决方案7】:

              在关于 Lambda 的讲座中遇到了这个问题,该讲座使用斐波那契作为可能的用例。

              你可以像这样创建一个递归 lambda:

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

              你有什么要记住的?

              • Lambda 在执行时被评估 -> 它们可能是递归的

              • 在另一个 lambda 中使用 lambda 变量需要 要初始化的变量 -> 在定义递归 lambda 之前 必须用 foo 值定义它

              • 在 lambda 中使用局部 lambda 变量需要该变量 是最终的,因此不能重新定义 -> 使用类/对象 lambda的变量,因为它使用默认值初始化

              【讨论】:

                【解决方案8】:

                Java 8 的另一个递归阶乘

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

                【讨论】:

                • 不错的解决方案,但我不明白为什么如果你使用方法,为什么不使用没有 lambdas 的递归方法。
                【解决方案9】:

                您可以使用此类创建递归函数:

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

                然后,您可以使用 lambda 和函数式接口的定义在 1 行中使用任何函数式接口,如下所示:

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

                我发现它非常直观且易于使用。

                【讨论】:

                  【解决方案10】:
                  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);
                    }
                  }
                  

                  在我的测试中,这是本地递归 lambda 所能达到的最佳效果。 它们也可以在流中使用,但我们失去了目标输入的便利性。

                  【讨论】:

                    【解决方案11】:

                    如果你发现自己经常需要做这种事情,另一种选择是创建一个帮助接口和方法:

                    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 中的一个老技巧,用于制作未命名的函数。

                    我不确定我是否会在生产代码中这样做,但这很有趣...

                    【讨论】:

                    • 作为参考,这种方法在函数式编程界通常被称为“定点组合器”,并且被广泛使用。
                    • 扩展Function&lt;T, U&gt; 并指定一个默认的apply 来启动递归可能会更好。
                    【解决方案12】:

                    我手边没有 Java8 编译器,所以无法测试我的答案。但是,如果您将“事实”变量定义为最终变量,它会起作用吗?

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

                    【讨论】:

                    • 它没有。也许在 Java 9 中,但不是在 Java 8 中。
                    • @ClaudeMartin 在 java >8 中不可能。问题是变量没有初始化,不是它可以变异。
                    【解决方案13】:

                    鉴于 lambda 中的“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;
                        };
                    }
                    

                    【讨论】:

                      【解决方案14】:

                      这是一个不依赖副作用的解决方案。为了使目的变得有趣,假设您想对递归进行抽象(否则实例字段解决方案完全有效)。 诀窍是使用匿名类来获取“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
                      }
                      

                      【讨论】:

                      • 这很酷,因为内部函数甚至不必是 la​​mbda 字段。它可以是您可以使用::f 访问的方法
                      【解决方案15】:

                      问题在于 lambda 函数想要对 final 变量进行操作,而我们需要一个可变的 Function-reference 可以替换为我们的 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);
                          }
                      }
                      

                      【讨论】:

                        【解决方案16】:

                        我通常使用(一次性定义所有功能接口)通用帮助类,它包装了功能接口类型的变量。 这种方式解决了局部变量初始化的问题,让代码看起来更清晰。

                        如果出现这个问题,代码将如下所示:

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

                        【讨论】:

                        • 我创建了一个实用程序类,这样您就可以传递一个递归闭包并获得一个预定义的函数类型:github.com/claudemartin/Recursive 您总是有一个名为“self”的附加参数来执行递归。有些甚至有一个带有缓存的版本,Demo.class 展示了如何获得一个相当快的斐波那契函数版本。
                        【解决方案17】:

                        您还可以通过创建一个大小为 1 的最终数组(例如 Function[])将其定义为局部变量,然后将该函数分配给元素 0。如果您需要确切的语法,请告诉我

                        【讨论】:

                          【解决方案18】:

                          有点像第一个回复...

                          public static Function<Integer,Double> factorial;
                          
                          static {
                              factorial = n -> {
                                  assert n >= 0;
                                  return (n == 0) ? 1.0 : n * factorial.apply(n - 1);
                              };
                          }
                          

                          【讨论】:

                          • 所有的回复基本一致。但是为什么 Java 要求您将 Function 声明为实例或类变量?为什么它不允许你在你的方法中声明它???
                          【解决方案19】:

                          我在今年的 JAX 上听说“lambds 不支持递归”。这句话的意思是 lambda 中的“this”总是指周围的类。

                          但我设法定义了——至少我如何理解“递归”这个术语——一个递归 lambda,它是这样的:

                          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 是将 lambda 必须实现的接口作为字段变量保留在周围类中。 lambda 可以引用该字段,并且该字段不会是隐式 final。

                          【讨论】:

                            【解决方案20】:

                            一种方法是编写一个辅助函数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 不完美的泛型类型产生的代码异味现在包含(或者,我敢说,封装)在 lambda 中,确保永远不会在不知情的情况下调用 factHelper

                            【讨论】:

                              【解决方案21】:
                              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));
                                  }
                              }
                              

                              【讨论】:

                                【解决方案22】:

                                您可以将递归 lambda 定义为实例或类变量:

                                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

                                【讨论】:

                                • 对。局部变量的递归特性已被删除。请参阅此电子邮件主题:mail.openjdk.java.net/pipermail/lambda-dev/2013-September/…
                                • @StuartMarks 但它仍然适用于实例/类变量,对吧?
                                • 在简单的情况下,是的,但是您会收到关于 factorial 可能未初始化的警告。我不相信这个示例中实际上存在问题,因为在初始化之前无法调用 lambda,但我确信有人可以提出一个足够复杂的示例,最终观察到处于未初始化状态的字段。在某些时候,使用命名方法和方法引用似乎更可取。在这里查看我的答案:stackoverflow.com/a/21652054/1441122
                                • “在定义之前不能引用一个字段”?
                                【解决方案23】:

                                本地和匿名类以及 lambda 在创建时按值捕获局部变量。因此,它们不可能通过捕获局部变量来引用自己,因为在创建它们时,指向它们自己的值还不存在。

                                本地和匿名类中的代码仍然可以使用this 引用自己。但是,lambda 中的 this 并不是指 lambda;它从外部范围引用this

                                您可以捕获可变数据结构,例如数组:

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

                                虽然不是一个优雅的解决方案。

                                【讨论】:

                                  【解决方案24】:

                                  另一个使用累加器的版本可以优化递归。 移至通用接口定义。

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

                                  【讨论】:

                                    【解决方案25】:

                                    一种解决方案是将此函数定义为一个实例属性。

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

                                    【讨论】:

                                    猜你喜欢
                                    • 2018-01-17
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2015-12-17
                                    • 1970-01-01
                                    • 1970-01-01
                                    • 2010-11-07
                                    • 1970-01-01
                                    相关资源
                                    最近更新 更多