KATA - Functional Programing in Java?

Updated: Nov 29, 2021

Java and OO go together like Java and Cream. But can you really be functional in Java?

Java at its core is and always will be a OO language, this is why I think functional purist favor languages like F#, Scala, and/or Haskell. I'm not a functional pro, I just came off a short F# project but I feel in love with types and the really complex things that could be done with just a few lines of code. Clean, easy to understand, east to read code is something I'm really into. But very little of the world used F#, most of my contracts are in Java. Can you teach an old dog new tricks, the old dog in this case, is me. Let's find out! You can follow along with my code at: https://github.com/dcrackel/ReplaceEveryNth

The Kata

I did a search for Java Kata, and the 1st one I find I'll try to write in a functional way:

replaceNth(text, n, oldValue, newValue);
       n:         2
       oldValue: 'a'
       newValue: 'o'
       
"Vader said: No, I am your father!" -> "Vader soid: No, I am your fother!"
1     2          3        4       -> 2nd and 4th occurence are replaced

Seems pretty easy right.... yeah.... I thought so too. I've done a dozen little challenges like this in interviews. I'm sure you know enough to know, this wouldn't be a blog if it was easy. Sometimes the biggest challenges live in the most unassuming and innocent of places. Maniacal laughter I'm getting ahead.

Testing the waters


Let's start with a test, you do always start with a test? Of course you do!


@Test
public void replaceEveryNth() {
    String input1 = "Vader said: No, I am your father!";
    String result = subject.replaceNth(input1, 2, 'a', 'o');
    assertEquals(result, "Vader soid: No, I am your fother!");
}

Here it is, a simple test that we pass in the phase, and the info from the kata details and check to see if the proper characters are flipped.


Now that we have a target we can write some code.


Imperative Approach


Because this is a blog we get to skip the day of struggle, swearing, and existential crisis because I really thought I could do a .steam pipe in some sort of lambda and but done. After banging my head on that for a bit.... I decided to just go ahead and do it a way I wouldn't struggle with.... This is Imperative approach to solving this problem. Take a look at the code, and I'll go over it detail.

public static String replaceNth(String text, Integer n, Character oldValue, Character newValue){
    char[] chars = text.toCharArray();
    char[] ret = new char[chars.length];
    int counter = 1;

    for (int i = 0; i < chars.length; ++i)
    {
        ret[i] = chars[i];
        if (chars[i] == oldValue) {
            if (counter % n == 0) {
                ret[i] = newValue;
                counter = 0;
            }
            counter++;
        }
    }
    return new String(ret);
}

I start by making a char array from the text string. I step over each position in the char array and check if: 1. This character in the array is the character we want to swap? 2. If it is, then is counter evenly divisible by N?


For example if this is position 2, and we want to swap the 2nd char the mod division ( % ) will be 0. If it's the same character want to flip, and the Nth occurrence is evenly divisible. Then swap it!



This totally works, except it's not pretty nor functional.

Time to make it functional!

It's function time!

There was about a day of struggle here. I tried to make predicates, functions, pass them around different ways.... even if I got it to work, it was far worse than the imperative approach. The biggest problem I faced with trying to use the stream method, was I needed access to the index of the character array so that I could do the mathematical functions to know if I should swap this spot in the array or not.


I reach out to some other devs to ask for advice. Never be afraid to ask for help when you are spinning you wheels. Thank you to ajx over at StackOverflow! I got back an outline that was helpful. 1. Break down each step into a simple function.

2. remember the sandwich. Impure function, pure function, impure functions.

One of the cores of functional programing is finding the code that live on a pure functional, and pushing the ones with side effects to the edges. Hence the sandwich. 3. Break things down into simple general use functions, then build on those.


But the most important part was something I didn't know you could do in Java and that was get a iterator of a stream. Stream.iterator(int i -> i +1) That was a big break through that let me solve this. Needing a iterator on a stream is a weird thing you will probably only ever need to know for kata's and interview questions.


This is meant to be readable, but it can be reduced. I'll show that last.


public static List<String> splitBy(String text, Character splitValue) {
    return Arrays.stream(text.split(Pattern.quote(splitValue.toString()), -1)).toList();
}
public static <T> List<T> generateEveryNthSequence(int n, T everyNthValue, T everyOtherValue, int size) {
    return Stream.iterate(1, i -> i + 1)
            .limit(size)
            .map(i -> i % n == 0 ? everyNthValue : everyOtherValue)
            .collect(Collectors.toList());
}

public static <T> Stream<T> alternateValues(Stream<T> stream1, List<String>  stream2) {
    Iterator<T> iterator1 = stream1.iterator();
    Iterator<T> iterator2 = (Iterator<T>) stream2.stream().iterator();
    return Stream.iterate(iterator1, t -> t == iterator1 ? iterator2 : iterator1)
            .takeWhile(t -> t.hasNext())
            .map(t -> t.next());
}

public static String replaceNth(String text, Integer n, Character oldValue, Character newValue){
    // "V", "der s", "id: No, I ", "m your f", "ther!"
    List<String> segments = splitBy(text, oldValue);
    // "a", "o", "a", "o", ...
    List<String> separators = generateEveryNthSequence(n, newValue.toString(), oldValue.toString(), segments.size() * 2);

    // "V", "a", "der s", "o", "id: No, I ", "a", "m your f", "o", "ther!", "a"
    Stream<String> alternatingItems = alternateValues(segments.stream(), separators);
    // "V", "a", "der s", "o", "id: No, I ", "a", "m your f", "o", "ther!"
    Stream<String> alternatingItemsTrimmed = alternatingItems.limit(segments.size() * 2 - 1);
    // "Vader soid: No, I am your fother!"
    return alternatingItemsTrimmed.collect(Collectors.joining()



Let's break this down


This has 3 functions. splitBy which breaks the string array into a List that is broken on the char that may need swapped. But that char is removed from the list, as seen below.

Result: "V", "der s", "id: No, I ", "m your f", "ther!" generateEveryNthSequence this generates a sequence where every Nth position is the new

character that needs swapped is the new value, and all the other slots are old value generateEveryNthSequence(3, 'A', 'B') =? ['B', 'B', 'A', 'B', 'B', 'A', ...]

In this case it will look like: "a", "o", "a", "o", ...

Now we need to combine the two!

That's exactly what alternatingItems does. It will combine the two streams together. This will be the result "V", "a", "der s", "o", "id: No, I ", "a", "m your f", "o", "ther!"


The final step, it trim off extra space and reduce it to a string. Which the last line does. // "Vader soid: No, I am your fother!"



One last refactor


We can reduce the main function just a bit, and this is what the final outcome looks like



public static String replaceNthReduced(String text, Integer n, Character oldValue, Character newValue)
{
    List<String> segments = splitBy(text, oldValue);
    List<String> separators = generateEveryNthSequence(n,
                                        newValue.toString(),
                                        oldValue.toString(),                                                                               
                                        segments.size() * 2);
                                        
    return alternateValues(segments.stream(), separators)
            .limit(segments.size() * 2 - 1)
            .collect(Collectors.joining());
}



Doesn't that look better than the 1st attempt to solve this problem? I know I think so ;)

Wrapping Up

There is a lot that can be gained from learning how to write functionally even in a non-functional language. It helps write cleaner easier to test code, by breaking out the what needs to happen into small testable parts. I hope this has encouraged you to expand what you know and try something new. If you have any questions or comments, they are always welcome. Be sure to check out the source at: https://github.com/dcrackel/ReplaceEveryNth

13 views0 comments