Aggregating values in hashmap by keys - java

Consider a simple HashMap<Integer,Integer>. How can I get all the values stored against keys which are multiple of, say 5? I have worked on Java and Collections for some time now, but suddenly I am clueless.
Any help would be highly appreciated.
Regards,
Salil

List<Integer> values = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getKey() % 5 == 0) {
values.add(entry.getValue());
}
}
FWIW, a comparable Java 8 approach might look like
map.entrySet().stream()
.filter(entry -> entry.getKey() % 5 == 0)
.map(Entry<Integer, Integer>::getValue)
.collect(toList());

Guava has a few helper methods to help you achieve this. In particular, Maps.filterKeys(Map, Predicate)
// populate a Map
Map<Integer, Integer> map = new HashMap<>();
map.put(3, 0);
map.put(15, 42);
map.put(75, 1234);
// filter it
Map<Integer, Integer> filtered = Maps.filterKeys(map, new Predicate<Integer>() {
#Override
public boolean apply(Integer input) {
return input % 5 == 0; // filter logic
}
});
// get the values
System.out.println(filtered.values());
prints
[1234, 42]
As Louis notes, "this isn't actually more efficient -- or even shorter -- than the "traditional" for-each approach."

Related

Filter out result from Map with multiple conditions

I have a Map with list of empid and name. I want to select only emp name of people whose id is greater than 7 and name starts with N.
The result must be SET.
I tried using map.entryset() but cannot think how to filter inside map.
Do we have to use if else?
How we will return to set if multiple elements are found?
It should be something like this,
Set<String> selectedEmps = empIdToName.entrySet().stream()
.filter(e -> e.getKey() > 7)
.filter(e -> e.getValue().startsWith("N"))
.map(Map.Entry::getValue)
.collect(Collectors.toSet());
If I understand you correctly, here's a solution:
Suppose we have this data:
Map<String, List<Integer>> map = new HashMap<>();
map.put("Noo", new ArrayList<>(Arrays.asList(8,8,9)));
map.put("No", new ArrayList<>(Arrays.asList(1,8,9)));
map.put("Aoo", new ArrayList<>(Arrays.asList(8,8,9)));
We can filter the data in this way:
map.entrySet().
stream()
.filter(e -> e.getKey().startsWith("N"))
.filter(e -> e.getValue().stream().filter(id -> id <= 7).findAny().orElse(0) == 0)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
The first filter excludes the Names that does not start with "N", the second filter goes through the remaining entries and check if all their ids are greater than 7. In the foreach I just print the data, but you can change the logic to your needs
The result should the this:
Noo [8, 8, 9]
Can be done very easily with a simple for-loop:
Map<Integer, String> map = ...
Set<String> result = new HashSet<>();
for(Map.Entry<Integer, String> entry : map.entrySet()){
if(entry.getKey() > 7){
String name = entry.getValue();
if(name.charAt(0) == 'N'){
result.add(name);
}
}
}
Note: if the names can be empty (length() == 0) then the name.charAt(0) approach will not work as you'll get a StringIndexOutOfBoundsException
Map<Integer, String> nameMap = new HashMap<>(); //A dummy map with empid and name of emp
public static void main(String[] args) {
nameMap.put(1,"John");
nameMap.put(2,"Doe");
nameMap.put(37,"Neon");
nameMap.put(14,"Shaun");
nameMap.put(35,"Jason");
nameMap.put(0,"NEO");
Set<String> empSet = nameMap.entrySet()
.stream()
.filter(x->x.getKey()>7 && x.getValue().startsWith("N"))
.map(x->x.getValue())
.collect(Collectors.toSet());
empSet.forEach(System.out::println);
}
The actual implementation and namespaces will vary but the basic
operations will remain same.

Collect to map the order/position value of a sorted stream

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.

Overwriting values in a HashMap that are in an ArrayList<String>

