Java Streams: Is there a cleaner way of doing this? - java

I am using streams to concatenate a series of strings and add commas between them, but there must be no comma at the beginning or the end of the result string.
import java.util.Arrays;
import java.util.List;
public class QuestionNine {
public static void main(String[] args) {
new QuestionNine().launch();
}
public void launch(){
List<String> words = Arrays.asList("Hello", "Bonjour", "engine", "Hurray", "What",
"Dog", "boat", "Egg", "Queen", "Soq", "Eet");
String result = (words.stream().map(str -> str + ",").reduce("", (a,b) -> a + b));
result = result.substring(0, result.length() -1); //removes last comma
System.out.println(result);
}
}
Instead of using the String.substring() method at the end to get rid of the last comma, is there a way i could have deleted the last comma within the stream pipeline?

The usual idiom is to use the joining Collector with Streams.
String res = words.stream().collect(Collectors.joining(","));
Although you can use String.join in your case since you are directly dealing with an Iterable.
String res = String.join(",", words);
The problem with your approach is that the mapping function you apply impose that there will be a comma at the end of each word. You could get rid of this mapping; and apply the reduce function such that you get the desired output:
.stream().reduce("", (a,b) -> a.isEmpty() ? b : a+","+b);
but I don't recommend this.

Yes, you can use Collectors.joining() here:
String joined = words.stream().collect(Collectors.joining(", "));
Or, also as noted from comments, you can use newly added String.join(CharSequence, Iterable) method.
String joined = String.join(", ", words);

Related

Java-Stream & Optional - Find a value that matches to a stream-element or provide a Default value

I have a Dictionary object which consists of several entries:
record Dictionary(String key, String value, String other) {};
I would like to replace words in the given String my a which are present as a "key" in one of the dictionaries with the corresponding value. I can achieve it like this, but I guess, there must be a better way to do this.
An example:
> Input: One <sup>a</sup> Two <sup>b</sup> Three <sup>D</sup> Four
> Output: One [a-value] Two [b-value] Three [D] Four
The code to be improved:
public class ReplaceStringWithDictionaryEntries {
public static void main(String[] args) {
List<Dictionary> dictionary = List.of(new Dictionary("a", "a-value", "a-other"),
new Dictionary("b", "b-value", "b-other"));
String theText = "One <sup>a</sup> Two <sup>b</sup> Three <sup>D</sup> Four";
Matcher matcher = Pattern.compile("<sup>([A-Za-z]+)</sup>").matcher(theText);
StringBuilder sb = new StringBuilder();
int matchLast = 0;
while (matcher.find()) {
sb.append(theText, matchLast, matcher.start());
Optional<Dictionary> dict = dictionary.stream().filter(f -> f.key().equals(matcher.group(1))).findFirst();
if (dict.isPresent()) {
sb.append("[").append(dict.get().value()).append("]");
} else {
sb.append("[").append(matcher.group(1)).append("]");
}
matchLast = matcher.end();
}
if (matchLast != 0) {
sb.append(theText.substring(matchLast));
}
System.out.println("Result: " + sb.toString());
}
}
Output:
Result: One [a-value] Two [b-value] Three [D] Four
Do you have a more elegant way to do this?
Since Java 9, Matcher#replaceAll can accept a callback function to return the replacement for each matched value.
String result = Pattern.compile("<sup>([A-Za-z]+)</sup>").matcher(theText)
.replaceAll(mr -> "[" + dictionary.stream().filter(f -> f.key().equals(mr.group(1)))
.findFirst().map(Dictionary::value)
.orElse(mr.group(1)) + "]");
Create a map from your list using key as key and value as value, use the Matcher#appendReplacement method to replace matches using the above map and calling Map.getOrDefault, use the group(1) value as default value. Use String#join to put the replacements in square braces
public static void main(String[] args) {
List<Dictionary> dictionary = List.of(
new Dictionary("a", "a-value", "a-other"),
new Dictionary("b", "b-value", "b-other"));
Map<String,String> myMap = dictionary.stream()
.collect(Collectors.toMap(Dictionary::key, Dictionary::value));
String theText = "One <sup>a</sup> Two <sup>b</sup> Three <sup>D</sup> Four";
Matcher matcher = Pattern.compile("<sup>([A-Za-z]+)</sup>").matcher(theText);
StringBuilder sb = new StringBuilder();
while (matcher.find()) {
matcher.appendReplacement(sb,
String.join("", "[", myMap.getOrDefault(matcher.group(1), matcher.group(1)), "]"));
}
matcher.appendTail(sb);
System.out.println(sb.toString());
}
record Dictionary( String key, String value, String other) {};
Map vs List
As #Chaosfire has pointed out in the comment, a Map is more suitable collection for the task than a List, because it eliminates the need of iterating over collection to access a particular element
Map<String, Dictionary> dictByKey = Map.of(
"a", new Dictionary("a", "a-value", "a-other"),
"b", new Dictionary("b", "b-value", "b-other")
);
And I would also recommend wrapping the Map with a class in order to provide continent access to the string-values of the dictionary, otherwise we are forced to check whether a dictionary returned from the map is not null and only then make a call to obtain the required value, which is inconvenient. The utility class can facilitate getting the target value in a single method call.
To avoid complicating the answer, I would not implement such a utility class, and for simplicity I'll go with a Map<String,String> (which basically would act as a utility class intended to act - providing the value within a single call).
public static final Map<String, String> dictByKey = Map.of(
"a", "a-value",
"b", "b-value"
);
Pattern.splitAsStream()
We can replace while-loop with a stream created via splitAsStream() .
In order to distinguish between string-values enclosed with tags <sup>text</sup> we can make use of the special constructs which are called Lookbehind (?<=</sup>) and Lookahead (?=<sup>).
(?<=foo) - matches a position that immediately precedes the foo.
(?=foo) - matches a position that immediately follows after the foo;
For more information, have a look at this tutorial
The pattern "(?=<sup>)|(?<=</sup>)" would match a position in the given string right before the opening tag and immediately after the closing tag. So when we apply this pattern splitting the string with splitAsStream(), it would produce a stream containing elements like "<sup>a</sup>" enclosed with tags, and plain string like "One", "Two", "Three".
Note that in order to reuse the pattern without recompiling, it can be declared on a class level:
public static final Pattern pattern = Pattern.compile("(?=<sup>)|(?<=</sup>)");
The final solution would result in lean and simple stream:
public static void foo(String text) {
String result = pattern.splitAsStream(text)
.map(str -> getValue(str)) // or MyClass::getValue
.collect(Collectors.joining());
System.out.println(result);
}
Instead of tackling conditional logic inside a lambda, it's often better to extract it into a separate method (sure, you can use a ternary operator and place this logic right inside the map operation in the stream if you wish instead of having this method, but it'll be a bit messy):
public static String getValue(String str) {
if (str.matches("<sup>\\p{Alpha}+</sup>")) {
String key = str.replaceAll("<sup>|</sup>", "");
return "[" + dictByKey.getOrDefault(key, key) + "]";
}
return str;
}
main()
public static void main(String[] args) {
foo("One <sup>a</sup> Two <sup>b</sup> Three <sup>D</sup> Four");
}
Output:
Result: One [a-value] Two [b-value] Three [D] Four
A link to Online Demo

