Reputation: 625
Please bear with me. I haven't programmed in over a year and I'm currently reviewing my Java for job interviews by doing "homework questions." My function is supposed to return a string containing every second character in the given string. Is there a less awkward way to do this?
public String stringBits(String str) {
StringBuffer tmp = new StringBuffer();
for(int i = 0; i<str.length(); i+=2)
tmp.append(str.charAt(i));
String ret = new String(tmp);
return ret;
Upvotes: 0
Views: 1358
Reputation: 32343
I would use StringBuilder
, not StringBuffer
. StringBuffer
is for multithreaded situations, and is therefore slower than StringBuilder
, as it doesn't synchronize. I tested the four basic ways of doing this listed by various answers in this thread.
Notice, though, the certain things I always do here; these should be the things your interviewer is really looking for:
String += nextCharacter;
as it is much, much slower than using a StringBuilder
.initialCapacity
because doing that is always faster. If you don't, if the StringBuilder
gets full, it has to reallocate a new array and copy over, which is slow.And the code:
import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Random;
public class EveryOtherTest {
public static class StringBenchmark extends SimpleBenchmark {
private String input;
protected void setUp() {
Random r = new Random();
int length = r.nextInt(1000) + 1000;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
sb.append((char) ('A' + r.nextInt(26)));
}
input = sb.toString();
}
public String timeCharArrayForeach(int reps) {
String output = "";
Random r = new Random();
for (int i = 0; i < reps; i++) {
StringBuilder sb = new StringBuilder(input.length() / 2 + 1);
boolean use = false;
for (char c : input.toCharArray()) {
if(use) sb.append(c);
use = !use;
}
String newOutput = sb.toString();
if (r.nextBoolean()) output = newOutput; // Trick the JIT
}
return output;
}
public String timeCharArrayPlusTwo(int reps) {
String output = "";
Random r = new Random();
for (int i = 0; i < reps; i++) {
StringBuilder sb = new StringBuilder(input.length() / 2 + 1);
char[] charArray = input.toCharArray();
for(int j = 0; j < input.length(); j += 2) {
sb.append(charArray[j]);
}
String newOutput = sb.toString();
if (r.nextBoolean()) output = newOutput; // Trick the JIT
}
return output;
}
public String timeCharAt(int reps) {
String output = "";
Random r = new Random();
for (int i = 0; i < reps; i++) {
StringBuilder tmp = new StringBuilder(input.length() / 2 + 1);
for (int j = 0; j < input.length(); j += 2) {
tmp.append(input.charAt(j));
}
String newOutput = tmp.toString();
if (r.nextBoolean()) output = newOutput; // Trick the JIT
}
return output;
}
public String timeIterator(int reps) {
String output = "";
Random r = new Random();
for(int i = 0; i < reps; i++) {
StringBuilder buf = new StringBuilder(input.length() / 2 + 1);
StringCharacterIterator iterator = new StringCharacterIterator(input);
for (char c = iterator.first(); c != CharacterIterator.DONE; c = iterator.next()) {
buf.append(c);
iterator.next();
}
String newOutput = buf.toString();
if (r.nextBoolean()) output = newOutput; // Trick the JIT
}
return output;
}
public String timeRegex(int reps) {
String output = "";
Random r = new Random();
for(int i = 0; i < reps; i++) {
String newOutput = input.replaceAll("(?<!^).(.)", "$1");
if (r.nextBoolean()) output = newOutput; // Trick the JIT
}
return output;
}
}
public static void main(String... args) {
Runner.main(StringBenchmark.class, args);
}
}
Results:
0% Scenario{vm=java, trial=0, benchmark=CharArrayForeach} 2805.55 ns; ?=688.96 ns @ 10 trials
20% Scenario{vm=java, trial=0, benchmark=CharArrayPlusTwo} 3428.48 ns; ?=475.32 ns @ 10 trials
40% Scenario{vm=java, trial=0, benchmark=CharAt} 2138.68 ns; ?=379.44 ns @ 10 trials
60% Scenario{vm=java, trial=0, benchmark=Iterator} 3963.94 ns; ?=389.53 ns @ 10 trials
80% Scenario{vm=java, trial=0, benchmark=Regex} 58743.66 ns; ?=10850.33 ns @ 10 trials
benchmark us linear runtime
CharArrayForeach 2.81 =
CharArrayPlusTwo 3.43 =
CharAt 2.14 =
Iterator 3.96 ==
Regex 58.74 ==============================
vm: java
trial: 0
Upvotes: 3
Reputation: 159864
You could use this regex equivalent
String newString = str.replaceAll("(?<!^).(.)", "$1");
Upvotes: 1
Reputation: 67504
What you're doing works as far as I can tell. Here's a simple test case:
package com.sandbox;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class SandboxTest {
@Test
public void testMocking() {
assertEquals("foo", stringBits("f1o2o3"));
}
public String stringBits(String str) {
StringBuffer tmp = new StringBuffer();
for (int i = 0; i < str.length(); i += 2) {
tmp.append(str.charAt(i));
}
String ret = new String(tmp);
return ret;
}
}
I think that's a pretty straight forward way of doing it. There's probably a way to do it with a regex and a group, but I have a feeling your current code would be easier to read.
I'd never heard of the StringCharacterIterator
until I saw @Joey's answer, but it looks like an interesting solution. Here's the code using his answer:
package com.sandbox;
import org.junit.Test;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import static org.junit.Assert.assertEquals;
public class SandboxTest {
@Test
public void testMocking() {
assertEquals("foo", stringBits("f1o2o3"));
}
public String stringBits(String str) {
StringBuilder buf = new StringBuilder();
StringCharacterIterator iterator = new StringCharacterIterator(str);
for (char c = iterator.first(); c != CharacterIterator.DONE; c = iterator.next()) {
buf.append(c);
iterator.next();
}
return buf.toString();
}
}
Upvotes: 0
Reputation: 7048
You could turn the string into a CharArray and use a for-each loop:
for (char c: str.toCharArray()){
}
Of course then you would probably need a counter or a flag in order to get every other character, so it's probably not less awkward.
Upvotes: 0
Reputation: 255
I believe this should work too, and looks more simple to me.
public String stringBits(String str) {
String tmp = "";
for(int i = 0; i<str.length(); i+=2)
tmp+=str.charAt(i);
return tmp;
I edit to say that i should be equal to 1 if you want the second, fourth, six, ... characters.
Upvotes: 0
Reputation: 11942
No. And this is not awkward at all. For every useful task, there might be a more suitable way, but in this case you have no choice but to iterate over the string.
Upvotes: 0
Reputation: 354834
There is a StringCharacterIterator class if you prefer an iterator approach.
Upvotes: 2