Let's say I have a HashMap with String keys and Integer values:
map = {cat=1, kid=3, girl=3, adult=2, human=5, dog=2, boy=2}
I want to switch the keys and values by putting this information into another HashMap. I know that a HashMap cannot have duplicate keys, so I tried to put the information into a HashMap with the Integer for the keys that would map to a String ArrayList so that I could potentially have one Integer mapping to multiple Strings:
swap = {1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
I tried the following code:
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (String x : map.keySet()) {
for (int i = 0; i <= 5; i++) {
ArrayList<String> list = new ArrayList<String>();
if (i == map.get(x)) {
list.add(x);
swap.put(i, list);
}
}
}
The only difference in my code is that I didn't hard code the number 5 into my index; I have a method that finds the highest integer value in the original HashMap and used that. I know it works correctly because I get the same output even if I hard code the 5 in there, I just didn't include it to save space.
My goal here is to be able to do this 'reversal' with any set of data, otherwise I could just hard code the value. The output I get from the above code is this:
swap = {1=[cat], 2=[boy], 3=[girl], 5=[human]}
As you can see, my problem is that the value ArrayList is only keeping the last String that was put into it, instead of collecting all of them. How can I make the ArrayList store each String, rather than just the last String?
With Java 8, you can do the following:
Map<String, Integer> map = new HashMap<>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
Map<Integer, List<String>> newMap = map.keySet()
.stream()
.collect(Collectors.groupingBy(map::get));
System.out.println(newMap);
The output will be:
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
you are recreating the arrayList for every iteration and i can't figure out a way to do it with that logic, here is a good way though and without the need to check for the max integer:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
List<String> get = swap.get(value);
if (get == null) {
get = new ArrayList<>();
swap.put(value, get);
}
get.add(key);
}
Best way is to iterate over the key set of the original map.
Also you have to asure that the List is present for any key in the target map:
for (Map.Entry<String,Integer> inputEntry : map.entrySet())
swap.computeIfAbsent(inputEntry.getValue(),()->new ArrayList<>()).add(inputEntry.getKey());
This is obviously not the best solution, but approaches the problem the same way you did by interchanging inner and outer loops as shown below.
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (Integer value = 0; value <= 5; value++) {
ArrayList<String> list = new ArrayList<String>();
for (String key : map.keySet()) {
if (map.get(key) == value) {
list.add(key);
}
}
if (map.containsValue(value)) {
swap.put(value, list);
}
}
Output
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
Best way I can think of is using Map.forEach method on existing map and Map.computeIfAbsent method on new map:
Map<Integer, List<String>> swap = new HashMap<>();
map.forEach((k, v) -> swap.computeIfAbsent(v, k -> new ArrayList<>()).add(k));
As a side note, you can use the diamond operator <> to create your new map (there's no need to repeat the type of the key and value when invoking the map's constructor, as the compiler will infer them).
As a second side note, it's good practice to use interface types instead of concrete types, both for generic parameter types and for actual types. This is why I've used List and Map instead of ArrayList and HashMap, respectively.
Using groupingBy like in Jacob's answer but with Map.entrySet for better performance, as suggested by Boris:
// import static java.util.stream.Collectors.*
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(groupingBy(Entry::getValue, mapping(Entry::getKey, toList())));
This uses two more methods of Collectors: mapping and toList.
If it wasn't for these two helper functions, the solution could look like this:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
groupingBy(
Entry::getValue,
Collector.of(
ArrayList::new,
(list, e) -> {
list.add(e.getKey());
},
(left, right) -> { // only needed for parallel streams
left.addAll(right);
return left;
}
)
)
);
Or, using toMap instead of groupingBy:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
toMap(
Entry::getValue,
(e) -> new ArrayList<>(Arrays.asList(e.getKey())),
(left, right) -> {
left.addAll(right);
return left;
}
)
);
It seams you override the values instrad of adding them to the already creared arraylist. Try this:
HashMap<Integer, ArrayList<String>> swapedMap = new HashMap<Integer, ArrayList<String>>();
for (String key : map.keySet()) {
Integer swappedKey = map.get(key);
ArrayList<String> a = swapedMap.get(swappedKey);
if (a == null) {
a = new ArrayList<String>();
swapedMap.put(swappedKey, a)
}
a.add(key);
}
I didn't have time to run it (sorry, don't have Java compiler now), but should be almost ok :)
You could use the new merge method in java-8 from Map:
Map<Integer, List<String>> newMap = new HashMap<>();
map.forEach((key, value) -> {
List<String> values = new ArrayList<>();
values.add(key);
newMap.merge(value, values, (left, right) -> {
left.addAll(right);
return left;
});
});

using HashMaps is it possible to return all keys that have values below a set threshold?