Removing special characters from string

I want to parse this string into a list and return {1.193493, 54.6333, 2.093077, 31.6235, 6.175355, 21.6479}. How do I get rid of the square brackets???? I used a for loop and replace but it doesn't work.
String st = "[[[1.193493,54.6333],[2.093077,31.6235],[6.175355,21.6479]]]"
String[] parsed = st.split(",");
for (String next : parsed) {
next.replace("//[", "").replace("//]", "");
}
replace() works with plain Strings, not regex. Therefore you can simply use:
next.replace("[", "").replace("]", "");
Also notice that you need to assign it to some string variable; assigning it to need won't work (won't modify elements in parsed array).
You should actually remove the braces first and split later, like this:
String[] parsed = st.replace("[", "").replace("]", "").split(",");
You can do it all at one time with this regex:
(?:\D*(\d*\.\d*))+
You can do this with one line:
List<String> list = Arrays.asList(t.replaceAll("\\[", "").replaceAll("\\]", "").split(","));
Full code:
package com.stackoverflow;
import java.util.Arrays;
import java.util.List;
public class Demo
{
public static void main(String[] args)
{
String t = "[[[1.193493,54.6333],[2.093077,31.6235],[6.175355,21.6479]]]";
List<String> list = Arrays.asList(t.replaceAll("\\[", "").replaceAll("\\]", "").split(","));
for(String s : list)
{
System.out.println(s);
}
}
}
Converts string to list of strings and prints it. If you need to convert strings to doubles, use Double.parseDouble(s) in loop.
You could achieve your goal in two steps.
1) Remove all special characters except comma (,)
2) Then split by comma (,)
public static void main(String[] args) {
String st = "[[[1.193493,54.6333],[2.093077,31.6235],[6.175355,21.6479]]]";
String[] parsed = st.replaceAll("[^\\d.,]+", "").split(",");
}
Output:

How to format a list of strings into [duplicate]

