Use Java 8 functional programming to generate letter sequences
Using functional programming to generate letter sequences in Java 8 is a big challenge. Lukas Eder happily accepts this challenge and will tell us how to use Java 8 to generate ABC sequences-of course, it is not a bad way.
I was overwhelmed by an interesting question raised by "mip" on Stack Overflow. The problem is:
1 2 3 |
I am looking for a way to generate the following letter sequences: A, B, C, ..., Z, AA, AB, AC, ..., ZZ. |
You should be able to quickly recognize that this is the Excel spreadsheet header, which looks exactly as follows:
So far, no answer has been implemented using Java 8 functional programming, so I accept this challenge. I will use jOO λ because the Stream API of Java 8 does not provide enough functionality to complete this task. I admit that I am wrong-thank you very much for the interesting answers to this question ).
First, we use functions to break down this algorithm. We need the following components:
1. A reproducible alphabet.
2. an upper limit, such as the number of letters to be generated. If the sequence ZZ is required to be generated, the upper bound is 2.
3. A method that combines letters in the alphabet with previously generated letters into a cartesian product.
Let's take a look at the Code:
1. generate an alphabet
We can write the alphabet like this, for example:
1 |
List<String> alphabet = Arrays.asList( "A" , "B" , ..., "Z" ); |
But this is poor. We use jOO λ instead:
1 2 3 4 |
List<String> alphabet = Seq .rangeClosed( 'A' , 'Z' ) .map(Object::toString) .toList(); |
The code above generates A closed range from Character A to Z where Java-8-Stream-speak contains the upper boundary), maps the character to A string, and finally converts it to A list.
So far, everything is fine. Now:
2. Use the upper boundary:
The required character sequences include:
However, it should be easy to think of extending this requirement to generate the following character sequences or more:
1 |
A .. Z, AA, AB, .. ZZ, AAA, AAB, .. ZZZ |
Therefore, we will use rangeClosed () again ():
1 2 3 4 |
// 1 = A .. Z, 2 = AA .. ZZ, 3 = AAA .. ZZZ Seq.rangeClosed( 1 , 2 ) .flatMap(length -> ...) .forEach(System.out::println); |
This method generates a separate stream for each length in the range [1 .. 2], and then combines the streams into a stream. The essence of flatMap () is similar to the nested loop in imperative programming.
3. Merge letters into a Descartes
This is the tricky part: we need to merge characters and the number of occurrences. Therefore, we will use the following stream:
1 2 3 4 5 |
Seq.rangeClosed( 1 , length - 1 ) .foldLeft(Seq.seq(alphabet), (s, i) -> s.crossJoin(Seq.seq(alphabet)) .map(t -> t.v1 + t.v2)) ); |
We use rangeClosed () again to generate the value in the range [1 .. length-1. FoldLeft () and reduce () are basically the same. The difference is that foldLeft () ensures that the order in the stream is from "Left to right" and does not need to be correlated using the fold function.
On the other hand, this is an easy-to-understand word: foldLeft () represents only one loop command. The "Origin" of the loop is the initialization value of the loop) is a complete alphabet Seq. seq (alphabet )). Now, in the range [1 .. the value in length-1] generates a Cartesian Product crossJoin () to generate a new alphabet, and then each merged letter is combined into a separate string t. v1 and t. v2 ).
This is the entire process.
Merge the preceding content
The following is A simple program that prints A. Z, AA. ZZ, AAA. ZZZ to the console:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import org.jooq.lambda.Seq; public class Test { public static void main(String[] args) { int max = 3 ; List<String> alphabet = Seq .rangeClosed( 'A' , 'Z' ) .map(Object::toString) .toList(); Seq.rangeClosed( 1 , max) .flatMap(length -> Seq.rangeClosed( 1 , length - 1 ) .foldLeft(Seq.seq(alphabet), (s, i) -> s.crossJoin(Seq.seq(alphabet)) .map(t -> t.v1 + t.v2))) .forEach(System.out::println); } } |
Statement
This is indeed not the optimal algorithm for this problem. In Stack Overflow, an anonymous user provides the best implementation method.
1 2 3 4 5 6 7 8 9 10 |
import static java.lang.Math.*; private static String getString( int n) { char [] buf = new char [( int ) floor(log( 25 * (n + 1 )) / log( 26 ))]; for ( int i = buf.length - 1 ; i >= 0 ; i--) { n--; buf[i] = ( char ) ( 'A' + n % 26 ); n /= 26 ; } return new String(buf); } |
Needless to say, this algorithm is much faster than the previous functional algorithm.