I got a code which gets all minimum values from a list called frequencies. Then it puts the min values with the percentage of total values into a String. To calculate the percentage I want to call minEntryes.getValue()(minEntryes is the Map<String, Integer> with all the min values in it), but it does not work. My code:
StringBuilder wordFrequencies = new StringBuilder();
URL url = new URL(urlString);//urlString is a String parameter of the function
AtomicInteger elementCount = new AtomicInteger();//total count of all the different characters
Map<String, Integer> frequencies = new TreeMap<>();//where all the frequencies of the characters will be stored
//example: e=10, r=4, (=3 g=4...
//read and count all the characters, works fine
try (Stream<String> stream = new BufferedReader(
new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)).lines()) {
stream
.flatMapToInt(CharSequence::chars)
.filter(c -> !Character.isWhitespace(c))
.mapToObj(Character::toString)
.map(String::toLowerCase)
.forEach(s -> {
frequencies.merge(s, 1, Integer::sum);
elementCount.getAndIncrement();
});
} catch (IOException e) {
return "IOException:\n" + e.getMessage();
}
//counting the letters which are present the least amount of times
//in the example from above those are
//r=4, g=4
try (Stream<Map.Entry<String, Integer>> stream = frequencies.entrySet().stream()) {
Map<String, Integer> minEntryes = new TreeMap<>();
stream
.collect(Collectors.groupingBy(Map.Entry::getValue))
.entrySet()
.stream()
.min(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.ifPresent(key -> {
IntStream i = IntStream.rangeClosed(0, key.size());
i.forEach(s -> minEntryes.put(key.get(s).getKey(), key.get(s).getValue()));
});
wordFrequencies.append("\n\nSeltenste Zeichen: (").append(100 / elementCount.floatValue() * minEntryes.getValue().append("%)"));
//this does not work
minEntryes.forEach((key, value) -> wordFrequencies.append("\n'").append(key).append("'"));
}
The compiler tells me to call get(String key) but I don't know the key. So my code to get it into the Map is way to complicated, I know, but I can't use Optional in this case(the task prohibits it). I tried to do it more simple but nothing worked.
I could get a key from minEntryes.forEach, but im wondering if there's a better solution for this.
It's not clear to me what you are trying to do, but if the question is how to get the value without knowing the key:
1st method: Use an for loop
for (int value : minEntryes.values()) {
// use 'value' instead of 'minEntryes.getValue()'
}
2nd method: Iterator "hack" (If you know there is always one value)
int value = minEntryes.values().iterator().next();
// use 'value' instead of 'minEntryes.getValue()'
Related
I want to use stream getfirst method two times but an error occurred says (java.lang.IllegalStateException: stream has already been operated upon or closed) and this stream code begins at comment named here.
//code
Stream<Map.Entry<String,Integer>> sorted =
map.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
Supplier<Stream<Map.Entry<String,Integer>>> sort2 = () -> sorted;
Optional<String> kk = Optional.of(sort2.get().findFirst().get().getKey());
Optional<Integer> vv = Optional.of(sort2.get().findFirst().get().getValue());
int vmax = vv.get().intValue() ;
int count=0;
ArrayList<String> a = new ArrayList<String>() ;
for(Map.Entry<String,Integer> h: map.entrySet() ) {
if(h.getValue()==vmax) {
a.add(h.getKey()) ;
count++;
}
}
if(count>1) {
Collections.sort(a);
System.out.println(a.get(0));
}
else {
System.out.println(kk.get());
}
map.clear();
}
}
}
You can't use a stream twice. Create a variable to hold the value of findFirst()
sort2 is not doing anything for you, and simply hiding the issue that you can't reuse sorted.
In this situation, by far the best solution is to store sorted.findFirst() in a variable and reuse it rather than call findFirst() twice. If you wanted to do it like this, then you would have to write
Supplier<Stream<Map.Entry<String,Integer>>> sort2 = () -> map.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()));
...which would sort the entries twice, which would be very inefficient.
Stream.findFirst is a terminal operation - calling findFirst twice on the same stream like you are doing is not possible.
It looks like you don't need to call it twice: even if you restarted a new stream, both times you would expect to get the same map entry. So call findFirst only once, and use the Optional it returns to you:
Optional<Map.Entry<String, Integer>> firstEntry = sorted.findFirst();
Optional<String> kk = firstEntry.map(Map.Entry::getKey);
Optional<Integer> vv = firstEntry.map(Map.Entry::getValue);
Note that there's a better way to get the maximum item from the stream than sorting: using the max operator.
Optional<Map.Entry<String, Integer>> maxEntry = map.entrySet().stream()
.max(Map.Entry.comparingByValue());
Below is the sample code, Suggest me what is wrong here as per my knowledge, I am passing a new key every time still it says duplicate key.
public class CollectorsDemo5 {
public static void main(String[] args) {
List<String> listOfCities=new ArrayList<String>();
listOfCities.add("Istanbul");
listOfCities.add("Istanbul");
listOfCities.add("Budapest");
listOfCities.add("Delhi");
listOfCities.add("Amsterdam");
listOfCities.add("Canberra");
listOfCities.add("Canberra");
Map<Integer, String> map = listOfCities.stream()
.collect(Collectors.toMap(listOfCities::indexOf, Function.identity()));
map.forEach((k,v)->System.out.println("key:"+k +" value:"+v));
}
}
Some of list elements are duplicate.
Hence indexOf will return the same value -> Throw duplicate key exception.
What you can do is to do a normal for loop:
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < listOfCities.size(); i++) {
map.put(i, listOfCities.get(i));
}
You get that exception because you have duplicate values in your stream. You can produce the result you desire with IntStream:
Map<Integer, String> map = IntStream.range(0, listOfCities.size())
.mapToObj(Function.identity())
.collect(Collectors.toMap(Function.identity(), listOfCities::get));
Using a simple for loop would be the way to go here. If you really want to use Streams
IntStream.range(0, listOfCities.size())
.collect(HashMap::new, (map, index) -> map.put(index, listOfCities.get(index)), HashMap::putAll);
This produces
{0=Istanbul, 1=Istanbul, 2=Budapest, 3=Delhi, 4=Amsterdam, 5=Canberra, 6=Canberra}
The above creates a new HashMap to which we add the city name keyed by the index. The last combiner is only applicable when using a parallel stream and is used to combine the multiple maps.
I am sorting a populated set of MyObject (the object has a getName() getter) in a stream using a predefined myComparator.
Then once sorted, is there a way to collect into a map the name of the MyObject and the order/position of the object from the sort?
Here is what I think it should look like:
Set<MyObject> mySet; // Already populated mySet
Map<String, Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(Collectors.toMap(MyObject::getName, //HowToGetThePositionOfTheObjectInTheStream));
For example, if the set contain three objects (object1 with name name1, object2 with name name2, object3 with name name3) and during the stream they get sorted, how do I get a resulting map that looks like this:
name1, 1
name2, 2
name3, 3
Thanks.
A Java Stream doesn't expose any index or positioning of elements, so I know no way of replacing /*HowToGetThePositionOfTheObjectInTheStream*/ with streams magic to obtain the desired number.
Instead, one simple way is to collect to a List instead, which gives every element an index. It's zero-based, so when converting to a map, add 1.
List<String> inOrder = mySet.stream()
.sorted(myComparator)
.map(MyObject::getName)
.collect(Collectors.toList());
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < inOrder.size(); i++) {
nameMap.put(inOrder.get(i), i + 1);
}
Try this one. you could use AtomicInteger for value of each entry of map. and also to guarantee order of map use LinkedHashMap.
AtomicInteger index = new AtomicInteger(1);
Map<String, Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(Collectors
.toMap(MyObject::getName, value -> index.getAndIncrement(),
(e1, e2) -> e1, LinkedHashMap::new));
The simplest solution would be a loop, as a formally correct stream solution that would also work in parallel requires a nontrivial (compared to the rest) merge functions:
Map<String,Integer> nameMap = mySet.stream()
.sorted(myComparator)
.collect(HashMap::new, (m, s) -> m.put(s.getName(), m.size()),
(m1, m2) -> {
int offset = m1.size();
m2.forEach((k, v) -> m1.put(k, v + offset));
});
Compare with a loop/collection operations:
List<MyObject> ordered = new ArrayList<>(mySet);
ordered.sort(myComparator);
Map<String, Integer> result = new HashMap<>();
for(MyObject o: ordered) result.put(o.getName(), result.size());
Both solutions assume unique elements (as there can be only one position). It’s easy to change the loop to detect violations:
for(MyObject o: ordered)
if(result.putIfAbsent(o.getName(), result.size()) != null)
throw new IllegalStateException("duplicate " + o.getName());
Dont use a stream:
List<MyObject> list = new ArrayList<>(mySet);
list.sort(myComparator);
Map<String, Integer> nameMap = new HashMap<>();
for (int i = 0; i < list.size(); i++) {
nameMap.put(list.get(i).getName(), i);
}
Not only will this execute faster than a stream based approach, everyone knows what's going on.
Streams have their place, but pre-Java 8 code does too.
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 have text file with a file that on every line contain pair of name and amount like this:
Mike 5
Kate 2
Mike 3
I need to sum these values by key. I resolved this in this way
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
try {
Files.lines(Paths.get("/Users/walter/Desktop/stuff.txt"))
.map(line -> line.split("\\s+")).forEach(line -> {
String key = line[0];
if (map.containsKey(key)) {
Integer oldValue = map.get(key);
map.put(key, oldValue + Integer.parseInt(line[1]));
} else {
map.put(line[0], Integer.parseInt(line[1]));
}
});
map.forEach((k,v) -> System.out.println(k + " " +v));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
;
}
How actually I could improve this code in more functional way, with abillity to process data more concurrently (using parallel streams, etc.)
When you want to write functional code, the rule is: don't use forEach. This is an imperative solution and breaks functional code.
What you want is to split each line and group by the first part (key) while summing the second part (values):
Map<String, Integer> map =
Files.lines(Paths.get("/Users/walter/Desktop/stuff.txt"))
.map(s -> s.split("\\s+"))
.collect(groupingBy(a -> a[0], summingInt(a -> Integer.parseInt(a[1]))));
In this code, we are splitting each line. Then, we are grouping the Stream using Collectors.groupingBy(classifier, downstream) where:
classifier, which is a function that classifies each item to extract the key of the resulting Map, just returns the first part of the line
downstream is a collector that reduces each value having the same key: in this case, it is Collectors.summingInt(mapper) which sums each integer extracted by the given mapper.
As a side-note (and just so you know), you could rewrite your whole forEach more simply using the new Map.merge(key, value, remappingFunction) method, with just a call to:
map.merge(line[0], Integer.valueOf(line[1]), Integer::sum);
This will put a new value with the key line[0] with the value Integer.valueOf(line[1]) if this key did not exist, otherwise, it will update the key with the given remapping function (which is the sum of the old and new value in this case).