This question already has answers here:
Create formatted string from ArrayList
(7 answers)
Closed 3 years ago.
I have a list of strings that I want to format each of them in the same way. e.g. myListOfStrings = str1, str2, str3, and my format is (%s)
I want to have something like this:
String.format(" (%s) ", myListOfStrings)
Will output
(str1) (str2) (str3)
Is there an elegant way of doing this? or do I have to use a string builder and do a foreach loop on all the strings?
You can do this with Java 8:
import static java.util.stream.Collectors.joining;
public static void main(String[] args) throws Exception {
final List<String> strings = Arrays.asList("a", "b", "c");
final String joined = strings.stream()
.collect(joining(") (", "(", ")"));
System.out.println(joined);
}
Or:
final String joined = strings.stream()
.map(item -> "(" + item + ")")
.collect(joining(" "));
Which one you prefer is a matter of personal preference.
The first joins the items on ) ( which gives:
a) (b) (c
Then you use the prefix and suffix arguments to joining to with prefix with ( and suffix with ), to produce the right outcome.
The second alternative transforms each item to ( + item + ) and then joins them on " ".
The first might also be somewhat faster, as it only requires the creation of one StringBuilder instance - for both the join and the pre/suffix. The second alternative requires the creation of n + 1 StringBuilder instances, one for each element and one for the join on " ".
if you want a one-line solution, you could use one of the the StringUtils.join methods in Apache Commons Lang.
String result = "(" + StringUtils.join(myListOfStrings, ") (") + ")";
You can try this:
List<String> list = new ArrayList<String>();
list.add("str1");
list.add("str2");
list.add("str3");
for(String s : list) {
System.out.print(String.format(" (%s) ", s));
}
String strOut = "";
for(int i=0; i< myListOfStrings.size(); i++){
strOut = strOut +"("+myListOfStrings.get(i)+")";
}
Boris The Spider's answer is what I would go with, but in case you are not using java 8, but maybe you are using Guava you can do something like this, albeit it is a bit verbose:
Joiner.on("").join(Collections2.transform(myListOfStrings, new Function<String, String>() {
#Override
public String apply(String input) {
return String.format(" (%s) ", input);
}
}));
Using Java8 new forEach method for iterating over collections:
public static String format(List<String> list) {
StringBuilder sb = new StringBuilder();
list.forEach(x -> sb.append(String.format(" (%s) ", x)));
return sb.toString();
}
Try it here: https://ideone.com/52EKRH

Convert Comma Separated Values to List<Long>

Assume I have a set of numbers like 1,2,3,4,5,6,7 input as a single String. I would like to convert those numbers to a List of Long objects ie List<Long>.
Can anyone recommend the easiest method?
You mean something like this?
String numbers = "1,2,3,4,5,6,7";
List<Long> list = new ArrayList<Long>();
for (String s : numbers.split(","))
list.add(Long.parseLong(s));
System.out.println(list);
Since Java 8 you can rewrite it as
List<Long> list = Stream.of(numbers.split(","))
.map(Long::parseLong)
.collect(Collectors.toList());
Little shorter versions if you want to get List<String>
List<String> fixedSizeList = Arrays.asList(numbers.split(","));
List<String> resizableList = new ArrayList<>(fixedSizeList);
or one-liner
List<String> list = new ArrayList<>(Arrays.asList(numbers.split(",")));
Bonus info:
If your data may be in form like String data = "1, 2 , 3,4"; where comma is surrounded by some whitespaces, the split(",") will produce as result array like ["1", " 2 ", " 3", "4"].
As you see second and third element in that array contains those extra spaces: " 2 ", " 3" which would cause Long.parseLong to throw NumberFormatException (since space is not proper numerical value).
Solution here is either:
using String#trim on those individual elements before parsing like Long.parseLong(s.trim())
consuming those extra whitespace along , while splitting. To do that we can use split("\\s*,\\s*") where
\s (written as "\\s" in string literals) represents whitespace
* is quantifier representing zero or more
so "\\s*" represents zero or more whitespaces (in other words makes it optional)
Simple and handy solution using java-8 (for the sake of completion of the thread):
String str = "1,2,3,4,5,6,7";
List<Long> list = Arrays.stream(str.split(",")).map(Long::parseLong).collect(Collectors.toList());
System.out.println(list);
[1, 2, 3, 4, 5, 6, 7]
Even better, using Pattern.splitAsStream():
Pattern.compile(",").splitAsStream(str).map(Long::parseLong).collect(Collectors‌​.toList());
String input = "1,2,3,4,5,6,7";
String[] numbers = input.split("\\,");
List<Integer> result = new ArrayList<Integer>();
for(String number : numbers) {
try {
result.add(Integer.parseInt(number.trim()));
} catch(Exception e) {
// log about conversion error
}
}
You can use String.split() and Long.valueOf():
String numbers = "1,2,3,4,5,6,7";
List<Long> list = new ArrayList<Long>();
for (String s : numbers.split(","))
list.add(Long.valueOf(s));
System.out.println(list);
If you're not on java8 and don't want to use loops, then you can use Guava
List<Long> longValues = Lists.transform(Arrays.asList(numbersArray.split(",")), new Function<String, Long>() {
#Override
public Long apply(String input) {
return Long.parseLong(input.trim());
}
});
As others have mentioned for Java8 you can use Streams.
List<Long> numbers = Arrays.asList(numbersArray.split(","))
.stream()
.map(String::trim)
.map(Long::parseLong)
.collect(Collectors.toList());
I've used the following recently:
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
...
final ImmutableList<Long> result = Splitter.on(",")
.trimResults()
.omitEmptyStrings()
.splitToStream(value)
.map(Long::valueOf)
.collect(toImmutableList());
This uses Splitter from Guava (to handle empty strings and whitespaces) and does
not use the surprising String.split().
I would use the excellent google's Guava library to do it. String.split can cause many troubles.
String numbers="1,2,3,4,5,6,7";
Iterable<String> splitIterator = Splitter.on(',').split(numbers);
List<String> list= Lists.newArrayList(splitIterator );

Fastest way to put contents of Set<String> to a single String with words separated by a whitespace?

I have a few Set<String>s and want to transform each of these into a single String where each element of the original Set is separated by a whitespace " ".
A naive first approach is doing it like this
Set<String> set_1;
Set<String> set_2;
StringBuilder builder = new StringBuilder();
for (String str : set_1) {
builder.append(str).append(" ");
}
this.string_1 = builder.toString();
builder = new StringBuilder();
for (String str : set_2) {
builder.append(str).append(" ");
}
this.string_2 = builder.toString();
Can anyone think of a faster, prettier or more efficient way to do this?
With commons/lang you can do this using StringUtils.join:
String str_1 = StringUtils.join(set_1, " ");
You can't really beat that for brevity.
Update:
Re-reading this answer, I would prefer the other answer regarding Guava's Joiner now. In fact, these days I don't go near apache commons.
Another Update:
Java 8 introduced the method String.join()
String joined = String.join(",", set);
While this isn't as flexible as the Guava version, it's handy when you don't have the Guava library on your classpath.
If you are using Java 8, you can use the native
String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
method:
Returns a new String composed of copies of the CharSequence elements joined together with a copy of the specified delimiter.
For example:
Set<String> strings = new LinkedHashSet<>();
strings.add("Java"); strings.add("is");
strings.add("very"); strings.add("cool");
String message = String.join("-", strings);
//message returned is: "Java-is-very-cool"
Set implements Iterable, so simply use:
String.join(" ", set_1);
As a counterpoint to Seanizer's commons-lang answer, if you're using Google's Guava Libraries (which I'd consider the 'successor' to commons-lang, in many ways), you'd use Joiner:
Joiner.on(" ").join(set_1);
with the advantage of a few helper methods to do things like:
Joiner.on(" ").skipNulls().join(set_1);
// If 2nd item was null, would produce "1, 3"
or
Joiner.on(" ").useForNull("<unknown>").join(set_1);
// If 2nd item was null, would produce "1, <unknown>, 3"
It also has support for appending direct to StringBuilders and Writers, and other such niceties.
Maybe a shorter solution:
public String test78 (Set<String> set) {
return set
.stream()
.collect(Collectors.joining(" "));
}
or
public String test77 (Set<String> set) {
return set
.stream()
.reduce("", (a,b)->(a + " " + b));
}
but native, definitely faster
public String test76 (Set<String> set) {
return String.join(" ", set);
}
I don't have the StringUtil library available (I have no choice over that) so using standard Java I came up with this ..
If you're confident that your set data won't include any commas or square brackets, you could use:
mySet.toString().replaceAll("\\[|\\]","").replaceAll(","," ");
A set of "a", "b", "c" converts via .toString() to string "[a,b,c]".
Then replace the extra punctuation as necesary.
Filth.
I use this method:
public static String join(Set<String> set, String sep) {
String result = null;
if(set != null) {
StringBuilder sb = new StringBuilder();
Iterator<String> it = set.iterator();
if(it.hasNext()) {
sb.append(it.next());
}
while(it.hasNext()) {
sb.append(sep).append(it.next());
}
result = sb.toString();
}
return result;
}
I'm confused about the code replication, why not factor it into a function that takes one set and returns one string?
Other than that, I'm not sure that there is much that you can do, except maybe giving the stringbuilder a hint about the expected capacity (if you can calculate it based on set size and reasonable expectation of string length).
There are library functions for this as well, but I doubt they're significantly more efficient.
This can be done by creating a stream out of the set and then combine the elements using a reduce operation as shown below (for more details about Java 8 streams check here):
Optional<String> joinedString = set1.stream().reduce(new
BinaryOperator<String>() {
#Override
public String apply(String t, String u) {
return t + " " + u;
}
});
return joinedString.orElse("");

Categories

Resources