One I know is to apply a Matcher to a substring, another is just to perform a manual search for the first character not being lower-case.
So what are the good ways to do this?
Using a Matcher seems to be a huge overkill for such a simple task.
In fact it's possible using a method equivalent to C++ find_first_not_of(), but Java does not seem to have one in its library.
Well, the Matcher way of doing it is 4-10 times slower than doing it manually, though, if done properly, is nice and readable (see Andreas' answer).
However, being used to the thought the code should generally be as efficient as you can make it (keeping it readable), I am still interested to see if it's possible to do the same thing faster using the standard library and / or native Java tricks.
I found an idiomatic method which is a bit faster: stream API
So currently discovered methods are:
// 1. Andreas' correction of the Matcher way
static String getLowercaseSubstringAt (final String s, final int pos) {
Matcher m = Pattern.compile("^\\p{Ll}+").matcher(s).region(pos, s.length());
return (m.find() ? m.group() : "");
}
// 2. The same with a static Pattern: 2.5 times faster
// I thought the compiler would optimize it by default
// Don't like the idea to make the Pattern external
static Pattern p = Pattern.compile("^\\p{Ll}+");
static String getLowercaseSubstringAt (final String s, final int pos) {
Matcher m = p.matcher(s).region(pos, s.length());
return (m.find() ? m.group() : "");
}
// 3. Stream API with a range of indices: 3x the speed of the first
// Makes use of the Java way to say `find_first(_not)_of()`
static String getLowercaseSubstringAt (final String s, final int pos) {
int idx = IntStream.range(pos, s.length())
.filter(i -> !Character.isLowerCase(s.charAt(i)))
.findFirst()
.orElse(s.length());
return s.substring(pos, idx);
}
// 4. Doing it manually. Relatively dirty, but fast. 10x the speed
static String getLowercaseSubstringAt (final String s, final int pos) {
for (int i = pos; i < s.length(); i++)
if (!Character.isLowerCase(s.charAt(i)))
return s.substring(pos, i);
return s.substring(pos);
}
Stream API looks like the best choice in terms of language currently: no need for external resources, clean, relatively fast.
Don't know why you say the Matcher way is really wasteful, both in code and in time.
Sure, a regex is slower than a simple for loop, but unless you're doing this repeatedly in a tight loop (1000+ times), you will not notice the difference. Until you measure performance and see a problem, it's likely not a problem. Beware premature optimizations.
But wasteful in code? Only because you're doing it wrong:
static String getLowercaseSubstringAt (final String s, final int pos) {
Matcher m = Pattern.compile("^\\p{Ll}+").matcher(s).region(pos, s.length());
return (m.find() ? m.group() : "");
}
Since your non-regex was using full lowercase checking with Character.isLowerCase(), I've updated the regex to do that as well.