I have been trying to get all the values from a string and put them in a map in the following manner:
So I have a string which is like this:
String cookies = "i=lol;haha=noice;df3=ddtb;"
So far I have been trying this out:
final Map<String, String> map = new HashMap<>();
map.put(cookies.split(";")[0].split("=")[0], cookies.split(";")[0].split("=")[1]);
But this way I can only put one value in and it is quite long and ugly. Is there any was to due this with regex or a loop?
You could use a loop to iterate over the key value pairs and put them into the map:
String[] cookieArr = cookies.split(";");
for(String cookieString : cookieArr){
String[] pair = cookieString.split("=");
if(pair.length < 2){
continue;
}
map.put(pair[0], pair[1]);
}
The if is only there to prevent ArrayIndexOutOfBounds expcetions if cookie string is malformed
an alternativ would be using a stream:
Arrays.stream(cookies.split(";")).forEach(cookieStr -> map.put(cookieStr.split("=")[0], cookieStr.split("=")[1]));
As mentioned by #WJS in the comment, you could use map.putIfAbsent(key, vlaue) instead of map.put(key, value) to prevent overriding of values. But in case of cookies it could be a desired behavior to overwrite the old value with the new.
You could do it like this. It presumes your format is consistent.
first splits each k/v pair on ";"
the splits on "=" into key and value.
and adds to map.
if duplicate keys show up, the first one encountered takes precedence (if you want the latest value for a duplicate key then use (a, b)-> b as the merge lambda.)
String cookies = "i=lol;haha=noice;df3=ddtb";
Map<String, String> map = Arrays.stream(cookies.split(";"))
.map(str -> str.split("=")).collect(Collectors
.toMap(a -> a[0], a->a[1], (a, b) -> a));
map.entrySet().forEach(System.out::println);
Prints
df3=ddtb
haha=noice
i=lol
Related
Is there any **effective way **of comparing elements in Java and print out the position of the element which occurs once.
For example: if I have a list: ["Hi", "Hi", "No"], I want to print out 2 because "No" is in position 2. I have solved this using the following algorithm and it works, BUT the problem is that if I have a large list it takes too much time to compare the entire list to print out the first position of the unique word.
ArrayList<String> strings = new ArrayList<>();
for (int i = 0; i < strings.size(); i++) {
int oc = Collections.frequency(strings, strings.get(i));
if (oc == 1)
System.out.print(i);
break;
}
I can think of counting each element's occurrence no and filter out the first element though not sure how large your list is.
Using Stream:
List<String> list = Arrays.asList("Hi", "Hi", "No");
//iterating thorugh the list and storing each element and their no of occurance in Map
Map<String, Long> counts = list.stream().collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()));
String value = counts.entrySet().stream()
.filter(e -> e.getValue() == 1) //filtering out all the elements which have more than 1 occurance
.map(Map.Entry::getKey) // creating a stream of element from map as all of these have only single occurance
.findFirst() //finding the first element from the element stream
.get();
System.out.println(list.indexOf(value));
EDIT:
A simplified version can be
Map<String, Long> counts2 = new LinkedHashMap<String, Long>();
for(String val : list){
long count = counts2.getOrDefault(val, 0L);
counts2.put(val, ++count);
}
for(String key: counts2.keySet()){
if(counts2.get(key)==1){
System.out.println(list.indexOf(key));
break;
}
}
The basic idea is to count each element's occurrence and store them in a Map.Once you have count of all elements occurrences. then you can simply check for the first element which one has 1 as count.
You can use HashMap.For example you can put word as key and index as value.Once you find the same word you can delete the key and last the map contain the result.
If there's only one word that's present only once, you can probably use a HashMap or HashSet + Deque (set for values, Deque for indices) to do this in linear time. A sort can give you the same in n log(n), so slower than linear but a lot faster than your solution. By sorting, it's easy to find in linear time (after the sort) which element is present only once because all duplicates will be next to each other in the array.
For example for a linear solution in pseudo-code (pseudo-Kotlin!):
counters = HashMap()
for (i, word in words.withIndex()) {
counters.merge(word, Counter(i, 1), (oldVal, newVal) -> Counter(oldVald.firstIndex, oldVald.count + newVal.count));
}
for (counter in counters.entrySet()) {
if (counter.count == 1) return counter.firstIndex;
}
class Counter(firstIndex, count)
Map<String,Boolean> + loops
Instead of using Map<String,Integer> as suggested in other answers.
You can maintain a HashMap (if you need to maintain the order, use LinkedHashMap instead) of type Map<String,Boolean> where a value would denote whether an element is unique or not.
The simplest way to generate the map is method put() in conjunction with containsKey() check.
But there are also more concise options like replace() + putIfAbsent(). putIfAbsent() would create a new entry only if key is not present in the map, therefore we can associate such string with a value of true (considered to be unique). On the other hand replace() would update only existing entry (otherwise map would not be effected), and if entry exist, the key is proved to be a duplicate, and it has to be associated with a value of false (non-unique).
And since Java 8 we also have method merge(), which expects tree arguments: a key, a value, and a function which is used when the given key already exists to resolve the old value and the new one.
The last step is to generate list of unique strings by iterating over the entry set of the newly created map. We need every key having a value of true (is unique) associated with it.
List<String> strings = // initializing the list
Map<String, Boolean> isUnique = new HashMap<>(); // or LinkedHashMap if you need preserve initial order of strings
for (String next: strings) {
isUnique.replace(next, false);
isUnique.putIfAbsent(next, true);
// isUnique.merge(next, true, (oldV, newV) -> false); // does the same as the commented out lines above
}
List<String> unique = new ArrayList<>();
for (Map.Entry<String, Boolean> entry: isUnique.entrySet()) {
if (entry.getValue()) unique.add(entry.getKey());
}
Stream-based solution
With streams, it can be done using collector toMap(). The overall logic remains the same.
List<String> unique = strings.stream()
.collect(Collectors.toMap( // creating intermediate map Map<String, Boolean>
Function.identity(), // key
key -> true, // value
(oldV, newV) -> false, // resolving duplicates
LinkedHashMap::new // Map implementation, if order is not important - discard this argument
))
.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.toList(); // for Java 16+ or collect(Collectors.toList()) for earlier versions
I have a map ,the Integer stands for the frequency and the Set stands for a Set of words ,Now I want to reduce this map to get the frequency of the set with the most words ,and the return the frequency and the number of words in that Set with a pair
public static Map<Integer, Set<String>> getCounts(In in)
My instinct tells me this is ok, this is what I think :
initial
Compare each word set separately,once the sum of words in a Set greater than the previous recorded, Update the pair
get the pair
but I get stuck as soon as I start ....
var temp = new Pair<Integer,Integer>(0, 0);
Stream.of(wordCounts)
.reduce()
Note that you might want to think about what you want to output when the map is empty.
You can use the convenient max method on streams
var pair = wordCounts.entrySet().stream()
.map(x -> new Pair<>(x.getKey(), x.getValue().size()))
.max(Comparator.comparing(Pair::getSecond));
This gives you an optional pair. The optional will be empty when the map is empty.
If you really want to use reduce, you can. Just change the max call to:
.reduce((a, b) -> a.getSecond() >= b.getSecond() ? a : b);
I hope I understood this correctly:
map.entrySet().stream().max(Comparator.comparing(x -> x.getValue().size()))
.map(x -> new Map.entry(x.getKey(), x.getValue().size()))
.orElse(new Map.entry(0, 0));
I have the list as follows:
List<Map<String,Object>> mapList=new ArrayList<>();
Map<String,Object> mapObject=new HashMap<String,Object>();
mapObject.put("No",1);
mapObject.put("Name","test");
mapList.add(mapObject);
Map<String,Object> mapObject1=new HashMap<String,Object>();
mapObject1.put("No",2);
mapObject1.put("Name","test");
mapList.add(mapObject1);
and so on...
Now I want to get all the values of the key "No" as a string seperated by comma as follows:
String noList="1,2,3"
Can anyone please suggest me what may best way to do it. I know we can do it by looping but instead of looping is any other ways to do it.
Explanations inline!
mapList.stream() // stream over the list
.map(m -> m.get("No")) // try to get the key "No"
.filter(Objects::nonNull) // filter any null values in case it wasn't present
.map(Object::toString) // call toString for each object
.collect(Collectors.joining(",")); // join the values
Simply map the list:
String list = mapList.stream()
.filter(x -> x.containsKey("No")) // get only the maps that has the key
.map(x -> x.get("No").toString()) // every map will be transformed like this
.collect(Collectors.joining(",")); // joins all the elements with ","
System.out.println(list);
The use of HashMap<String, Object> suggests that it might be better to create a new class for this data. Have you considered this possibility before?
You can loop like this:
List<String> noList = new ArrayList<>(mapList.size());
for (Map<String,Object> m : mapList) {
Optional.ofNullable(m.get("No")) // get value mapped to "No" or empty Optional
.map(Object::toString)
.ifPresent(noList::add); // if not empty, add to list
}
System.out.println(String.join(",", noList));
or internally (the officially preferred version IIRC):
List<String> noList = new ArrayList<>(mapList.size());
mapList.forEach(m ->
Optional.ofNullable(m.get("No")).map(Object::toString).ifPresent(noList::add));
System.out.println(String.join(",", noList));
Now that I think of it, it's shorter than the Stream version.
Answered a pretty similar question 30 minutes ago.
You are using repeated keys. This makes it look like you don't need maps, but a class with the attributes "No", "Name", etc. If you've this class you can just iterate your instances on the list and concatenating to a String.
If no matter what you want to have your maps, simply get the values of the "No" key, but note that this is a wrong practise and you should be probably using a class instead of maps:
String res = "";
for(int i = 0; i < mapList.size(); i++) {
Map<String,Object> map = mapList.get(i);
res.concat(map.get("No"));
if(i != mapList.size() - 1)
res.concat(",");
}
PS: If you are going with the bad solution practise, use the stream alternatives in the other answers if your knowledge of stream is enough to understand them.
I have a map I want to populate:
private Map<String, Set<String>> myMap = new HashMap<>();
with this method:
private void compute(String key, String[] parts) {
myMap.computeIfAbsent(key, k -> getMessage(parts));
}
compute() is invoked as follows:
for (String line : messages) {
String[] parts = line.split("-");
validator.validate(parts); //validates parts are as expected
String key = parts[parts.length - 1];
compute(key, parts);
}
parts elements are like this:
[AB, CC, 123]
[AB, FF, 123]
[AB, 456]
In the compute() method, as you can see I am trying to use the last part of the element of the array as a key and the other parts to be used as values for the map I am looking to build.
My Question: How do I add to existing key only the unique values using Java 8 functional style e.g.
{123=[AB, FF, CC]}
As you requested I added a lambda variant, which just adds the parts via lambda to the map in the compute-method:
private void compute(String key, String[] parts) {
myMap.computeIfAbsent(key,
s -> Stream.of(parts)
.limit(parts.length - 1)
.collect(toSet()));
}
But in this case you will only get something like 123=[AB, CC] in your map. Use merge instead, if you want to add also all values which come on subsequent calls:
private void compute(String key, String[] parts) {
myMap.merge(key,
s -> Stream.of(parts)
.limit(parts.length - 1)
.collect(toSet()),
(currentSet, newSet) -> {currentSet.addAll(newSet); return currentSet;});
}
I am not sure what you intend with computeIfAbsent, but from what you listed as parts and what you expect as output, you may also want to try the following instead of the whole code you listed :
// the function to identify your key
Function<String[], String> keyFunction = strings -> strings[strings.length - 1];
// the function to identify your values
Function<String[], List<String>> valuesFunction = strings -> Arrays.asList(strings).subList(0, strings.length - 1);
// a collector to add all entries of a collection to a (sorted) TreeSet
Collector<List<String>, TreeSet<Object>, TreeSet<Object>> listTreeSetCollector = Collector.of(TreeSet::new, TreeSet::addAll, (left, right) -> {
left.addAll(right);
return left;
});
Map myMap = Arrays.stream(messages) // or: messages.stream()
.map(s -> s.split("-"))
.peek(validator::validate)
.collect(Collectors.groupingBy(keyFunction,
Collectors.mapping(valuesFunction, listTreeSetCollector)));
Using your samples as input you get the result you mentioned (well, actually sorted, as I used a TreeSet).
String[] messages = new String[]{
"AB-CC-123",
"AB-FF-123",
"AB-456"};
produces a map containing:
123=[AB, CC, FF]
456=[AB]
Last, but not least: if you can, pass the key and the values themselves to your method. Don't split the logic about identifying the key and identifying the values. That makes it really hard to understand your code later on or by someone else.
Try this:
private void compute(String[] parts) {
int lastIndex = parts.length - 1;
String key = parts[lastIndex];
List<String> values = Arrays.asList(parts).subList(0, lastIndex);
myMap.computeIfAbsent(key, k -> new HashSet<>()).addAll(values);
}
Or if you want, you can replace the entire loop with a stream:
Map<String, Set<String>> myMap = messages.stream() // if messages is an array, use Arrays.stream(messages)
.map(line -> line.split("-"))
.peek(validator::validate)
.collect(Collectors.toMap(
parts -> parts[parts.length - 1],
parts -> new HashSet<>(Arrays.asList(parts).subList(0, parts.length - 1)),
(a, b) -> { a.addAll(b); return a; }));
To add more parts to a possibly existing key you're using the wrong method; you want merge(), not computeIfAbsent().
If validator.valudate() throws a checked Exception, you must call it outside a stream, so you'll need a foreach loop:
for (String message : messages) {
String[] parts = message.split("-");
validator.validate(parts);
LinkedList<String> list = new LinkedList(Arrays.asList(parts));
String key = list.getLast();
list.removeLast();
myMap.merge(key, new HashSet<>(list), Set::addAll);
}
Using a LinkedList, which has methods getLast() and removeLast(), makes the code very readable.
Disclaimer: Code may not compile or work as it was thumbed in on my phone (but there's a reasonable chance it will work)
I wrote a program, that reads multiple (similar) textfiles out of a Folder. Im splitting the information by space and store everything in one arraylist which contains data kind of this:
key1=hello
key2=good
key3=1234
...
key15=repetition
key1=morning
key2=night
key3=5678
...
Now I'm looking for a way to get those information out of this list and somehow grouped by their keys into other lists. So im looking for a way to get a result like this:
keyList1 = {hello,morning}
keyList2 = {good,night}
and so on.
So I have to check very line for a keyword such as "key1" and split the value at the "=" and go on and on.
I think, the datastructure that suits your (described) needs best is a MultiMap. It is like a map, but with the possibility to store more than one value for a key.
For example the implementation from the guava project.
http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/collect/Multimap.html
First, you have to iterate over the arraylist:
final Multimap<String, String> multimap = ArrayListMultimap.create();
for ( String element : arrayList ) {
String[] splitted = element.split( "=" );
multimap.put( splitted[0], splitted[1] );
}
You get a List of values the following way:
for (String key : multimap.keySet()) {
List<String> values = multimap.get(key);
}
You might want to add some sanity checks for the splitting of your Strings.
(Code is untested)
It looks like you may be looking something like this kind of grouping (assuming you have access to Java 8)
List<String> pairs = Files.readAllLines(Paths.get("input.txt"));
Map<String, List<String>> map = pairs
.stream()
.map(s -> s.split("="))
.collect(
Collectors.groupingBy(
arr -> arr[0],
LinkedHashMap::new,//to preserve order of keys
Collectors.mapping(arr -> arr[1],
Collectors.toList())));
System.out.println(pairs);
System.out.println("---");
System.out.println(map);
Output:
[key1=hello, key2=good, key3=1234, key15=repetition, key1=morning, key2=night, key3=5678]
---
{key1=[hello, morning], key2=[good, night], key3=[1234, 5678], key15=[repetition]}