I'm trying to output multiple lists of data, of varying length, to a CSV file. Each list should be a column in the output CSV file. Is there a straight-forward way of doing thing? If I were outputting each list as a row, I'd just loop over each list and output a return when I hit the end, but this approach does not work when working column-wise.
I thought of going over all the lists at once, item by item and incrementing a counter, but this would also fail because some lists are longer than others. To remedy this I would have to check at each iteration whether the counter is past the end of each list, which would be fairly expensive in terms of computations.
Thanks for any ideas!
I think this is pretty straight-forward:
public static void main(String... args) throws IOException {
ArrayList<ArrayList<String>> rows = getRandomData();
if (rows.size() == 0)
throw new RuntimeException("No rows");
// normalize data
int longest = 0;
for (List<String> row : rows)
if (row.size() > longest)
longest = row.size();
for (List<String> row : rows)
while (row.size() < longest)
row.add("");
if (longest == 0)
throw new RuntimeException("No colums");
// fix special characters
for (int i = 0; i < rows.size(); i++)
for (int j = 0; j < rows.get(i).size(); j++)
rows.get(i).set(j, fixSpecial(rows.get(i).get(j)));
// get the maximum size of one column
int[] maxColumn = new int[rows.get(0).size()];
for (int i = 0; i < rows.size(); i++)
for (int j = 0; j < rows.get(i).size(); j++)
if (maxColumn[j] < rows.get(i).get(j).length())
maxColumn[j] = rows.get(i).get(j).length();
// create the format string
String outFormat = "";
for (int max : maxColumn)
outFormat += "%-" + (max + 1) + "s, ";
outFormat = outFormat.substring(0, outFormat.length() - 2) + "\n";
// print the data
for (List<String> row : rows)
System.out.printf(outFormat, row.toArray());
}
private static String fixSpecial(String s) {
s = s.replaceAll("(\")", "$1$1");
if (s.contains("\n") || s.contains(",") || s.contains("\"") ||
s.trim().length() < s.length()) {
s = "\"" + s + "\"";
}
return s;
}
private static ArrayList<ArrayList<String>> getRandomData() {
ArrayList<ArrayList<String>> data = new ArrayList<ArrayList<String>>();
String[] rand = { "Do", "Re", "Song", "David", "Test", "4", "Hohjoh", "a \"h\" o", "tjo,ad" };
Random r = new Random(5);
for (int i = 0; i < 10; i++) {
ArrayList<String> row = new ArrayList<String>();
for (int j = 0; j < r.nextInt(10); j++)
row.add(rand[r.nextInt(rand.length)]);
data.add(row);
}
return data;
}
Output (pretty ugly since its random) (escapes):
Re , 4 , "tjo,ad" , "tjo,ad" ,
"tjo,ad" , "a ""h"" o" , , ,
Re , "a ""h"" o" , Hohjoh , "tjo,ad" , 4
4 , David , , ,
4 , Test , "tjo,ad" , Hohjoh , Re
Do , Hohjoh , Test , ,
Hohjoh , Song , , ,
4 , Song , , ,
4 , Do , Song , Do ,
Song , Test , Test , ,
It's worth having a look at http://commons.apache.org/sandbox/csv/
This also references some other CSV libraries.
Note that many answers have not considered strings which contain commas. That's the sort of reason why libraries are better than doing it yourself.
You can use String.format():
System.out.println(String.format("%4s,%4s,%4s", "a", "bb", "ccc"));
System.out.println(String.format("%4s,%4s,%4s", "aaa", "b", "c"));
The result will be a fixed column width of 4 characters - as long as the used values are shorter. Otherwise the layout will break.
a, bb, ccc
aaa, b, c
I'm not familiar with Java at all, but if you have a matrix oriented data type, you could fill the rows using easy looping, then transpose it, then write it out using easy looping. Your printing routine could handle null entries by outputting a null string, or fixed width spaces if you prefer.
Create an array of iterators (one for each list.) Then loop over the array, checking if the iterator hasNext(); if it does, output iterator.next(). Outputting commas and newlines is trivial. Stop when all iterators have returned hasNext()==false.
You can do something like this:
List<List<?>> listOfLists = new LinkedList<List<?>>();
List<Iterator<?>> listOfIterators = new LinkedList<Iterator<?>>();
for (List<?> aList : listOfLists) {
listOfIterators.add(aList.iterator());
}
boolean done = false;
while(!done)
{
done = true;
for (Iterator<?> iter : listOfIterators)
{
if (iter.hasNext())
{
Object obj = iter.next();
//PROCESS OBJ
done = false;
}
else
{
//PROCESS EMPTY ELEMENT
}
}
}
For CSV processing I have used this library several times: http://www.csvreader.com/java_csv.php Very simple and convenient.
Cheerz!
I would have to check at each iteration whether the counter is past the end of each list, which would be fairly expensive in terms of computations.
Get over it. This will, realistically, be small compared to the cost of actually doing the iteration, which in turn will be tiny compared to the cost of writing any given bit of text to the file. At least, assuming you have random access containers.
But you shouldn't be thinking in terms of a counter and indexing anyway; you should be thinking in terms of iterators (which sidestep the random-access question and simplify the code).
If you wanted to do this in one pair of loops and one method, you could do the following.
public static void writeCSV(PrintWriter pw, List<List<String>> columnsRows) {
for(int i=0;;i++) {
StringBuilder line = new StringBuilder();
boolean empty = true;
for (List<String> column : columnsRows) {
String text = i < column.size() ? column.get(i) : "";
found &= i >= column.size();
if (text.contains(",") || text.contains("\"") || text.contains("\n") || text.trim() != text)
text = '"' + text.replaceAll("\"", "\"\"") + '"';
line.append(text).append(',');
}
if (empty) break;
pw.println(line.substring(0, line.length()-1));
}
}
As an exercise, you could do this with one loop, but it wouldn't be as clear as to what its doing.
Using the sample data from #dacwe, this method takes 10 us (micro-seconds).
Related
I have a string:
str = "Hello there"
I am removing the whitespace:
String[] parts = str.split("\\s+");
Creating a List and populating it with the parts:
List<String> theParts = new ArrayList<String>();
for (int i = 0; i < parts.length; i++) {
theParts.add(parts[i]);
}
The size of the List is 2.Now, I want to increase it's size in order to be the same size as another list.
Let's say the other list has size 3.
So, I check:
if (otherList.size() > theParts.size()) {
and then, I want to change the theParts list in order to contain an empty space (the number which shows how much greater the otherList is) between it's parts.
So, I want theParts to be (add a space at every odd position):
theParts[0] = "Hello"
theParts[1] = " "
theParts[2] = "there"
I am not sure if this can be happen with Lists, but I can't think another solution.
Or use something like join (doesn't work, just an idea to use something like this):
if (otherList.size() > theParts.size()) {
for (int i = 0; i < otherList.size(); i++) {
if (i%2 !=0) {
String.join(" ", theParts);
}
}
}
Just insert the spaces as you're populating the list:
List<String> theParts = new ArrayList<>(2 * parts.length - 1);
for (int i = 0; i < parts.length; i++) {
if (i > 0) theParts.add(" ");
theParts.add(parts[i]);
}
You could use a word break regex:
public void test() throws Exception {
String str = "Hello there";
List<String> strings = Arrays.asList(str.split("\\b"));
for ( String s : strings ) {
System.out.println("'"+s+"'");
}
}
this will retain all of the spaces for you.
'Hello'
' '
'there'
for(String dis : theParts){
newParts.add(dis);//'newPart is another list '
String last = parts[parts.length -2]; // until new list read last element
if(!last.equals(dis)){
newParts.add(" ");
}if(last.equals(dis)){
newParts.add(" ");
}
}
This question already has answers here:
Create ArrayList from array
(42 answers)
Closed 6 years ago.
I would like to convert an Array of Strings into an ArrayList of ArrayList, where the inner ArrayList has a dynamic number of elements. Who can help ? Thanks in advance
String[] sentences = {"hello","how are you","i am fine","and you ?","thank you"}
//Output with number of elements = 2
["hello","how are you"]
["i am fine","and you ?"]
["thank you"]
//Output with number of elements = 3
["hello","how are you","i am fine"]
["and you ?","thank you"]
public static void main(String[] args) {
String[] sentences = {"hello", "how are you", "i am fine", "and you ?", "thank you"};
System.out.println(split(2,sentences));
System.out.println(split(3,sentences));
}
public static List<List<String>> split(int numberOfElements, String[] sentences) {
List<List<String>> lists = new ArrayList<List<String>>();
int index = 0;
for (String sentence : sentences) {
if (index % numberOfElements == 0) {
lists.add(new ArrayList<String>());
}
lists.get(index / numberOfElements).add(sentences[index]);
index++;
}
return lists;
}
Output:
[[hello, how are you], [i am fine, and you ?], [thank you]]
[[hello, how are you, i am fine], [and you ?, thank you]]
public static void main(String[] args) {
String[] sentences = { "hello", "how are you", "i am fine", "and you ?", "thank you" };
List<List<String>> convertIntoList = convertIntoList(sentences, 2);
System.out.println(convertIntoList);
convertIntoList = convertIntoList(sentences, 3);
System.out.println(convertIntoList);
}
private static List<List<String>> convertIntoList(String[] sentences, int nbElement) {
List<List<String>> listOfListTarget = new ArrayList<List<String>>();
int currentIndex = 0;
while (currentIndex < sentences.length) {
int nextIndex = currentIndex + nbElement;
if (nextIndex > sentences.length) {
nextIndex = sentences.length;
}
final String[] copyOfRange = Arrays.copyOfRange(sentences, currentIndex, nextIndex);
List<String> subList = new ArrayList<String>();
subList.addAll(Arrays.asList(copyOfRange));
listOfListTarget.add(subList);
currentIndex+=nbElement;
}
return listOfListTarget;
}
Is this is a homework?
So you have an array of strings, and you want to create a List> with that, with each inner List containing at most x number of elements.
To get x number of elements and put them in a List, you can do a simple for loop.
String[] myStringArray = { ... };
List<String> myListOfString = new ArrayList<>();
for(int i=0; i<x; i++) {
myListOfString.add(myStringArray[i]);
}
So for example if you have these values
String[] myStringArray = {"a", "b", "c", "d", "e"};
x = 2;
You'll get the following list using the above loop:
["a", "b"]
Great! But we need to get all the contents of the myStringArray! How do we do that? Then let's do the first step, we iterate through all the contents of the array. We can do that like this.
int i=0;
while(i < myStringArray.length) {
System.out.println(myStringArray[i]);
i++;
}
Which will output:
a
b
c
d
e
This doesn't solve the problem... but at least we know how to iterate the whole thing. The next step is to get x of them. Sounds simple right? So basically we need to create a list of x from the contents. Maybe we can use the logic we created a few examples back to solve the problem.
// Create list of list of string here
int i = 0;
while(i < myStringArray.length) {
// Create list of string here
for(int j=0; j<x; j++) {
// Add myStringArray[j] to list of string here
}
// Add the list of string to the list of list of string here
i++;
}
Easy right? No. This gives the following lists:
["a", "b"]
["a", "b"]
["a", "b"]
["a", "b"]
["a", "b"]
Why? In the first loop, we are iterating up to how many is in the array. In the second loop, we are adding element 0 and 1 to a list. Obviously it wouldn't work. The second loop needs to be aware that it should not add previously added elements, and at the same time the first loop needs to be aware of what the second loop is doing. So you might think, maybe we can use the int i to indicate where the second loop should start?
int i = 0;
while(i<myStringArray.length) {
while(i<x) {
// add myStringArray[i];
i++;
}
i++;
}
Unfortunately, using the same values as previous, this will only give the following list
["a", "b"]
Because i is iterating through the whole array. So when it goes from 0 to length, whatever the value of i is used on the second array. When it loops again, i becomes 1, so the start of the second loop is at 1.
We need a separate variable to do the counting, while still keeping in mind where we currently are in the second loop.
int i = 0;
while(i<myStringArray.length) {
int count = 0;
while(count < x) {
// Add myStringArray[count+i] to list of string
count++;
}
// Add to list of list of string
i += count + 1; // Need to be aware of how much we have processed
}
This will do what we want, but unfortunately we can get in trouble at certain values. Say x is 10 and myStringArray is only of length 2. This will throw an exception because when it reaches the point of count+i = 3, that index doesn't exist anymore. The second loop also needs to be aware of how much is still remaining.
Finally we'll have the following code
int i = 0;
while(i<myStringArray.length) {
int count = 0;
while(count < x && count+i < myStringArray.length) {
// Add myStringArray[count+i] to list of string
}
// Add to list of list of string
i += count; // Need to be aware of how much we have processed
}
Which will give
["a", "b"]
["c", "d"]
["e"]
Edit: Next time try to put some code that you tried something.
I am trying to remove duplicated words from an array, and I keep getting null values. I'm not allowed to use java sorting methods so I have to develop my own. Here's my code:
public class Duplicate{
public static void main(String[] args){
String[] test = {"a", "b", "abvc", "abccc", "a", "bbc", "ccc", "abc", "bbc"};
removeDuplicate(test);
}
public static String[] removeDuplicate(String[] words){
boolean [] isDuplicate = new boolean[words.length];
int i,j;
String[] tmp = new String[words.length];
for (i = 0; i < words.length ; i++){
if (isDuplicate[i])
continue;
for(j = 0; j < words.length ; j++){
if (words[i].equals(words[j])) {
isDuplicate[j] = true;
tmp[i] = words[i];
}
}
}
for(i=0;i<words.length;i++)
System.out.println(tmp[i]);
return tmp;
}
}
I tried doing
if(words == null)
words == "";
But it doesn't work. I also want to return the tmp array with a new size.
For example, test array length = 9, after removing the duplicates,I should get a new array with a length of 7.Thank you for your help.
EDIT:
result i get:
a
b
abvc
abccc
null
bbc
ccc
abc
null
You're getting nulls because the result array contains fewer words than the input array. However, you're constructing the arrays of the same length.
You don't have to sort to solve this problem. However, if you're not allowed to use the tools provided by java.utils, then this is either a poorly contrived test question or whomever told you not to use the Java utility classes is poorly informed.
You can solve without sorting by doing (assuming Java 1.5+):
public class Duplicate {
public static void main(String[] args) {
String[] test = {"a", "b", "abvc", "abccc", "a", "bbc", "ccc", "abc", "bbc"};
String[] deduped = removeDuplicate(test);
print(deduped);
}
public static String[] removeDuplicate(String[] words) {
Set<String> wordSet = new LinkedHashSet<String>();
for (String word : words) {
wordSet.add(word);
}
return wordSet.toArray(new String[wordSet.size()]);
}
public static void print(String[] words) {
for (String word : words) {
System.out.println(word);
}
}
}
The output will be:
a
b
abvc
abccc
bbc
ccc
abc
I would go for hashset to remove duplicates, it will remove duplicates since hash function for the same string will give same value, and duplicates will be eliminated. Then you can convert it to a string.
I would recommend doing this with a different approach. If you can use an ArrayList, why not just create one of those, and add the non-duplicate values to it, like this:
ArrayList<String> uniqueArrayList = new ArrayList<String>();
for(int i = 0; i < words.length; i++){
if(!uniqueArrayList.contains(words[i])){ // If the value isn't in the list already
uniqueArrayList.add(words[i]);
}
}
Now, you have an array list of all of your values without the duplicates. If you need to, you can work on converting that back to a regular array.
EDIT
I really think you should use the above option if you can, as there is no clean or decently efficient way to do this only using arrays. However, if you must, you can do something like this:
You can use the code you have to mark values as null if they are duplicates, and also create a counter to see how many unique values you have, like this:
int uniqueCounter = 0;
for(int i = 0; i < isDuplicate.length; i++){
if(!isDuplicate[i]){
uniqueCounter++;
}
}
Then, you can create a new array of the size of unique items, and loop through the words and add non-duplicate values.
String[] uniqueArray = new String[uniqueCounter];
int uniqueIndex = 0;
int wordsIndex = 0;
while(index < uniqueArray.length){
// Check if words index is not a duplicate
if(!isDuplicate[wordsIndex]){
// Add to array
uniqueArray[uniqueIndex] = words[wordsIndex];
uniqueIndex++; // Need to move to next spot in unique.
}
// Need to move to next spot in words
wordsIndex++;
}
Again, I HIGHLY recommend against something like this. It is very poor, and pains me to write, but for the sake of example on how it could be done using an array, you can try it.
I don't have the time to write functioning code, but I would reccomend to first sort the array using Arrays.sort(stringArray) and then loop throug the array coparing one string to the previous. Strings that match the previous one are duplicates.
Note: This method is probably not the fastest one and though only should be used on small arrays or in tasks where performance does not matter.
What about this approach?
public static String[] removeDuplicate(String[] words){
// remember which word is a duplicate
boolean[] isDuplicate = new boolean[words.length];
// and count them
int countDuplicate = 0;
for (int i = 0; i < words.length ; i++){
// only check "forward" because "backwards checked" duplicates have been marked yet
for(int j = i + 1; j < words.length ; j++){
if (words[i].equals(words[j])) {
isDuplicate[j] = true;
countDuplicate++;
}
}
}
// collect non-duplicate strings
String[] tmp = new String[words.length - countDuplicate];
int j = 0;
for (int i = 0; i < isDuplicate.length; i++) {
if (isDuplicate[i] == false) {
tmp[j] = words[i];
j++;
}
}
// and return them
return tmp;
}
Given a string, I want to find all variants without transposition, only deletion. For example, given the string:
helloo
The list of variants would be as follows (separated by white space).
helloo hello heloo helo
My solution so far is to move through each character, and then if the current character matches the next character, recursively try the original and the deleted character version, as follows.
// takes String with at most two consecutive characters of any character,
// and returns an Iterable of all possible variants (e.g. hheello -> heello, hhello, ...)
private static Iterable<String> findAllVariants(String word) {
StringBuilder variant = new StringBuilder(word);
Queue<String> q = new LinkedList<String>();
findAllVariants(word, variant, 0, q);
return q;
}
// helper method
private static void findAllVariants(String word, StringBuilder variant, int currIndex, Queue<String> q) {
if (currIndex == variant.length() - 1) q.add(variant.toString());
for (int i = currIndex; i < variant.length() - 1; i++) {
char thisChar = variant.charAt(i);
char nextChar = variant.charAt(i+1);
if (thisChar == nextChar) {
// get all variants with repeat character
findAllVariants(word, variant, i+1, q);
// get all variants without repeat character;
variant = variant.deleteCharAt(i);
findAllVariants(word, variant, i, q);
}
}
}
However, I end up getting a large number of copies of answers, and none of others. When I do my algorithm on paper, it seems correct. What am I doing wrong?
Something along the lines of the following code will enable you to get all possibilities (remember to add word itself if needed). The idea is to retreive all possibilities for removing one char (e.g. hello results in ello hllo helo hell). These results can in turn be used to get the possibilities for removing two chars (remove one char again). Resulting in llo elo ell for ello and so on...
List<String> getPossibilities(String word) {
int removeChars = word.length() - 1;
List<String> possibilities = new ArrayList();
List<String> options = Arrays.asList(word);
for(int i = 0; i <= removeChars; i++) {
List<String> results = new ArrayList();
for(String option : options) {
for(String result : removeOneChar(option)) {
if(!results.contains(result)) {
results.add(result);
}
}
}
possibilities.addAll(results);
options = results;
}
return possibilities;
}
private static List<String> removeOneChar(String word) {
List<String> results = new ArrayList();
for(int i = 0; i < word.length(); i++) {
int secondPart = i + 2;
if(secondPart <= word.length()) {
results.add(
word.substring(0, i)
+ word.substring(i + 1, word.length()));
}
else {
results.add(
word.substring(0, i));
}
}
return results;
}
Notice the if(!contains(result)) in order to prevent any duplicates.
Note I've used substring() to accomplish this, you're approach with removeCharAt() is a another good option. You could run some tests to see which performs better to decide which one to use. Notice using the latter could possibly remove the need of the if in the private method.
I'd use rather different algorithm: I'd find all repetitions (ll) (oo) (lll) (ooo) etc.., keep an array describing their positions in the text, and the count of characters per each repetition.
e.g Array A =
[l|2]
[o|2]
.
.
.
Then I'd say have second array with initial count zero and increase there the count and print out all permutations:
Array B =
[l|1]
[o|1]
==> prints helo
Step 2: (increment count)
B =
[l|2]
[o|1]
==> prints hello
Step 3:
B =
[l|3] ==> bigger than max,so reset it to 0, and increment the second cell now, so it becomes:
B =
[l|1]
[o|2]
==> prints heloo
Step 4: (increment first elem again)
[l|2] ==> not bigger than max, so no overflow, so keeping it that way
[o|2]
==> prints helloo
I run into this case a lot of times when doing simple text processing and print statements where I am looping over a collection and I want to special case the last element (for example every normal element will be comma separated except for the last case).
Is there some best practice idiom or elegant form that doesn't require duplicating code or shoving in an if, else in the loop.
For example I have a list of strings that I want to print in a comma separated list. (the do while solution already assumes the list has 2 or more elements otherwise it'd be just as bad as the more correct for loop with conditional).
e.g. List = ("dog", "cat", "bat")
I want to print "[dog, cat, bat]"
I present 2 methods the
For loop with conditional
public static String forLoopConditional(String[] items) {
String itemOutput = "[";
for (int i = 0; i < items.length; i++) {
// Check if we're not at the last element
if (i < (items.length - 1)) {
itemOutput += items[i] + ", ";
} else {
// last element
itemOutput += items[i];
}
}
itemOutput += "]";
return itemOutput;
}
do while loop priming the loop
public static String doWhileLoopPrime(String[] items) {
String itemOutput = "[";
int i = 0;
itemOutput += items[i++];
if (i < (items.length)) {
do {
itemOutput += ", " + items[i++];
} while (i < items.length);
}
itemOutput += "]";
return itemOutput;
}
Tester class:
public static void main(String[] args) {
String[] items = { "dog", "cat", "bat" };
System.out.println(forLoopConditional(items));
System.out.println(doWhileLoopPrime(items));
}
In the Java AbstractCollection class it has the following implementation (a little verbose because it contains all edge case error checking, but not bad).
public String toString() {
Iterator<E> i = iterator();
if (! i.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = i.next();
sb.append(e == this ? "(this Collection)" : e);
if (! i.hasNext())
return sb.append(']').toString();
sb.append(", ");
}
}
I usually write it like this:
static String commaSeparated(String[] items) {
StringBuilder sb = new StringBuilder();
String sep = "";
for (String item: items) {
sb.append(sep);
sb.append(item);
sep = ",";
}
return sb.toString();
}
There are a lot of for loops in these answers, but I find that an Iterator and while loop reads much more easily. E.g.:
Iterator<String> itemIterator = Arrays.asList(items).iterator();
if (itemIterator.hasNext()) {
// special-case first item. in this case, no comma
while (itemIterator.hasNext()) {
// process the rest
}
}
This is the approach taken by Joiner in Google collections and I find it very readable.
string value = "[" + StringUtils.join( items, ',' ) + "]";
My usual take is to test if the index variable is zero, e.g.:
var result = "[ ";
for (var i = 0; i < list.length; ++i) {
if (i != 0) result += ", ";
result += list[i];
}
result += " ]";
But of course, that's only if we talk about languages that don't have some Array.join(", ") method. ;-)
I think it is easier to think of the first element as the special case because it is much easier to know if an iteration is the first rather than the last. It does not take any complex or expensive logic to know if something is being done for the first time.
public static String prettyPrint(String[] items) {
String itemOutput = "[";
boolean first = true;
for (int i = 0; i < items.length; i++) {
if (!first) {
itemOutput += ", ";
}
itemOutput += items[i];
first = false;
}
itemOutput += "]";
return itemOutput;
}
I'd go with your second example, ie. handle the special case outside of the loop, just write it a bit more straightforward:
String itemOutput = "[";
if (items.length > 0) {
itemOutput += items[0];
for (int i = 1; i < items.length; i++) {
itemOutput += ", " + items[i];
}
}
itemOutput += "]";
Java 8 solution, in case someone is looking for it:
String res = Arrays.stream(items).reduce((t, u) -> t + "," + u).get();
I like to use a flag for the first item.
ArrayList<String> list = new ArrayList()<String>{{
add("dog");
add("cat");
add("bat");
}};
String output = "[";
boolean first = true;
for(String word: list){
if(!first) output += ", ";
output+= word;
first = false;
}
output += "]";
Since your case is simply processing text, you don't need the conditional inside the loop. A C example:
char* items[] = {"dog", "cat", "bat"};
char* output[STRING_LENGTH] = {0};
char* pStr = &output[1];
int i;
output[0] = '[';
for (i=0; i < (sizeof(items) / sizeof(char*)); ++i) {
sprintf(pStr,"%s,",items[i]);
pStr = &output[0] + strlen(output);
}
output[strlen(output)-1] = ']';
Instead of adding a conditional to avoid generating the trailing comma, go ahead and generate it (to keep your loop simple and conditional-free) and simply overwrite it at the end. Many times, I find it clearer to generate the special case just like any other loop iteration and then manually replace it at the end (although if the "replace it" code is more than a couple of lines, this method can actually become harder to read).
...
String[] items = { "dog", "cat", "bat" };
String res = "[";
for (String s : items) {
res += (res.length == 1 ? "" : ", ") + s;
}
res += "]";
or so is quite readable. You can put the conditional in a separate if clause, of course. What it makes idiomatic (I think so, at least) is that it uses a foreach loop and does not use a complicated loop header.
Also, no logic is duplicated (i.e. there is only one place where an item from items is actually appended to the output string - in a real world application this might be a more complicated and lengthy formatting operation, so I wouldn't want to repeat the code).
In this case, you are essentially concatenating a list of strings using some separator string. You can maybe write something yourself which does this. Then you will get something like:
String[] items = { "dog", "cat", "bat" };
String result = "[" + joinListOfStrings(items, ", ") + "]"
with
public static String joinListOfStrings(String[] items, String sep) {
StringBuffer result;
for (int i=0; i<items.length; i++) {
result.append(items[i]);
if (i < items.length-1) buffer.append(sep);
}
return result.toString();
}
If you have a Collection instead of a String[] you can also use iterators and the hasNext() method to check if this is the last or not.
If you are building a string dynamically like that, you shouldn't be using the += operator.
The StringBuilder class works much better for repeated dynamic string concatenation.
public String commaSeparate(String[] items, String delim){
StringBuilder bob = new StringBuilder();
for(int i=0;i<items.length;i++){
bob.append(items[i]);
if(i+1<items.length){
bob.append(delim);
}
}
return bob.toString();
}
Then call is like this
String[] items = {"one","two","three"};
StringBuilder bob = new StringBuilder();
bob.append("[");
bob.append(commaSeperate(items,","));
bob.append("]");
System.out.print(bob.toString());
Generally, my favourite is the multi-level exit. Change
for ( s1; exit-condition; s2 ) {
doForAll();
if ( !modified-exit-condition )
doForAllButLast();
}
to
for ( s1;; s2 ) {
doForAll();
if ( modified-exit-condition ) break;
doForAllButLast();
}
It eliminates any duplicate code or redundant checks.
Your example:
for (int i = 0;; i++) {
itemOutput.append(items[i]);
if ( i == items.length - 1) break;
itemOutput.append(", ");
}
It works for some things better than others. I'm not a huge fan of this for this specific example.
Of course, it gets really tricky for scenarios where the exit condition depends on what happens in doForAll() and not just s2. Using an Iterator is such a case.
Here's a paper from the prof that shamelessly promoted it to his students :-). Read section 5 for exactly what you're talking about.
I think there are two answers to this question: the best idiom for this problem in any language, and the best idiom for this problem in java. I also think the intent of this problem wasn't the tasks of joining strings together, but the pattern in general, so it doesn't really help to show library functions that can do that.
Firstly though the actions of surrounding a string with [] and creating a string separated by commas are two separate actions, and ideally would be two separate functions.
For any language, I think the combination of recursion and pattern matching works best. For example, in haskell I would do this:
join [] = ""
join [x] = x
join (x:xs) = concat [x, ",", join xs]
surround before after str = concat [before, str, after]
yourFunc = surround "[" "]" . join
-- example usage: yourFunc ["dog", "cat"] will output "[dog,cat]"
The benefit of writing it like this is it clearly enumerates the different situations that the function will face, and how it will handle it.
Another very nice way to do this is with an accumulator type function. Eg:
join [] = ""
join strings = foldr1 (\a b -> concat [a, ",", b]) strings
This can be done in other languages as well, eg c#:
public static string Join(List<string> strings)
{
if (!strings.Any()) return string.Empty;
return strings.Aggregate((acc, val) => acc + "," + val);
}
Not very efficient in this situation, but can be useful in other cases (or efficiency may not matter).
Unfortunately, java can't use either of those methods. So in this case I think the best way is to have checks at the top of the function for the exception cases (0 or 1 elements), and then use a for loop to handle the case with more than 1 element:
public static String join(String[] items) {
if (items.length == 0) return "";
if (items.length == 1) return items[0];
StringBuilder result = new StringBuilder();
for(int i = 0; i < items.length - 1; i++) {
result.append(items[i]);
result.append(",");
}
result.append(items[items.length - 1]);
return result.toString();
}
This function clearly shows what happens in the two edge cases (0 or 1 elements). It then uses a loop for all but the last elements, and finally adds the last element on without a comma. The inverse way of handling the non-comma element at the start is also easy to do.
Note that the if (items.length == 1) return items[0]; line isn't actually necessary, however I think it makes what the function does more easier to determine at a glance.
(Note that if anyone wants more explanation on the haskell/c# functions ask and I'll add it in)
It can be achieved using Java 8 lambda and Collectors.joining() as -
List<String> items = Arrays.asList("dog", "cat", "bat");
String result = items.stream().collect(Collectors.joining(", ", "[", "]"));
System.out.println(result);
I usually write a for loop like this:
public static String forLoopConditional(String[] items) {
StringBuilder builder = new StringBuilder();
builder.append("[");
for (int i = 0; i < items.length - 1; i++) {
builder.append(items[i] + ", ");
}
if (items.length > 0) {
builder.append(items[items.length - 1]);
}
builder.append("]");
return builder.toString();
}
If you are just looking for a comma seperated list of like this: "[The, Cat, in, the, Hat]", don't even waste time writing your own method. Just use List.toString:
List<String> strings = Arrays.asList("The", "Cat", "in", "the", "Hat);
System.out.println(strings.toString());
Provided the generic type of the List has a toString with the value you want to display, just call List.toString:
public class Dog {
private String name;
public Dog(String name){
this.name = name;
}
public String toString(){
return name;
}
}
Then you can do:
List<Dog> dogs = Arrays.asList(new Dog("Frank"), new Dog("Hal"));
System.out.println(dogs);
And you'll get:
[Frank, Hal]
A third alternative is the following
StringBuilder output = new StringBuilder();
for (int i = 0; i < items.length - 1; i++) {
output.append(items[i]);
output.append(",");
}
if (items.length > 0) output.append(items[items.length - 1]);
But the best is to use a join()-like method. For Java there's a String.join in third party libraries, that way your code becomes:
StringUtils.join(items,',');
FWIW, the join() method (line 3232 onwards) in Apache Commons does use an if within a loop though:
public static String join(Object[] array, char separator, int startIndex, int endIndex) {
if (array == null) {
return null;
}
int bufSize = (endIndex - startIndex);
if (bufSize <= 0) {
return EMPTY;
}
bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length()) + 1);
StringBuilder buf = new StringBuilder(bufSize);
for (int i = startIndex; i < endIndex; i++) {
if (i > startIndex) {
buf.append(separator);
}
if (array[i] != null) {
buf.append(array[i]);
}
}
return buf.toString();
}