This method converts a string to mixed case, id est mixedCase("hello, world!") -> "HeLlO, wOrLd!"
.
// Any input string is lower case already
private static String mixedCase(String input) {
// getAsCharacterList() simply turns a string into a List of Characters.
List<Character> charactersAsList = getAsCharacterList(input);
StringBuilder result = new StringBuilder();
int index = 0;
for (Character character : charactersAsList) {
if (Character.isLetter(character)) {
if (index % 2 == 0) {
result.append(Character.toUpperCase(character));
} else {
result.append(character);
}
index++;
} else {
result.append(character);
}
}
return result.toString();
}
It works, but admittedly it looks terrible and I have difficulties making it more concise. I would have preferred to use streams if I didn't had to reference indices...
-
\$\begingroup\$ Note: Related question rags-to-riches in go: Alternate letters to UpperCase \$\endgroup\$rolfl– rolfl2016年04月30日 13:46:17 +00:00Commented Apr 30, 2016 at 13:46
2 Answers 2
Is there a more concise way to write this Java string converter?
Sure. No need for the intermediary charactersAsList
variable, nor the getAsCharacterList
method.
Since you know in advance the final length of the StringBuilder
,
it's good to pass that to the constructor when initializing.
// input is already lowercase
private static String mixedCase(String input) {
StringBuilder result = new StringBuilder(input.length());
int index = 0;
for (char character : input.toCharArray()) {
if (Character.isLetter(character)) {
if (index % 2 == 0) {
result.append(Character.toUpperCase(character));
} else {
result.append(character);
}
index++;
} else {
result.append(character);
}
}
return result.toString();
}
Borrowing from the idea of @BrainFRZ to set the already lowercased input in StringBuilder
, and from @ferada's comment to use a boolean
to decide to uppercase or not, here's another variation that has some advantages:
private static String mixedCase(String input) {
StringBuilder result = new StringBuilder(input);
int index = 0;
boolean toUpper = true;
for (char character : input.toCharArray()) {
if (Character.isLetter(character)) {
if (toUpper) {
character = Character.toUpperCase(character);
}
toUpper = !toUpper;
}
result.setCharAt(index, character);
index++;
}
return result.toString();
}
This takes advantage of the input being already lowercased,
and naturally letting StringBuilder
to use that.
With some of the conditions eliminated, this version is slightly more compact.
-
\$\begingroup\$ The method arguments will already be lower cased, i clarified that in my OP. What is the advantage about initializing the StringBuilder with a known length? \$\endgroup\$AdHominem– AdHominem2016年04月30日 11:49:53 +00:00Commented Apr 30, 2016 at 11:49
-
\$\begingroup\$ Ok, that change in the description invalidated a paragraph in my answer, but fine, I updated it accordingly.
StringBuilder
dynamically increases its storage as needed, which can lead to reallocation of memory and array copies. If you know the final size in advance,StringBuilder
can allocate the right amount memory from the start, and avoid resizing and performance loss \$\endgroup\$janos– janos2016年04月30日 11:54:02 +00:00Commented Apr 30, 2016 at 11:54 -
\$\begingroup\$ @AdHominem I combined some ideas from the other answer and comments for a slightly more compact alternative, see the update. \$\endgroup\$janos– janos2016年04月30日 12:03:21 +00:00Commented Apr 30, 2016 at 12:03
There's actually a really easy way of doing this with a StringBuilder
! All you need to do is set up a new StringBuilder and use its build-in setCharAt
method.
public class StringTest {
private static String mixedCase(String input) {
StringBuilder sb = new StringBuilder(input.toLowerCase());
for (int i = 0; i < input.length(); i += 2) {
sb.setCharAt(i, Character.toUpperCase(input.charAt(i)));
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(mixedCase("testing")); //--> TeStInG
}
}
This way you don't have to do any fun with modulo operations, collections, or any of that stuff. If you want to have it mixed the other way, you can start the for-loop at i = 1
for "tEsTiNg".
Addendum:
As discussed in the comments, this solution only applies if you want the case to alternate every other letter based on its position in the string as opposed to alternating the case of every other letter ignoring non-letters.
-
\$\begingroup\$ This doesn't take into the account the
Character.isLetter(character)
in condition in the original code, and so the output of this will be different. Consider the input stringhello world
for example. \$\endgroup\$Simon Forsberg– Simon Forsberg2016年04月30日 11:30:59 +00:00Commented Apr 30, 2016 at 11:30 -
\$\begingroup\$ The
isLetter(...)
call is redundant.toUpperCase
will only process letters anyway @SimonForsberg \$\endgroup\$rolfl– rolfl2016年04月30日 11:44:01 +00:00Commented Apr 30, 2016 at 11:44 -
\$\begingroup\$ @rolfl That's not what I was thinking of, in the OP's code,
index++
is only performed if the character is a letter.Which means that in the OP's codehello world
will becomeHeLlO wOrLd
while this code makes itHeLlO WoRlD
\$\endgroup\$Simon Forsberg– Simon Forsberg2016年04月30日 11:46:43 +00:00Commented Apr 30, 2016 at 11:46 -
1\$\begingroup\$ And that, is of course a remarkably good point. \$\endgroup\$rolfl– rolfl2016年04月30日 11:48:04 +00:00Commented Apr 30, 2016 at 11:48
-
\$\begingroup\$ That's true, I was only testing with the "Hello, world!" which gave his expected output and I wasn't thinking that it was because ", " is two characters. (I also forgot to have
input.toLowerCase()
in my answer, and I've updated that part) \$\endgroup\$BrainFRZ– BrainFRZ2016年04月30日 11:49:49 +00:00Commented Apr 30, 2016 at 11:49