Home > Software design >  How can I define mutually recursive lambda functions in Java?
How can I define mutually recursive lambda functions in Java?

Time:12-12

I'm trying to implement L-system as functions. For example, dragon curve looks like this:

static String F(int n) {
    return n == 0 ? "F" : F(n - 1)   " "   G(n -1);
}

static String G(int n) {
    return n == 0 ? "G" : F(n - 1)   "-"   G(n -1);
}

@Test
void testDragonCurveAsStaticFunction() {
    assertEquals("F", F(0));
    assertEquals("F G", F(1));
    assertEquals("F G F-G", F(2));
    assertEquals("F G F-G F G-F-G", F(3));
}

I want to implement this with lambda functions. I got the following implementation by referring to recursion - Implement recursive lambda function using Java 8 - Stack Overflow.

@Test
void testDragonCurveAsLambdaFunction() {
    interface IntStr { String apply(int i); }
    IntStr[] f = new IntStr[2];
    f[0] = n -> n == 0 ? "F" : f[0].apply(n - 1)   " "   f[1].apply(n - 1);
    f[1] = n -> n == 0 ? "G" : f[0].apply(n - 1)   "-"   f[1].apply(n - 1);
    assertEquals("F", f[0].apply(0));
    assertEquals("F G", f[0].apply(1));
    assertEquals("F G F-G", f[0].apply(2));
    assertEquals("F G F-G F G-F-G", f[0].apply(3));
}

Is there a way to implement this without using an array?

But I want to create a generic L-System, so I don't want to define a new class, interface or method for the dragon curve.

CodePudding user response:

I tried implementing it using a class combining two IntStr fields set via constructor or setters:

interface IntStr { 
    String apply(Integer i); 
}
    
class Recursive {
    IntStr other;
    IntStr curr;

    public Recursive() {}
    
    public Recursive(String p1, String s1, String p2, String s2) {
        this.curr  = n -> n == 0 ? p1 : curr.apply(n - 1)   s1   other.apply(n - 1);
        this.other = n -> n == 0 ? p2 : curr.apply(n - 1)   s2   other.apply(n - 1);
    }
    
    public void setCurr(String p, String s) {
        this.curr  = n -> n == 0 ? p : curr.apply(n - 1)   s   other.apply(n - 1);
    }
   
    public void setOther(String p, String s) {
        this.other = n -> n == 0 ? p : curr.apply(n - 1)   s   other.apply(n - 1);
    }
}

Then the following code succeeded:

void testDragonCurveAsLambdaFunction() {
    Recursive f1 = new Recursive("F", " ", "G", "-");
// or using setters
//  f1.setCurr("F", " "); 
//  f1.setOther("G", "-");
    assertEquals("F", f1.curr.apply(0));
    assertEquals("F G", f1.curr.apply(1));
    assertEquals("F G F-G", f1.curr.apply(2));
    assertEquals("F G F-G F G-F-G", f1.curr.apply(3));
}

An example using AtomicReference as a container for IntStr reference without creating Recursive class:

void testDragonCurveAsLambdaFunction() {
    AtomicReference<IntStr> 
        curr  = new AtomicReference<>(), 
        other = new AtomicReference<>(); 
    
    curr.set(n -> n == 0 ? "F" : curr.get().apply(n - 1)   " "   other.get().apply(n - 1));
    other.set(n -> n == 0 ? "G" : curr.get().apply(n - 1)   "-"   other.get().apply(n - 1));

    assertEquals("F", curr.get().apply(0));
    assertEquals("F G", curr.get().apply(1));
    assertEquals("F G F-G", curr.get().apply(2));
    assertEquals("F G F-G F G-F-G", curr.get().apply(3));
}

CodePudding user response:

I found a solution that uses Map.

interface IntStr {

    String apply(int n);

    static IntStr cond(String then, IntStr otherwise) {
        return n -> n == 0 ? then : otherwise.apply(n - 1);
    }

    static IntStr constant(String string) {
        return n -> string;
    }

    static IntStr call(Map<String, IntStr> map, String functionName) {
        return n -> map.get(functionName).apply(n);
    }

    static IntStr concat(IntStr... functions) {
        return n -> Arrays.stream(functions)
            .map(f -> f.apply(n))
            .collect(Collectors.joining(""));
    }
}

and

@Test
void testDragonCurveAsLambda() {
    Map<String, IntStr> map = new HashMap<>();
    map.put("F", IntStr.cond("F", IntStr.concat(
        IntStr.call(map, "F"),
        IntStr.constant(" "),
        IntStr.call(map, "G"))));
    map.put("G", IntStr.cond("G", IntStr.concat(
        IntStr.call(map, "F"),
        IntStr.constant("-"),
        IntStr.call(map, "G"))));
    IntStr f = map.get("F");
    assertEquals("F", f.apply(0));
    assertEquals("F G", f.apply(1));
    assertEquals("F G F-G", f.apply(2));
    assertEquals("F G F-G F G-F-G", f.apply(3));
}
  • Related