Reputation: 15104
I have the following code that iterates over Java DateTimeFormatter
pattern combinations of "E".."EEEE"
and "M".."MMMM"
.
My question is, is there an idiomatic (or just 'more idiomatic') way to use Java Streams in this case?
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class DateTimeFormattingStackOverflow {
static LocalDateTime dateTime = LocalDateTime.now();
static Stream<String> substrings(String str) {
return IntStream.range(1, str.length() + 1)
.mapToObj(i -> str.substring(0, i));
}
static void printDateTime(String pattern) {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern);
System.out.println(pattern + ", " + dtf.format(dateTime));
}
public static void main(String[] args) {
Stream<String> patterns = substrings("EEEE")
.flatMap(e -> substrings("MMMM").map(m -> e + " " + m))
.map(em -> em + " d");
patterns.forEach(DateTimeFormattingStackOverflow::printDateTime);
}
}
Output
E M d, Sat 7 1
E MM d, Sat 07 1
E MMM d, Sat Jul 1
E MMMM d, Sat July 1
EE M d, Sat 7 1
EE MM d, Sat 07 1
EE MMM d, Sat Jul 1
EE MMMM d, Sat July 1
...
Upvotes: 9
Views: 816
Reputation: 298143
I wouldn’t stream over substrings at all. It’s enough to stream over something representing the sixteen combinations and to construct the format string in one operation:
Stream<String> patterns = IntStream.range(0, 16).map(i -> 15-i)
.mapToObj(i -> "EEEE".substring(i>>2)+" "+"MMMM".substring(i&3)+" d");
The fact that these are four times four combinations makes the calculation eligible for cheap bit arithmetic, but in principle, any n×m combination is possible by streaming from 0 to n×m and using i/n
and i%n
for selecting the element (like performing the substring
operation). The preceding .map(i -> 15-i)
step just reverses the order to match it with your original code; you can omit it if the order doesn’t matter.
Upvotes: 0
Reputation: 34460
You are using an IntStream
to drive the string. That's one way to do it, and here are two other ways:
static Stream<String> substrings(String str) {
return str.length() == 1 ? Stream.of(str) :
Stream.concat(Stream.of(str), substrings(str.substring(1)));
}
This creates a Stream
recursively, while the other way would be as follows:
static Stream<String> substrings2(String str) {
return Stream.iterate(str, s -> s.substring(1)).limit(str.length());
}
This applies the given function to the previous result. As it creates an infinite stream, you have to use limit
.
I have slightly modified your main
method, so that you avoid one map
operation:
substrings("EEEE")
.flatMap(e -> substrings("MMMM").map(m -> e + " " + m + " d"))
.forEach(DateTimeFormattingStackOverflow::printDateTime);
I really don't know if the ways above are more or less idiomatic than your way, but if you ask me, the most idiomatic way to do this task is with a nested loop:
String e = "EEEE";
String m = "MMMM";
for (int i = 0; i < e.length(); i++)
for (int j = 0; j < m.length(); j++)
printDateTime(e.substring(i) + " " + m.substring(j) + " d");
And this might be translated to Java 8 as follows:
IntStream.range(0, e.length()).boxed()
.flatMap(i -> IntStream.range(0, m.length())
.mapToObj(j -> e.substring(i) + " " + m.substring(j) + " d"))
.forEach(DateTimeFormattingStackOverflow::printDateTime);
Upvotes: 1
Reputation: 120848
I don't know if that is more idiomatic, but I would squeeze that to a single method (it could be just me...)
public static Stream<String> combinations(String first, String second, String d, LocalDateTime dateTime) {
Stream<String> s = IntStream.range(0, first.length())
.mapToObj(i -> first.substring(0, i + 1))
.flatMap(x -> IntStream.range(0, second.length())
.mapToObj(i -> second.substring(0, i + 1))
.map(m -> String.join(" ", x, m))
.map(res -> res + " d")
.peek(System.out::print)
.map(DateTimeFormatter::ofPattern)
.map(dtf -> dtf.format(dateTime)));
return s;
}
combinations("EEEE", "MMMM", " d", LocalDateTime.now()).forEach(x -> System.out.println(", " + x));
Upvotes: 0
Reputation: 726509
I would change the way in which you generate repeated runs of a character: rather than constructing the longest string manually, and then iterating its prefixes, I would construct runs with Collection.nCopies
, like this:
static Stream<String> repeatedRuns(String c, int start, int end) {
return IntStream
.rangeClosed(start, end)
.mapToObj(len ->
Collections.nCopies(len, c).stream().collect(Collectors.joining(""))
);
}
You would then replace substrings("EEEE")
with repeatedRuns("E", 1, 4)
.
Upvotes: 0