Is it possible for return all values of a HashMap if they're below a certain value? This is the Hash:
Map<String, Integer> StockIO = new HashMap<>();
The String being the stock item and Integer being the stock value.
I'm sure I'm missing something simple here.
This is the correct working and tested code using a jbutton click that then prints to a textarea:
private void OrderActionPerformed(java.awt.event.ActionEvent evt) {
Set<String> keys = new HashSet<>();
for(Map.Entry<String,Integer> entry: StockIO.entrySet())
{
if (entry.getValue() <= 10)
{
keys.add(entry.getKey());
Oresult.setText(entry.getKey());
}
}
}
I didn't get your question fully, but check this code below. May be it is what you want:
HashMap<String,Integer> jj=new HashMap<String, Integer>();
jj.put("A",10);
jj.put("B",20);
jj.put("c",30);
jj.put("Bd",50);
jj.put("Af",40);
jj.put("Bd",240);
jj.put("Ads",130);
jj.put("Bde",240);
jj.put("As",130);
jj.put("Bfe",210);
int threshold=100;
for(String key:jj.keySet()){
String stock_item=key;
Integer stock_value=jj.get(key);
if(stock_value<threshold)
System.out.println("Item:"+stock_item+" "+" Value:"+stock_value+"\n");
}`
Yes, but it isn't efficient. If you want ordering, use an ordered map, i.e. a TreeMap.
What you want is effectively filtering your collection of keys.
With this code, you get all the keys that have a value lower than or equal to a specific threshold.
public static Set<String> keysWithValuesBelowThreshold(Map<String,Integer> map, int threshold) {
Set<String> keys = new HashSet<>();
for(Map.Entry<String,Integer> entry: map.entrySet()) {
if (entry.getValue() <= threshold) {
keys.add(entry.getKey());
}
}
return keys;
}
With Java 8:
public static Set<String> keysWithValuesBelowThreshold(Map<String,Integer> map, int threshold) {
return map.entrySet().stream()
.filter(e -> e.getValue() <= threshold)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}

Most concise way to get highest n numbers out of a map?

I have the following already:
public enum InvoiceCurrency {
EUR(
s -> (s.contains("€") || s.contains("EUR"))
),
USD(
s -> (s.contains("$") || s.contains("USD"))
);
private final Predicate<String> predicate;
InvoiceCurrency(final Predicate<String> predicate) {
this.predicate = predicate;
}
public boolean matchesString(final String value) {
return predicate.test(value);
}
public static EnumMap<InvoiceCurrency, Integer> createMapping(final Stream<String> valuesStream) {
EnumMap<InvoiceCurrency, Integer> mapping = new EnumMap<>(InvoiceCurrency.class);
mapping.replaceAll((k, v) -> 0);
Stream<InvoiceCurrency> enums = Arrays.stream(InvoiceCurrency.values());
valuesStream.forEach(
s -> enums.forEach(
e -> {
if (e.matchesString(s)) {
mapping.compute(e, (k, v) -> v++);
}
}
)
);
return mapping;
}
}
private InvoiceCurrency calculateCurrency() {
EnumMap<InvoiceCurrency, Integer> map = InvoiceCurrency.createMapping(data.words.stream().map(w -> w.content));
InvoiceCurrency maximum = map.entrySet().parallelStream(). //how to continue?
}
This results in a mapping from the enum to the 'number of occurences', so EUR can be mapped to 10 and USD to 1. Possibly, the count may be the same.
Now have do I, as concise as possibly and with the ability to use java-8, get the InvoiceCurrency that belongs to the highest number? And is there a concise way to see that the top 2 of the sorted integer count actually has the same value?
I know I can program it with loops, etc., but I wish to rely on the java-8 spirit for the most maintainable code.
Simple example with a Map<String, Integer> but the same will work with your example. Prints the top 2 entries (b and c or d).
import static java.util.Collections.reverseOrder;
import static java.util.Comparator.comparingInt;
//...
Map<String, Integer> map = new HashMap<>();
map.put("a", 2);
map.put("b", 10);
map.put("c", 5);
map.put("d", 5);
map.put("e", 1);
map.entrySet().parallelStream()
.sorted(reverseOrder(comparingInt(Map.Entry::getValue)))
.limit(2)
.forEach(System.out::println);
//or: .forEachOrdered(System.out::println);
//to print in descending order
NOTE: from b129 on, you can also use sorted(comparingInt(Map.Entry::getValue).reversed()) instead of sorted(reverseOrder(comparingInt(Map.Entry::getValue))).

Categories