convert comma (with a space) delimited string into columns under the matching values from the string itself
Question
I have an output in the console from my code like the sample given below,
... columns=20xs, viewport_supported=true, wta=false, ..., mmf=false
... columns=11xs, viewport_supported=false, wta=false, ..., mmf=true
... columns=15xs, viewport_supported=true, wta=false, ..., mmf=false
I want to re-arrange it by extracting the common string left to the '=' sign and put it as the column header and its values in its column. How to start doing this in Java?
I am looking for the output like below:
columns viewport_supported wta and so on
20xs true false
11xs false false
15xs true false
Solution 4
I finally was able to do it like this: Split the string using split() method, create a Map dynamic array and assign key and value to the string respectively on either side of the 'equals to sign'. Then printing the header information by parsing the map representing the first data row and then print all the data rows by iterating the objects from the Map. excerpts from my code is given below.
/* delimiter */
String delimiter = ", ";
// given string will be split by the argument delimiter provided.
String[] temp = capabilities.split(delimiter);
/* print substrings */
Map<String, String> row = new HashMap<String, String>();
for (int i = 0; i < temp.length; i++) {
String delimiter1 = "=";
String[] temp1 = null;
temp1 = temp[i].split(delimiter1);
if (temp1 != null && temp1.length == 2) {
row.put(temp1[0], temp1[1]);
}
}
dataRows.add(row);
// print the header --- but first, get the header information by parsing the map representing the first data row
List<String> fieldNames = new ArrayList<String>();
Map<String, String> firstRow = dataRows.get(0);
for (String fieldName : firstRow.keySet()) {
fieldNames.add(fieldName);
}
for (String fieldName : fieldNames) {
System.out.print("\t" + fieldName);
}
// now, print all data rows
for (Map<String, String> dataRow : dataRows) {
System.out.println("\n");
for (String fieldName : fieldNames) {
System.out.print("\t" + dataRow.get(fieldName));}
Thank you all for your help.
OTHER TIPS
Have a look at the split
method of the String
class. You'll probably want to use it to split each line into the separate column=value
sections, then again to split the column from the value. That should get you started - ask again if you get stuck.
Okay, here goes. Here's a solution using Guava
First here's a helper class called Table
(not to be confused with Guava's Table
interface):
public class Table {
private static final MapSplitter MAP_SPLITTER =
Splitter.on(',').trimResults().withKeyValueSeparator(Splitter.on('='));
private final Multimap<String, String> values =
Multimaps.newListMultimap(
// keys are sorted, values maintain insertion order
Maps.<String, Collection<String>>newTreeMap(),
new Supplier<List<String>>() {
@Override public List<String> get() {
return Lists.newArrayList();
}
});
// keys are sorted, to correspond with the order of the other map
private final Map<String, Integer> lengths = Maps.newTreeMap();
private int modCount = 0;
public void addRow(final String row) {
modCount++;
final Map<String, String> lineData = MAP_SPLITTER.split(row);
final boolean empty = values.isEmpty();
if (!empty && values.keySet().size() != lineData.size()) {
throw new IllegalArgumentException("Bad row: " + row);
}
for (final Entry<String, String> entry : lineData.entrySet()) {
final String key = entry.getKey();
final String value = entry.getValue();
if (!empty && !values.containsKey(key)) {
throw new IllegalArgumentException(
"Bad column: " + key + " in row " + row);
}
final Integer tempLength;
if (empty) {
tempLength = key.length();
} else {
tempLength = lengths.get(key);
}
values.put(key, value);
lengths.put(key, Math.max(value.length(), tempLength));
}
}
public Iterable<String> getHeaders() {
return Collections.unmodifiableSet(values.asMap().keySet());
}
public Iterable<Integer> getColumnSizes() {
return Collections.unmodifiableCollection(lengths.values());
}
public Iterable<Iterable<String>> getData() {
return new Iterable<Iterable<String>>() {
@Override
public Iterator<Iterable<String>> iterator() {
return new RowIterator();
}
};
}
private class RowIterator extends AbstractIterator<Iterable<String>> {
private int rowIndex = -1;
private final int modCount = Table.this.modCount;
private final int maxRow =
values.asMap().values().iterator().next().size() - 1;
@Override
protected Iterable<String> computeNext() {
if (Table.this.modCount != modCount) {
throw new ConcurrentModificationException();
}
final Map<String, Collection<String>> map = values.asMap();
if (rowIndex++ == maxRow) {
return endOfData();
}
final List<String> data =
Lists.newArrayListWithCapacity(map.size());
for (final Collection<String> column : map.values()) {
data.add(((List<String>) column).get(rowIndex));
}
return Collections.unmodifiableCollection(data);
}
}
}
Now we can use this table class to format your data:
final String input = "columns=20xs, viewport_supported=true, wta=false, mmf=false\n"
+ "columns=11xs, viewport_supported=false, wta=false, mmf=true \n"
+ "columns=15xs, viewport_supported=true, wta=false, mmf=false";
final Table table = new Table();
final Iterable<String> lines = Splitter.on('\n').trimResults().split(input);
for (final String line : lines) {
// add one row of data
table.addRow(line);
}
// Using Appendable so you can easily switch to some other implementation,
// e.g. System.out
final Appendable appendable = new StringBuilder();
final Iterable<Integer> columnSizes = table.getColumnSizes();
final Iterable<String> headers = table.getHeaders();
final Iterator<String> headerIterator = headers.iterator();
final Iterable<Iterable<String>> data = table.getData();
{
// write headers
boolean first = true;
for (final Integer size : columnSizes) {
if (first) {
first = false;
} else {
appendable.append(" | ");
}
appendable.append(Strings.padEnd(headerIterator.next(), size, ' '));
}
appendable.append('\n');
}
{
// underline headers
boolean first = true;
for (final Integer size : columnSizes) {
if (first) {
first = false;
} else {
appendable.append("-+-");
}
appendable.append(Strings.repeat("-", size));
}
appendable.append('\n');
}
// write data
for (final Iterable<String> row : data) {
boolean first = true;
final Iterator<String> rowIterator = row.iterator();
for (final Integer size : columnSizes) {
if (first) {
first = false;
} else {
appendable.append(" | ");
}
appendable.append(Strings.padEnd(rowIterator.next(), size, ' '));
}
appendable.append('\n');
}
System.out.println(appendable);
And here's the Output:
columns | mmf | viewport_supported | wta
--------+-------+--------------------+------
20xs | false | true | false
11xs | true | false | false
15xs | false | true | false
This will be a lot more difficult if your lines have variable contents (not all lines contain the same columns), which is why I enforced that all rows must contain the same columns. Feel free to change that (or anything else).
I have recently done the exact same thing you are trying to do.
I am not sure how your data is stored, but for demonstration purpose I am using a String 'a'. Lets say String 'a' contains:
String a = 20xs, true, false;
then, you can use split()
function to split sentence String and separate them into word String. The following code does that:
a = a.trim();
String[] words = a.split(","); // creates array of Strings with each words in String a
// words would contain [20xs] [true] [false]
I have written a function that will pad spaces to the back of every words.
/** Add spaces to Strings so that all of the are of same number of characters
* @param str String to be padded
* @param n total number words String should be padded to
* @return str Padded string
*/
private String padString(String str, int n){
if(str.length() < n){
for(int j = str.length(); j < n; j++){
str += " ";
} // end for
} // end if
return str;
} // end padString
so now lets say if you do:
System.out.println(padString(words[0],15) + padString(words[1],15) + padString(words[2],15)
// So the result will be [20xs ] [true ] [false ]
You will get following in the console when you write above code:
20xs true false
you can pass higher value in padString()
function if you need bigger gaps.
As i can see the column heading is same all the time you can write the println code at the at first with the same amount of padding so it always starts at the beginning and acts like heading.
I hope this helps.