I'm attempting a block of code that I can't get quite right. I have a list with say, 1000 string entries (of 3 - 10 characters each) that I collect into a single comma-delimited string. If the total size of the characters in the resulting string is more than 8100, I need to split the list and create TWO comma-delimited strings (or 3, or whatever factor of 8100). I know this needs a groupingBy, but my syntax isn't working.
So my first question is, how can I determine the sum of the characters in my list, and how can I group the list such that there are no more than 8100 characters in each group of lists? This is what I have so far:
AtomicInteger counter = new AtomicInteger();
String codes = configInfos.stream()
.map(ConfigInfo::getCode)
.collect(Collectors.groupingBy(it -> counter.getAndIncrement() / 8100))
This needs to be solved with Streams?
Otherwise I would solve it like this:
String [] array = configInfos
.stream()
.map( ConfigInfo::getCode )
.toArray( String []::new );
List<String> codes = new ArrayList<>();
StringJoiner joiner = new StringJoiner( "," );
for( String s : array )
{
if( joiner.length() + 1 + s.length() > 8100 )
{
codes.add( joiner.toString() );
joiner = new StringJoiner( "," );
}
joiner.add( s );
}
if( joiner.length() > 0 ) codes.add( joiner.toString() );
But I have to confess, I have no clue how to solve this with Streams …
Edit: don’t use streams for this
I need the string maintained when I do the split. It has to look like
the original list, with list 1 being "Entries", "are". List 2 would be
"three", "upto". List 3 would be "ten", "chars", and so on.
Stream operations are not suited for your task. I recommend you use a classical loop for the clearest and easiest to maintain code.
Original answer: Intstream.range()
Not sure this is really what you want. In case you prefer to use a stream, here’s my attempt at that.
final int maxSubstringLength = 9; // 8100
List<String> entries = List.of("Entries", "are", "three", "upto", "ten", "chars", "each");
String totalString = entries.stream().collect(Collectors.joining(","));
// round up in the division
int substringCount = (totalString.length() + maxSubstringLength - 1) / maxSubstringLength;
List<String> substrings = IntStream.range(0, substringCount)
.mapToObj(i -> totalString.substring(i * maxSubstringLength, Math.min((i + 1) * maxSubstringLength, totalString.length())))
.collect(Collectors.toList());
substrings.forEach(System.out::println);
Output:
Entries,a
re,three,
upto,ten,
chars,eac
h
For your very long string just put 8100 as max substring length where I put 9 for the demonstration.
Related
I've got:
String s = "ZpglnRxqenU"
I need to assign a number to each character in the string like:
z-1
p-2
g-3
l-4
n-5
r-6
x-7
q-8
e-9
n-10
u-11
I do not want to count the frequency of characters.
I tried to use HashMap:
Map<String, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
map.put(String.valueOf(s.charAt(i)), i + 1);
}
But Map a has unique key and I lost first n occurrence
How do I count letters?
If you want to count the number of characters in a string use s.length();.
If you want to count the number of different characters in a String, you already can with the code you wrote. map.size() will give exactly that, because the map only stores each key once (in your case the 'letters' (they are called char's in java, chars is a diminutive for characters)).
How put() in maps work:
The first time you put a key to the map, it is added with the value you give, the second time the value is changed.
Swap the key-value of your map. Use the number for the key, as it will be unique.
Use a NavigableMap to keep them in order.
NavigableMap< Integer , String > = new TreeMap<>() ;
Example code using conventional style.
String input = "ZpglnRxqenU";
int[] codePoints = input.codePoints().toArray();
NavigableMap < Integer, String > numberedCharacters = new TreeMap <>();
for ( int ordinal = 1 ; ordinal <= codePoints.length ; ordinal++ )
{
numberedCharacters.putIfAbsent(
ordinal ,
Character.toString( codePoints[ ordinal - 1 ] )
);
}
Example code using streams & lambdas. Same effect, not necessarily better in this particular case.
String input = "ZpglnRxqenU";
int[] codePoints = input.codePoints().toArray();
NavigableMap < Integer, String > numberedCharacters =
IntStream
.rangeClosed( 1 , codePoints.length )
.boxed()
.collect(
Collectors.toMap(
Function.identity() ,
ordinal -> Character.toString( codePoints[ ordinal - 1 ] ) ,
( o1 , o2 ) -> o1 ,
TreeMap :: new )
);
To get all the characters from the map, call values. The resulting Collection object promises to iterate in the order of iteration of the map’s keys.
String recreated = String.join( "" , numberedCharacters.values() );
Dump to console.
System.out.println( "input = " + input );
System.out.println( "numberedCharacters = " + numberedCharacters );
System.out.println( "recreated = " + recreated );
When run.
input = ZpglnRxqenU
numberedCharacters = {1=Z, 2=p, 3=g, 4=l, 5=n, 6=R, 7=x, 8=q, 9=e, 10=n, 11=U}
recreated = ZpglnRxqenU
Basil's solution should be good enough for what you want but if you strictly want to model like in your example you can have a List<Pair<Character, Integer>> as a storage for your data. There are many libraries offering Pair or Tuplestructures or you could just create your own.
Populating your list is a matter of personal taste but in essence you iterate over the characters array, map each character and its index to a new Pair(char, idx) then collect them in a list
If I have a List< Integer > whose integer values are Unicode code point numbers. How do I construct a String object of characters determined by those code points?
For example:
List < Integer > codePoints = List.of( 100, 111, 103, 128054 ) ;
… or:
List < Integer > codePoints = "cat".codePoints().boxed().toList();
How do I get another String object with value cat from codePoints?
String has a constructor that takes an array of int code point numbers.
int[] ints = codePoints.stream().mapToInt(i -> i).toArray();
String string = new String(ints, 0, ints.length);
Converts List -> Stream -> IntStream -> int[] -> String
List ➠ Stream ➠ StringBuilder ➠ String
One solution is to convert your List into a Stream. Then collect the elements of that stream into a StringBuilder. The StringBuilder class offers an appendCodePoint method specifically to accommodate code point integer numbers. When the mutable StringBuilder is complete, convert to an immutable String.
String output = codePoints.stream().collect( StringBuilder :: new , StringBuilder :: appendCodePoint , StringBuilder :: append ).toString();
Or different formatting:
String output =
codePoints
.stream()
.collect( StringBuilder :: new , StringBuilder :: appendCodePoint , StringBuilder :: append )
.toString();
Here is some example code.
String input = "dog🐶" ;
List < Integer > codePoints = input.codePoints().boxed().collect( Collectors.toList() ); // In Java 16+, replace the last part with simply `.toList()`.
String output =
codePoints
.stream()
.collect( StringBuilder :: new , StringBuilder :: appendCodePoint , StringBuilder :: append )
.toString();
See this code run live at IdeOne.com.
input: dog🐶
codePoints: [100, 111, 103, 128054]
output: dog🐶
To understand how that code with StringBuilder method references works, see Java 8 Int Stream collect with StringBuilder.
We could make a utility method of this code, for convenience. For safety, we could add a call to .filter to skip any invalid code point number (either negative or beyond Character.MAX_CODE_POINT).
public static final String listOfCodePointsToString( List< Integer > codePoints )
{
String output =
codePoints
.stream()
.filter( codePoint -> Character.isValidCodePoint( codePoint ) )
.collect( StringBuilder :: new , StringBuilder :: appendCodePoint , StringBuilder :: append )
.toString();
return output ;
}
See that code run live at IdeOne.com.
The existing answers are fine, but there is also a simple "old fashioned" approach that doesn't require the use of functional interfaces or streams. Here's minimal and complete sample code:
package cp2string;
import java.util.List;
public class CP2String {
public static void main(String[] args) {
List< Integer> codePoints = List.of(100, -999, 111, 103, 128054);
Character BLACK_VERTICAL_RECTANGLE = '\u25AE';
StringBuilder sb = new StringBuilder();
for (int cp : codePoints) {
sb.append(Character.toString(Character.isValidCodePoint(cp) ? cp : BLACK_VERTICAL_RECTANGLE));
}
System.out.println("sb=" + sb.toString());
}
}
When the code is run this is the output:
sb=d▮og🐶
Notes:
The black rectangle in the output represents an invalid code point that was deliberately included in the sample data.
JDK 11 or later is required for the static method Character.toString(int codePoint).
A font change may be needed to properly render the output. I used Segoe UI Symbol.
Obviously using a stream based approach is the way to go if parallel operations are a consideration, but for scenarios where performance and scalability are not concerns, a simple approach is arguably just as good.
My Question is inspired by this Question, but is aimed at using Java Streams to arrive an a List<Integer>.
I have this code that kind of works. It seems to be returning an ArrayList, presumably ArrayList<Integer>. But the compiler refuses to let me declare the result as such.
String input = "1 2 3 4 5";
Stream stream = Arrays.stream( input.split( " " ) );
var x = stream.map( s -> Integer.valueOf( ( String ) s ) ).collect( Collectors.toList() );
This runs when using the new var feature of recent Java versions.
System.out.println( x.getClass() );
System.out.println( x );
class java.util.ArrayList
[1, 2, 3, 4, 5]
I have two questions:
Why is x reported as an ArrayList yet I cannot declare x to be an ArrayList (error: incompatible types), such as:ArrayList<Integer> x = stream.map( s -> Integer.valueOf( ( String ) s ) ).collect( Collectors.toList() );
Is there a better way to use streams to convert this string of digits to a List of Integer?
First, your Stream is raw. Using a raw type means that anything that uses a type parameter is erased to its upper bound, here Object. So map returns another raw Stream and collect returns an Object. Easy fix: Stream<String>.
Stream<String> stream = Arrays.stream( input.split( " " ) );
Second, Collectors.toList is specified to return List<T>, or List<String> here.
There are no guarantees on the type, mutability, serializability, or thread-safety of the List returned; if more control over the returned List is required, use toCollection(Supplier).
If you aren't satisfied with List and you absolutely need an ArrayList, supply one:
.collect(Collectors.toCollection(ArrayList::new));
Incidentally, you can replace the lambda expression
s -> Integer.valueOf( ( String ) s )
with the method reference
Integer::valueOf
After these changes, your code might look like this:
String input = "1 2 3 4 5";
Stream< String > stream = Arrays.stream( input.split( " " ) );
List< Integer > x = stream.map( Integer::valueOf ).collect( Collectors.toList() );
Or, if you insist on precisely ArrayList rather than List, make that:
String input = "1 2 3 4 5";
Stream< String > stream = Arrays.stream( input.split( " " ) );
ArrayList< Integer > x = stream.map( Integer::valueOf ).collect( Collectors.toCollection( ArrayList::new ) );
Once these changes are made, this seems like a reasonably good way of converting a string containing space-separated integers to an ArrayList<Integer>. A minor improvement would be to change the split regular expression argument to "\\s+", to represent one or more whitespace characters. In case "1 2" arrives, with multiple spaces between numbers, this will prevent empty strings that would match in between space characters.
You can create an ArrayList, but you shouldn't:
ArrayList<Integer> x =
stream.map(Integer::valueOf)
.collect(Collectors.toCollection(ArrayList::new));
Refactoring:
List<Integer> x =
Arrays.stream(input.split(" "))
.map(Integer::valueOf)
.collect(Collectors.toList());
Or, with the Pattern.splitAsStream:
List<Integer> x =
Pattern.compile(" ").splitAsStream("1 2 3 4 5")
.map(Integer::valueOf)
.collect(Collectors.toList());
List<Integer> list = Arrays.stream("1 2 3 4 5".split(" ")).map(s -> Integer.valueOf(s)).collect(Collectors.toList());
This compiles. According to the collectors source code: "There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {#code List} returned;"
Why is x reported as an ArrayList yet I cannot declare x to be an ArrayList.
Because
collect(...) is returning a statically inferred type, based on the type of the result of Collectors.toList().
That is (in this case) Collector<String,?,List<String>> ... according to the Collectors javadoc.
The fact that the actual object is an ArrayList is an implementation detail that could conceivably change in future versions of Java.
Is there a better way to use streams to convert this string of digits to a List of Integer?
I will leave that to others to say. However, I think that a simpler / cleaner way to do this is to not use streams for this task.
String input = "1 2 3 4 5";
var x = new ArrayList<>(Arrays.asList(input.split(" ")));
The above code is simpler and most likely more efficient than the streams-based versions I have seen.
Does it exist better way to parse String to Integer using stream than this :
String line = "1 2 3 4 5";
List<Integer> elements = Arrays.stream(line.split(" ")).mapToInt(x -> Integer.parseInt(x))
.boxed().collect(Collectors.toList());
You can eliminate one step if you parse the String directly to Integer:
String line = "1 2 3 4 5";
List<Integer> elements = Arrays.stream(line.split(" ")).map(Integer::valueOf)
.collect(Collectors.toList());
Or you can stick to primitive types, which give better performance, by creating an int array instead of a List<Integer>:
int[] elements = Arrays.stream(line.split(" ")).mapToInt(Integer::parseInt).toArray ();
You can also replace
Arrays.stream(line.split(" "))
with
Pattern.compile(" ").splitAsStream(line)
I'm not sure which is more efficient, though.
There's one more way to do it that will be available since java-9 via Scanner#findAll:
int[] result = scan.findAll(Pattern.compile("\\d+"))
.map(MatchResult::group)
.mapToInt(Integer::parseInt)
.toArray();
I want to learn how I can check the range of a list in java 8.
For example,
My Code:
List<String> objList = new ArrayList<>();
objList.add("Peter");
objList.add("James");
objList.add("Bart");
objList.stream().map((s) -> s + ",").forEach(System.out::print);
My out come is
Peter,James,Bart,
but I want to know how I can get rid of the last ,
Note: I know I must use filter here , yet I do not know how and I know there is another way to solve this which is as follows
String result = objList.stream()
.map(Person::getFirstName)
.collect(Collectors.joining(","));
yet I want to know how to check the range and get rid of , in my first code.
There's no direct way to get the index of a stream item while you're processing the items themselves. There are several alternatives, though.
One way is to run the stream over the indexes and then get the elements from the list. For each element index it maps i to the i'th element and appends a "," for all indexes except the last:
IntStream.range(0, objList.size())
.mapToObj(i -> objList.get(i) + (i < objList.size()-1 ? "," : ""))
.forEach(System.out::print);
A second, more concise variation is to special case the first element instead of the last one:
IntStream.range(0, objList.size())
.mapToObj(i -> (i > 0 ? "," : "") + objList.get(i))
.forEach(System.out::print);
A third way is to use the particular reduce operation that is applied "between" each two adjacent elements. The problem with this technique is that it does O(n^2) copying, which will become quite slow for large streams.
System.out.println(objList.stream().reduce((a,b) -> a + "," + b).get());
A fourth way is to special-case the last element by limiting the stream to length n-1. This requires a separate print statement, which isn't as pretty though:
objList.stream()
.limit(objList.size()-1)
.map(s -> s + ",")
.forEach(System.out::print);
System.out.print(objList.get(objList.size()-1));
A fifth way is similar to the above, special-casing the first element instead of the last:
System.out.print(objList.get(0));
objList.stream()
.skip(1)
.map(s -> "," + s)
.forEach(System.out::print);
Really, though the point of the joining collector is to do this ugly and irritating special-casing for you, so you don't have to do it yourself.
You could do this:
objList.stream().flatMap((s) -> Stream.of(s, ','))
.limit(objList.size() * 2 - 1).forEach(System.out::print);
flatMap replaces each element of the original stream with the elements in the streams returned from the mapping function.
So if your stream was originally
"Peter" - "James" - "Bart"
The above mapping function changes it to
"Peter" - "," - "James" - "," - "Bart" - ","
Then the limit removes the last "," by shortening the stream to be at most the length of the value that is passed to it, which in this case is the size of the stream - 1. The size of the stream was 2 * the size of the list before limit because flatMap doubled it's length.
Note that this will throw an IllegalArgumentException if the list is empty, because the value passed to limit will be -1. You should check for this first if that is a possibility.
What about:
String concat = objList.stream().reduce(",", String::concat);
System.out.println(concat);
We can also try using limit and skip methods of stream API to this problem. Here is my try to this problem.
returnData.stream().limit(returnData.size()-1).forEach(s->System.out.print(s+","));
returnData.stream().skip(returnData.size()-1).forEach(s->System.out.print(s));
returnData is a List of Integers having values 2,4,7,14. The output will look like 2,4,7,14
objList.stream().filter(s -> { return !s.equals("Bart") })
This will reduce the stream to the strings which are NOT equal to Bart
And this will print the last value of a map :
Map<Integer, String> map = new HashMap<>();
map.put(0, "a");
map.put(1, "c");
map.put(2, "d");
Integer lastIndex = map.keySet().size() - 1;
Stream<String> lastValueStream = map.values().stream().filter(s -> s.equals(map.get(lastIndex)));
try this,
int[] counter = new int[]{0};
objList.stream()
.map(f -> (counter[0]++ > 0? "," : "") + f)
.forEach(System.out::print);