I want to split below string and store it into HashMap.
String responseString = "name~peter-add~mumbai-md~v-refNo~";
first I split the string using delimeter hyphen (-) and storing it into ArrayList as below:
public static List<String> getTokenizeString(String delimitedString, char separator) {
final Splitter splitter = Splitter.on(separator).trimResults();
final Iterable<String> tokens = splitter.split(delimitedString);
final List<String> tokenList = new ArrayList<String>();
for(String token: tokens){
tokenList.add(token);
}
return tokenList;
}
List<String> list = MyClass.getTokenizeString(responseString, "-");
and then using the below code to convert it to HashMap using stream.
HashMap<String, String> = list.stream()
.collect(Collectors.toMap(k ->k.split("~")[0], v -> v.split("~")[1]));
The stream collector doesnt work as there is no value against refNo.
It works correctly if I have even number of elements in ArrayList.
Is there any way to handle this? Also suggest how I can use stream to do these two tasks (I dont want to use getTokenizeString() method) using stream java 8.
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] : ""));
private final String dataSheet = "103343262,6478342944, 103426540,84528784843, 103278808,263716791426, 103426733,27736529279,
103426000,27718159078, 103218982,19855201547, 103427376,27717278645,
103243034,81667273413";
final int chunk = 2;
AtomicInteger counter = new AtomicInteger();
Map<String, String> pairs = Arrays.stream(dataSheet.split(","))
.map(String::trim)
.collect(Collectors.groupingBy(i -> counter.getAndIncrement() / chunk))
.values()
.stream()
.collect(toMap(k -> k.get(0), v -> v.get(1)));
result:
pairs =
"103218982" -> "19855201547"
"103278808" -> "263716791426"
"103243034" -> "81667273413"
"103426733" -> "27736529279"
"103426540" -> "84528784843"
"103427376" -> "27717278645"
"103426000" -> "27718159078"
"103343262" -> "6478342944"
We need to group each 2 elements into key, value pairs, so will partion the list into chunks of 2, (counter.getAndIncrement() / 2) will result same number each 2 hits ex:
IntStream.range(0,6).forEach((i)->System.out.println(counter.getAndIncrement()/2));
prints:
0
0
1
1
2
2
You may use the same idea to partition list into chunks.
Another short way to do :
String responseString = "name~peter-add~mumbai-md~v-refNo~";
Map<String, String> collect = Arrays.stream(responseString.split("-"))
.map(s -> s.split("~", 2))
.collect(Collectors.toMap(a -> a[0], a -> a.length > 1 ? a[1] : ""));
System.out.println(collect);
First you split the String on basis of - , then you map like map(s -> s.split("~", 2))it to create Stream<String[]> like [name, peter][add, mumbai][md, v][refNo, ] and at last you collect it to toMap as a[0] goes to key and a[1] goes to value.
Related
I want to split the following String and store it into a Map.
String = "key_a:<value_a1>\r\n\r\nkey_b:<value_b1>\r\n\r\nkey_c:<value_c1, value_c2, value_c3>"
The string can have line breaks in between the pairs. A key can have multiple values that are separated by a , and begin with a < and end with a >.
Now this String needs to be converted to a Map<String, List<String>>.
The structure of the map should look like this:
key_a={value_a1},
key_b={value_b1},
key_c={value_c1, value_c2, value_c3}
I currently only have the logic for splitting apart the different key-value-pairs from each other, but I don't know how to implement the logic that splits the values apart from each other, removes the brackets and maps the attributes.
String strBody = "key_a:<value_a1>\r\n\r\nkey_b:<value_b1>\r\n\r\nkey_c:<value_c1, value_c2, value_c3>"
Map<String, List<String>> map = Pattern.compile("\\r?\\n")
.splitAsStream(strBody)
.map(s -> s.split(":"))
//...logic for splitting values apart from each other, removing <> brackets and storing it in the map
)
You can filter the arrays having two values and then use Collectors.groupingBy to group the elements into Map, You can find more examples here about groupingBy and `mapping
Map<String, List<String>> map = Pattern.compile("\\r?\\n")
.splitAsStream(strBody)
.map(s -> s.split(":"))
.filter(arr -> arr.length == 2)
.collect(Collectors.groupingBy(arr -> arr[0],
Collectors.mapping(arr -> arr[1].replaceAll("[<>]", ""),
Collectors.toList())));
An additional approach which also splits the list of values:
Map<String,List<String>> result =
Pattern.compile("[\\r\\n]+")
.splitAsStream(strBody)
.map(s -> s.split(":"))
.map(arr -> new AbstractMap.SimpleEntry<>(
arr[0],
Arrays.asList(arr[1].replaceAll("[<>]", "").split("\\s*,\\s"))))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Your input has two \r\n to separate the entries, you need to split it by it as well, otherwise you will get empty entries, which you then need to filter out.
I'd remove the angle brackets from the string before processing it in the stream.
And then only the step of collection remains.
Map<String, String> map = Pattern.compile("\\r?\\n\\r?\\n")
.splitAsStream(strBody.replaceAll("[<>]",""))
.map(s -> s.split(":"))
.collect(Collectors.toMap(e -> e[0], e-> e[1]));
Try this.
String strBody = "key_a:<value_a1>\r\n\r\nkey_b:<value_b1>\r\n\r\nkey_c:<value_c1, value_c2, value_c3>";
Map<String, List<String>> result = Arrays.stream(strBody.split("\\R\\R"))
.map(e -> e.split(":", 2))
.collect(Collectors.toMap(a -> a[0],
a -> List.of(a[1].replaceAll("^<|>$", "").split("\\s,\\s*"))));
System.out.println(result);
output
{key_c=[value_c1, value_c2, value_c3], key_b=[value_b1], key_a=[value_a1]}
I have List with Strings:
List<String> cookiesUpdate = Arrays.asList("A=2" , "B=3");
I want to convert it to Map:
{
"A": "2",
"B": "3"
}
Code:
Map<String, String> cookies = cookiesUpdate.stream()
.collect(Collectors.toMap(String::toString, String::toString));
How to write those splitters above? If compiler thinks key String is Object.
.split("=")[0];
.split("=")[1];
Split should be done by "=" (or "\\s*=\\s" to exclude whitespaces around =)
Update Also it is better to provide limit argument to String::split to split at the first occurrence of "=", thanks #AndrewF for suggestion!
Fix toMap collector to use the first element of the array as key and the last as the value; a merge function may be needed if several namesake cookies are possible
Map<String, String> map = cookies.stream()
.map(ck -> ck.split("\\s*=\\s*", 2)) // Stream<String[]>
.filter(arr -> arr.length > 1) // ignore invalid cookies
.collect(Collectors.toMap(arr -> arr[0], arr -> arr[1], (v1, v2) -> v1));
If there are multiple cookies with the same name, it may be worth to collect them into Set<String> thus keeping all unique values. For this, Collectors.groupingBy and Collectors.mapping:
Map<String, Set<String>> map2 = cookies.stream()
.map(ck -> ck.split("\\s*=\\s*", 2)) // Stream<String[]>
.filter(arr -> arr.length > 1) // ignore invalid cookies
.collect(Collectors.groupingBy(
arr -> arr[0],
Collectors.mapping(arr -> arr[1], Collectors.toSet())
));
I'm trying to write a method that will validate String. If string has same amount of every char like "aabb", "abcabc", "abc" it is valid or if contains one extra symbol like "ababa" or "aab" it is also valid other cases - invalid.
Update: sorry, I forget to mention such cases like abcabcab -> a-3, b-3, c-2 -> 2 extra symbols (a, b) -> invalid. And my code doesn't cover such cases.
Space is a symbol, caps letters are different from small letters. Now I have this, but it looks ambiguous (especially last two methods):
public boolean validate(String line) {
List<Long> keys = countMatches(countChars(line));
int matchNum = keys.size();
if (matchNum < 2) return true;
return matchNum == 2 && Math.abs(keys.get(0) - keys.get(1)) == 1;
}
Counting unique symbols entry I'd wish to get List<long>, but I don't know how:
private Map<Character, Long> countChars(String line) {
return line.chars()
.mapToObj(c -> (char) c)
.collect(groupingBy(Function.identity(), HashMap::new, counting()));
}
private List<Long> countMatches(Map<Character, Long> countedEntries) {
return new ArrayList<>(countedEntries.values()
.stream()
.collect(groupingBy(Function.identity(), HashMap::new, counting()))
.keySet());
}
How can I optimize a method above? I need just List<Long>, but have to create a map.
As I could observe, you are looking for distinct frequencies using those two methods. You can merge that into one method to use a single stream pipeline as below :
private List<Long> distinctFrequencies(String line) {
return line.chars().mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(Function.identity(),
Collectors.counting()))
.values().stream()
.distinct()
.collect(Collectors.toList());
}
Of course, all you need to change in your validate method now is the assignment
List<Long> keys = distinctFrequencies(line);
With some more thought around it, if you wish to re-use the API Map<Character, Long> countChars somewhere else as well, you could have modified the distinct frequencies API to use it as
private List<Long> distinctFrequencies(String line) {
return countChars(line).values()
.stream()
.distinct()
.collect(Collectors.toList());
}
you could perform an evaluation if every char in a string has the same occurence count using the stream api like this:
boolean valid = "aabbccded".chars()
.boxed()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.values().stream()
.reduce((a, b) -> a == b ? a : -1L)
.map(v -> v > 0)
.get();
EDIT:
after reading the comments, I now believe to have understood the requirement.
a string is considered valid if all chars in it have the same occurrence count like aabb
or if there is a single extra character like abb
the string abcabcab is invalid as it has 3a 3b and 2c and thus, it has 1
extra a and 1 extra b, that is too much. hence, you can't perform the validation with a frequency list, you need additional information about how often the char lengths differ -> Map
here is a new trial:
TreeMap<Long, Long> map = "abcabcab".chars()
.boxed()
.collect(groupingBy(Function.identity(), counting()))
.values().stream()
.collect(groupingBy(Function.identity(), TreeMap::new, counting()));
boolean valid = map.size() == 1 || // there is only a single char length
( map.size() == 2 && // there are two and there is only 1 extra char
((map.lastKey() - map.firstKey()) * map.lastEntry().getValue() <= 1));
the whole validation could be executed in a single statement by using the Collectors.collectingAndThen method that #Nikolas used in his answer or you could use a reduction as well:
boolean valid = "aabcc".chars()
.boxed()
.collect(groupingBy(Function.identity(), counting()))
.values().stream()
.collect(groupingBy(Function.identity(), TreeMap::new, counting()))
.entrySet().stream()
.reduce((min, high) -> {
min.setValue((min.getKey() - high.getKey()) * high.getValue()); // min.getKey is the min char length
return min; // high.getKey is a higher char length
// high.getValue is occurrence count of higher char length
}) // this is always negative
.map(min -> min.getValue() >= -1)
.get();
Use Collector.collectingAndThen that is a collector that uses a downstream Collector and finisher Function that maps the result.
Use the Collectors.groupingBy and Collectors.counting to get the frequency of each character in the String.
// Results in Map<Integer, Long>
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting())
Use the map -> new HashSet<>(map.values()).size() == 1 that checks whether all frequencies are equal - if so, there is one distinct value.
Wrapping these two in Collector.collectingAndThen looks like:
String line = "aabbccdeed";
boolean isValid = line.chars() // IntStream of characters
.boxed() // boxed as Stream<Integer>
.collect(Collectors.collectingAndThen( // finisher's result type
Collectors.groupingBy( // grouped Map<Integer, Integer>
Function.identity(), // ... of each character
Collectors.counting()), // ... frequency
map -> new HashSet<>(map.values()).size() == 1 // checks the frequencies
));
// aabbccded -> false
// aabbccdeed -> true
You can do like this:
first count every character occurrence.
then find min value for occurrence.
and at the last step sum all values that the difference with the smallest value(minValue) is less than or equal to one.
public static boolean validate(String line) {
Map<Character, Long> map = line.chars()
.mapToObj(c -> (char) c)
.collect(groupingBy(Function.identity(), Collectors.counting()));
long minValue = map.values().stream().min(Long::compareTo).orElse(0l);
return map.values().stream().mapToLong(a -> Math.abs(a - minValue)).sum() <= 1;
}
I am trying to get all Values from Keys that contain the same substring. For example:
If a Key's string is "AAABBB'
and another Key's string is 'XXXBBB'
I want to get the Value from both of those Keys. (Since BBB matches)
The relationship of the substring match should be 3 characters in length. The prefix is from index 0-3 and the suffix index is from 3-6.
For example: AAABBB
(AAA is the suffix and BBB is the prefix.)
(The relationship AABBBA is ignored because AAB and BBA do not match.)
I'm trying to avoid using nested for loops because my algorithm will run very slow at O(N^2). I'm working on finding a solution with 1 HashMap and 1 for loop.
HashMap a = new HashMap();
map.put("AAABBB", 1);
map.put("CCCPPP", 2);
map.put("XXXBBB", 3);
map.put("AAAOOO",4);
for (Entry<String, String> entry : a.entrySet()) {
String prefix = entry.getKey().substring(0,3);
String suffix = entry.getKey().substring(3,6);
if(map.contains("ANY_SUBSTRING" + suffix){
System.out.println(map.get("ANY_SUBSTRING" + suffix);
}
}
Output: (1,3)
AAABBB => 1
XXXBBB => 3
I have following approach with streams.
Define a function to extract the suffix or prefix of each key of your map
Stream your maps entryset and group by prefix/suffix
filter those out which have no prefix/suffix incommon
Using your example map and assuming each key length is = 6
Map<String,Integer> map = new HashMap<>();
map.put("AAABBB", 1);
map.put("CCCPPP", 2);
map.put("XXXBBB", 3);
map.put("AAAOOO",4);
Function<Entry<String, Integer>,String> prefix = e -> e.getKey().substring(0,3);
Function<Entry<String, Integer>,String> suffix = e -> e.getKey().substring(3);
Map<String,List<Integer>> resultBySuffix =
map.entrySet().stream()
.collect(Collectors.groupingBy( suffix ,
Collectors.mapping(Entry::getValue, Collectors.toList())
)).entrySet().stream()
.filter(e -> e.getValue().size() > 1)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
System.out.println(resultBySuffix);
Map<String,List<Integer>> resultByPrefix =
map.entrySet().stream()
.collect(Collectors.groupingBy( prefix ,
Collectors.mapping(Entry::getValue, Collectors.toList())
)).entrySet().stream()
.filter(e -> e.getValue().size() > 1)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
System.out.println(resultByPrefix);
No idea what the time complexity of the above example is like. But I think you can see what is going on (in terms of readability)
I have a stream of Strings like-
Token1:Token2:Token3
Here ':' is delimiter character. Here Token3 String may contain delimiter character in it or may be absent.
We have to convert this stream into map with Token1 as key and value is array of two strings- array[0] = Token2 and array[1] = Token3 if Token3 is present, else null.
I have tried something like-
return Arrays.stream(inputArray)
.map( elem -> elem.split(":"))
.filter( elem -> elem.length==2 )
.collect(Collectors.toMap( e-> e[0], e -> {e[1],e[2]}));
But It didn't work. Beside that it do not handle the case if Token3 is absent or contain delimiter character in it.
How can I accomplish it in Java8 lambda expressions?
You can map every input string to the regex Matcher, then leave only those which actually match and collect via toMap collector using Matcher.group() method:
Map<String, String[]> map = Arrays.stream(inputArray)
.map(Pattern.compile("([^:]++):([^:]++):?(.+)?")::matcher)
.filter(Matcher::matches)
.collect(Collectors.toMap(m -> m.group(1), m -> new String[] {m.group(2), m.group(3)}));
Full test:
String[] inputArray = {"Token1:Token2:Token3:other",
"foo:bar:baz:qux", "test:test"};
Map<String, String[]> map = Arrays.stream(inputArray)
.map(Pattern.compile("([^:]++):([^:]++):?(.+)?")::matcher)
.filter(Matcher::matches)
.collect(Collectors.toMap(m -> m.group(1), m -> new String[] {m.group(2), m.group(3)}));
map.forEach((k, v) -> {
System.out.println(k+" => "+Arrays.toString(v));
});
Output:
test => [test, null]
foo => [bar, baz:qux]
Token1 => [Token2, Token3:other]
The same problem could be solved with String.split as well. You just need to use two-arg split version and specify how many parts at most do you want to have:
Map<String, String[]> map = Arrays.stream(inputArray)
.map(elem -> elem.split(":", 3)) // 3 means that no more than 3 parts are necessary
.filter(elem -> elem.length >= 2)
.collect(Collectors.toMap(m -> m[0],
m -> new String[] {m[1], m.length > 2 ? m[2] : null}));
The result is the same.
You can achieve what you want with the following:
return Arrays.stream(inputArray)
.map(elem -> elem.split(":", 3)) // split into at most 3 parts
.filter(arr -> arr.length >= 2) // discard invalid input (?)
.collect(Collectors.toMap(arr -> arr[0], arr -> Arrays.copyOfRange(arr, 1, 3))); // will add null as the second element if the array length is 2