Convert comma separated String to List
List<String> items = Arrays.asList(str.split("\\s*,\\s*"));
The above code splits the string on a delimiter defined as: zero or more whitespace, a literal comma, zero or more whitespace which will place the words into the list and collapse any whitespace between the words and commas.
Please note that this returns simply a wrapper on an array: you CANNOT for example .remove() from the resulting List. For an actual ArrayList you must further use new ArrayList<String>.
Convert comma separated String to List
List<String> items = Arrays.asList(str.split("\\s*,\\s*"));
The above code splits the string on a delimiter defined as: zero or more whitespace, a literal comma, zero or more whitespace which will place the words into the list and collapse any whitespace between the words and commas.
Please note that this returns simply a wrapper on an array: you CANNOT for example .remove() from the resulting List. For an actual ArrayList you must further use new ArrayList<String>.
Arrays.asList returns a fixed-size List backed by the array. If you want a normal mutable java.util.ArrayList you need to do this:
List<String> list = new ArrayList<String>(Arrays.asList(string.split(" , ")));
Or, using Guava:
List<String> list = Lists.newArrayList(Splitter.on(" , ").split(string));
Using a Splitter gives you more flexibility in how you split the string and gives you the ability to, for example, skip empty strings in the results and trim results. It also has less weird behavior than String.split as well as not requiring you to split by regex (that's just one option).
Java 8 convert String of ints to List<Integer> - Stack Overflow
java - Split List<String[]> into List<List<String[]>> - Code Review Stack Exchange
regex - convert a string to a list of object using Java 8 - Stack Overflow
arraylist - split string and store it into HashMap java 8 - Stack Overflow
You need to split the string and make a Stream out of each parts. The method splitAsStream(input) does exactly that:
Pattern pattern = Pattern.compile(", ");
List<Integer> list = pattern.splitAsStream(ints)
.map(Integer::valueOf)
.collect(Collectors.toList());
It returns a Stream<String> of the part of the input string, that you can later map to an Integer and collect into a list.
Note that you may want to store the pattern in a constant, and reuse it each time it is needed.
Regular expression splitting is what you're looking for
Stream.of(ints.split(", "))
.map(Integer::parseInt)
.collect(Collectors.toList());
Motivations for improvement
I think "solving in 1 or 2 statements" is not a good target. Aiming for Java 8 features for the sake of using Java 8 features is also not a good target. A better target would be a combination of:
- correct and reasonably robust
- easy to understand
- reasonably efficient
Here's a bit shorter and more efficient, but pretty bad solution:
private List<List<String[]>> splitIntoBlocks(List<String[]> rows) {
List<List<String[]>> blocks = new ArrayList<>();
int start = 0;
for (int index = 0; index < rows.size(); index++) {
String marker = rows.get(index)[0];
if (marker.equals("3000")) {
start = index;
} else if (marker.equals("3900")) {
int end = index + 1;
blocks.add(rows.subList(start, end));
}
}
return blocks;
}
This is bad (as bad as the original code), because it doesn't validate the input. It will only work correctly with input in the assumed format, which it doesn't verify, and it doesn't detect or signal when something is wrong, which could lead to nasty bugs.
Validating inputs
The posted code assumes the input follows a certain format. It should at least document the assumptions, and state if the caller can be trusted to enforce those assumptions.
As you mentioned, the input comes from CSV files. Since the expected format is not captured by the List<String[]> type, I would be very cautious. A verification is needed somewhere to avoid bugs or undefined behavior, for example the following checks come to mind:
- There are the same number of START and END markers (you already did this one)
- The START and END markers don't overlap
- (optional?) There are no garbage records after an END and before the next START
- The rows are not empty (avoid crashing due to index out of bounds exceptions)
When any of these assumptions fail, I would either raise a custom exception that the caller can handle, or else throw IllegalArgumentException to fail fast.
So in fact, instead of making the posted code shorter, I'm suggesting to actually make it longer, by adding more validation logic.
Making the code easier to understand
Another answer already mentioned to improve the variable names. (I suggest following Java conventions with camelCase names, so startIndices and endIndices.)
The values "3000" and "3900" are magic strings. It would be better to use private static final constants for such values, so that they stand out, and with a descriptive name.
Building the lists of startIndices and endIndices use the same logic, duplicated. It would be good to extract to a helper method.
When returning a container type, it's usually more practical to return an empty container instead of null.
Low hanging fruit
l1 and l2 are very opaque variable names. start_indices and end_indices would be much more descriptive.
//get all position of 3000
List<Integer> l1 = IntStream.range(0,rows.size())
.filter(i->rows.get(i)[0].equals("3000"))
.boxed()
.collect(Collectors.toList());
This code takes int indices, boxes them into Integer indices, and collects them into a List<Integer>. This results in many small objects in memory.
We can avoid the boxing, by replacing .boxed().collect(Collectors.toList()) with .toArray() and changing the variable type to int[].
// get positions of all "3000"'s
int[] start_indices = IntStream.range(0,rows.size())
.filter(i->rows.get(i)[0].equals("3000"))
.toArray();
Apply similar change to the group end indices, and corresponding changes to the final res construction.
Single Pass
The above is still passing over the rows input 3 times; the first to collect the starting indices, the second to collect the ending indices, and the third time to assemble the result. Three passes means you cannot apply this grouping operation on an ephemeral stream; it requires a concrete collection that can be repeatedly iterated over.
Moreover, an IntStream is being used to generate indices into the rows list -- a hack -- which requires an ArrayList or similar \$O(1)\$-indexable collection to avoid quadratic time complexity.
It would be much better to perform a single pass over the stream of data, and collecting the required groups along the way:
private List<List<String[]>> splitIntoBlocks(List<String[]> rows) {
return rows.stream().collect(Block.collector());
}
Well, the above clearly solves the "looking for someone to solve this in perhaps 1 or 2 statements" request, but clearly additional work is still required. The Block.collector() class is required, writing of which (to handle possible splitting & parallel stream handling) is a non-trivial exercise. See Collector.of(...)
Use map(...) method to perform transformations on the original String. Instead of calling Fruit::valueOf through a method reference, split each string on space inside map(...), and construct a combined string when you get exactly two parts:
List<Fruit> f = pattern.splitAsStream("Red apple, blue banana, orange")
.map(s -> {
String[] parts = s.split(" ");
String tmp = parts.length == 2
? parts[0]+"_"+parts[1]
: s;
return Fruit.valueOf(tmp.toUpperCase());
}).collect(Collectors.toList());
Demo.
If you need to perform any additional transformations of the result, you can do them in the same lambda code block prior to the return statement.
Here is another sample:
f = pattern.splitAsStream(fruitString)
.map(s -> Arrays.stream(s.split(" ")).map(String::toUpperCase).collect(Collectors.joining("_")))
.map(Fruit::valueOf).collect(Collectors.toList());
Or by StreamEx:
StreamEx.split(fruitString, ", ")
.map(s -> StreamEx.split(s, " ").map(String::toUpperCase).joining("_"))
.map(Fruit::valueOf).toList();
Unless Splitter is doing any magic, the getTokenizeString method is obsolete here. You can perform the entire processing as a single operation:
Map<String,String> map = Pattern.compile("\\s*-\\s*")
.splitAsStream(responseString.trim())
.map(s -> s.split("~", 2))
.collect(Collectors.toMap(a -> a[0], a -> a.length>1? a[1]: ""));
By using the regular expression \s*-\s* as separator, you are considering white-space as part of the separator, hence implicitly trimming the entries. There’s only one initial trim operation before processing the entries, to ensure that there is no white-space before the first or after the last entry.
Then, simply split the entries in a map step before collecting into a Map.
First of all, you don't have to split the same String twice.
Second of all, check the length of the array to determine if a value is present for a given key.
HashMap<String, String> map=
list.stream()
.map(s -> s.split("~"))
.collect(Collectors.toMap(a -> a[0], a -> a.length > 1 ? a[1] : ""));
This is assuming you want to put the key with a null value if a key has no corresponding value.
Or you can skip the list variable :
HashMap<String, String> map1 =
MyClass.getTokenizeString(responseString, "-")
.stream()
.map(s -> s.split("~"))
.collect(Collectors.toMap(a -> a[0], a -> a.length > 1 ? a[1] : ""));
You should be using java 8 to run this code. Just take those strings and split them on "." split method of java need regex so to match "." you need "\.".Then transform array to list, then add words to list.
public static void main(String[] args) {
List<String> words = new ArrayList<String>();
List<String> list = new ArrayList<String>();
list.add("One.two.three");
list.add("one.two.four");
list.stream().forEach(str -> {
words.addAll(Arrays.asList(str.split("\\.")));
});
System.out.println(words.toString());
//output : [One, two, three, one, two, four]
}
For java 8+, you can use flatmap as -
String[] words = list.stream().flatMap(str -> Arrays.stream(str.split("\\."))).toArray(String[]::new);
If you're using Java 8, you can:
int[] numbers = Arrays.asList(numbersArray.split(",")).stream()
.map(String::trim)
.mapToInt(Integer::parseInt).toArray();
If not, I think your approach is the best option available.
Using java 8 Streams:
List<Integer> longIds = Stream.of(commaSeperatedString.split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());