Handling nested Collections with Java 8 streams - java

Lately I came across a problem during working with nested collections (values of Maps inside a List):
List<Map<String, Object>> items
This list in my case contains 10-20 Maps.
At some point I had to replace value Calculation of key description to Rating. So I come up with this solution:
items.forEach(e -> e.replace("description","Calculation","Rating"));
It would be quite fine and efficient solution if all maps in this list will contain key-Value pair ["description", "Calculation"]. Unfortunately, I know that there will be only one such pair in the whole List<Map<String, Object>>.
The question is:
Is there a better (more efficient) solution of finding and replacing this one value, instead of iterating through all List elements using Java-8 streams?
Perfection would be to have it done in one stream without any complex/obfuscating operations on it.

items.stream()
.filter(map -> map.containsKey("description"))
.findFirst()
.ifPresent(map -> map.replace("description", "Calculation", "Rating"));
You will have to iterate over the list until a map with the key "description" is found. Pick up the first such, and try to replace.
As pointed out by #Holger, if the key "description" isn't single for all the maps, but rather the pair ("description", "Calculation") is unique:
items.stream()
.anyMatch(m -> m.replace("description", "Calculation", "Rating"));

Related

How to use Java streams to create a list out of a map site

Starting with a map like:
Map<Integer, String> mapList = new HashMap<>();
mapList.put(2,"b");
mapList.put(4,"d");
mapList.put(3,"c");
mapList.put(5,"e");
mapList.put(1,"a");
mapList.put(6,"f");
I can sort the map using Streams like:
mapList.entrySet()
.stream()
.sorted(Map.Entry.<Integer, String>comparingByKey())
.forEach(System.out::println);
But I need to get list (and a String) of the correspondent sorted elements (that would be: a b c d e f) that do correspond with the keys: 1 2 3 4 5 6.
I cannot find the way to do it in that Stream command.
Thanks
As #MA says in his comment I need a mapping and that is not explained in this question: How to convert a Map to List in Java?
So thank you very much #MA
Sometimes people are too fast into closing questions!
You can use a mapping collector:
var sortedValues = mapList.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.mapping(Entry::getValue, Collectors.toList()))
You could also use some of the different collection classes instead of streams:
List<String> list = new ArrayList<>(new TreeMap<>(mapList).values());
The downside being that if you do all that in a single line it can get quite messy, quite fast. Additionally you're throwing away the intermediate TreeMap just for the sorting.
If you want to sort on the keys and collect only the values, you need to use a mapping function to only preserve the values after your sorting. Afterwards you can just collect or do a foreach loop.
mapList.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.map(Map.Entry::getValue)
.collect(Collectors.toList());

Can we collect two lists from Java 8 streams?

Consider I have a list with two types of data,one valid and the other invalid.
If I starting filter through this list, can i collect two lists at the end?
Can we collect two lists from Java 8 streams?
You cannot but if you have a way to group elements of the Lists according to a condition.
In this case you could for example use Collectors.groupingBy() that will return Map<Foo, List<Bar>> where the values of the Map are the two List.
Note that in your case you don't need stream to do only filter.
Filter the invalid list with removeIf() and add all element of that in the first list :
invalidList.removeIf(o -> conditionToRemove);
goodList.addAll(invalidList);
If you don't want to change the state of goodList you can do a shallow copy of that :
invalidList.removeIf(o -> conditionToRemove);
List<Foo> terminalList = new ArrayList<>(goodList);
terminalList.addAll(invalidList);
This is a way using Java 8 streams API. Consider I have a List of String elements: the input list has strings with various lengths; only strings with length 3 are valid.
List<String> input = Arrays.asList("one", "two", "three", "four", "five");
Map<Boolean, List<String>> map = input.collect(Collectors.partitioningBy(s -> s.length() == 3));
System.out.println(map); // {false=[three, four, five], true=[one, two]}
The resulting Map has only two records; one with valid and the other with not-valid input list elements. The map record with key=true has the valid string as a List: one, two. The other is a key=false and the not-valid strings: three, four, five.
Note the Collectors.partitioningBy produces always two records in the resulting map irrespective of the existence of valid or not-valid values.
Another suggestion: filter using a lambda that adds the elements that you want to filter out of the stream to a separate list.
The collector can only return a single object!
But you could create a custom collector that simply puts the stream elements into two lists, to then return a list of lists containing these two lists.
There are many examples how to do that.
If you need to classify binary (true|false), you can use Collectors.partitioningBy that will return Map<Boolean, List> (or other downstream collection like Set, if you additionally specify).
If you need more than 2 categories - use groupBy or just Collectors.toMap of collections.

Java Streams: Organize a collection into a map and select smallest key

I'm pretty sure this is not possible in one line, but I just wanted to check:
List<WidgetItem> selectedItems = null;
Map<Integer, List<WidgetItem>> itemsByStockAvailable = WidgetItems.stream()
.collect(Collectors.groupingBy(WidgetItem::getAvailableStock));
selectedItems = itemsByStockAvailable.get(
itemsByStockAvailable.keySet().stream().sorted().findFirst().get());
Basically I'm collecting all widget items into a map where the key is the availableStock quantity and the value is a list of all widgets that have that quantity (since multiple widgets might have the same value). Once I have that map, I would want to select the map's value that corresponds to the smallest key. The intermediate step of creating a Map isn't necessary, it's just the only way I could think of to do this.
It appears what you want is to keep all the widget items that were grouped with the lowest available stock. In that case, you can collect the grouped data into a TreeMap to ensure the ordering based on increasing values of the stock and retrieve the first entry with firstEntry()
List<WidgetItem> selectedItems =
widgetItems.stream()
.collect(Collectors.groupingBy(
WidgetItem::getAvailableStock,
TreeMap::new,
Collectors.toList()
))
.firstEntry()
.getValue();
The advantage is that it is done is one-pass over the initial list.
Essentially you want to get all the input elements which are minimal according to the custom comparator Comparator.comparingInt(WidgetItem::getAvailableStock). In general this problem could be solved without necessity to store everything into the intermediate map creating unnecessary garbage. Also it could be solved in single pass. Some interesting solutions already present in this question. For example, you may use the collector implemented by Stuart Marks:
List<WidgetItem> selectedItems = widgetItems.stream()
.collect(maxList(
Comparator.comparingInt(WidgetItem::getAvailableStock).reversed()));
Such collectors are readily available in my StreamEx library. The best suitable in your case is MoreCollectors.minAll(Comparator):
List<WidgetItem> selectedItems = widgetItems.stream()
.collect(MoreCollectors.minAll(
Comparator.comparingInt(WidgetItem::getAvailableStock)));
If you want to avoid creating the intermediate map, you can first determine the smallest stock value, filter by that value and collect to list.
int minStock = widgetItems.stream()
.mapToInt(WidgetItem::getAvailableStock)
.min()
.getAsInt(); // or throw if list is empty
List<WidgetItem> selectItems = widgetItems.stream()
.filter(w -> minStock == w.getAvailableStock())
.collect(toList());
Also, do not use sorted().findFirst() to find the min value of a stream. Use min instead.
You can find the smallest key in a first pass and then get all the items having that smallest key:
widgetItems.stream()
.map(WidgetItem::getAvailableStock)
.min(Comparator.naturalOrder())
.map(min ->
widgetItems.stream()
.filter(item -> item.getAvailableStock().equals(min))
.collect(toList()))
.orElse(Collections.emptyList());
I would collect the data into a NavigableMap, which involves only a small change to your original code:
List<WidgetItem> selectedItems = null;
NavigableMap<Integer, List<WidgetItem>> itemsByStockAvailable =
WidgetItems.stream()
.collect(Collectors.groupingBy(WidgetItem::getAvailableStock,
TreeMap::new, Collectors.toList()));
selectedItems = itemsByStockAvailable.firstEntry().getValue();

which datastructure for this hashmap scenario

I have a scenario where i store values in a hashmap.
Keys are strings like
fruits
fruits_citrus_orange
fruits_citrus_lemon
fruits_fleshly_apple
fruits_fleshly
fruits_dry
and so on.
Values are some objects. Now for a given input say fruits_fleshly i need to retrieve all cases where it starts with "fruits_fleshly"
In the above case I need to fetch
fruits_fleshly_apple
fruits_fleshly
One way to do this is by doing String.indexOf over all the keys. Is there any other effective way to do this instead of iterating over all the keys in a map
though these are strings, but to me, it looks like these are certain categories & sub categories, like fruit, fruit-freshly, fruit-citrus etc..
If that is a case you can instead implement a Tree data-structure. This would be most effective for search operation.
since Tree has a parent-child structure, there is a root node & child node. You can have a structure like this:
(0) (1) (2)
fruit
|_____citrus
| |_____lemon
| |_____orange
|
|_____freshly
|_____apple
|_____
in this structure, say if you want to search for citrus fruit, you can just go to citrus, and list all its child. And finally you can construct full name by concatenating the name as a path from root to leaves.
Iterating the map seems quite simple and straight-forward way of doing this. However, since you don't want to iterate over keys on your own, you can use Guava's Maps#filterEntries, if you are ok with using 3rd party library.
Here's how it would work:
Map<String, Object> = Maps.filterEntries(
yourMap,
Predicate.containsPattern("^fruits_fleshly"));
But, that would too iterate over the map in the backyard. So, iteration is still there, if you are bothered about efficiency.
Since HashMap doesn't maintain any order for its keys it's not a very good choice for this problem. A better choice is the TreeMap: it has methods for retrieving a sub map for a range of keys. These methods run in O(log n) time (n number of entries) so it's better than iterating over the keys.
Map subMap = myMap.subMap("fruits_fleshly", true, "fruits_fleshly\uffff", true);
The nature of a hashmap means that there's no way to do a "like" comparison on keys - you have to iterate over them all to find where key.startsWith(input).
I suppose you could nest hashmaps and split up your keys. E.g.,
{
"fruits":{
"citrus":{
"orange":(value),
"lemon":(value)
},
"fleshly":{
"apple":(value),
"":(value)
}
}
}
...etc.
The performance implications are probably horrific on a small scale, but that may not matter in a homework context but maybe not so bad if you're dealing with a lot of data and only a couple layers of nesting.
Alternatively, create a Category object with a List of Categories (sub-categories) and a List of entries.
I believe Radix Trie is what you are looking for. It is similar idea as #ay89 solution.
You can just use this open source library Radix Trie example. It perform better than O(log(N)). You will be able to find a hashmap assigned to a key in average constant time (number of underscores in your search key string) with a decent implementation of Radix Trie.fruits
fruits_citrus_orange
fruits_citrus_lemon
fruits_fleshly_apple
fruits_fleshly
fruits_dry
Trie<String, Map> trie = new PatriciaTrie<>;
trie.put("fruits", hashmap1);
trie.put("fruits_citrus_orange", hashmap2);
trie.put("fruits_citrus_lemon", hashmap3);
trie.put("fruits_fleshly_apple", hashmap4);
trie.put("fruits_fleshly", hashmap5);
Map.Entry<String, Map> entry = trie.select("fruits_fleshy");
If you just want one hashmap to be return by select you might be able to get slightly better performance if you implement your own Radix Trie.

nth item of hashmap

HashMap selections = new HashMap<Integer, Float>();
How can i get the Integer key of the 3rd smaller value of Float in all HashMap?
Edit
im using the HashMap for this
for (InflatedRunner runner : prices.getRunners()) {
for (InflatedMarketPrices.InflatedPrice price : runner.getLayPrices()) {
if (price.getDepth() == 1) {
selections.put(new Integer(runner.getSelectionId()), new Float(price.getPrice()));
}
}
}
i need the runner of the 3rd smaller price with depth 1
maybe i should implement this in another way?
Michael Mrozek nails it with his question if you're using HashMap right: this is highly atypical scenario for HashMap. That said, you can do something like this:
get the Set<Map.Entry<K,V>> from the HashMap<K,V>.entrySet().
addAll to List<Map.Entry<K,V>>
Collections.sort the list with a custom Comparator<Map.Entry<K,V>> that sorts based on V.
If you just need the 3rd Map.Entry<K,V> only, then a O(N) selection algorithm may suffice.
//after edit
It looks like selection should really be a SortedMap<Float, InflatedRunner>. You should look at java.util.TreeMap.
Here's an example of how TreeMap can be used to get the 3rd lowest key:
TreeMap<Integer,String> map = new TreeMap<Integer,String>();
map.put(33, "Three");
map.put(44, "Four");
map.put(11, "One");
map.put(22, "Two");
int thirdKey = map.higherKey(map.higherKey(map.firstKey()));
System.out.println(thirdKey); // prints "33"
Also note how I take advantage of Java's auto-boxing/unboxing feature between int and Integer. I noticed that you used new Integer and new Float in your original code; this is unnecessary.
//another edit
It should be noted that if you have multiple InflatedRunner with the same price, only one will be kept. If this is a problem, and you want to keep all runners, then you can do one of a few things:
If you really need a multi-map (one key can map to multiple values), then you can:
have TreeMap<Float,Set<InflatedRunner>>
Use MultiMap from Google Collections
If you don't need the map functionality, then just have a List<RunnerPricePair> (sorry, I'm not familiar with the domain to name it appropriately), where RunnerPricePair implements Comparable<RunnerPricePair> that compares on prices. You can just add all the pairs to the list, then either:
Collections.sort the list and get the 3rd pair
Use O(N) selection algorithm
Are you sure you're using hashmaps right? They're used to quickly lookup a value given a key; it's highly unusual to sort the values and then try to find a corresponding key. If anything, you should be mapping the float to the int, so you could at least sort the float keys and get the integer value of the third smallest that way
You have to do it in steps:
Get the Collection<V> of values from the Map
Sort the values
Choose the index of the nth smallest
Think about how you want to handle ties.
You could do it with the google collections BiMap, assuming that the Floats are unique.
If you regularly need to get the key of the nth item, consider:
using a TreeMap, which efficiently keeps keys in sorted order
then using a double map (i.e. one TreeMap mapping integer > float, the other mapping float > integer)
You have to weigh up the inelegance and potential risk of bugs from needing to maintain two maps with the scalability benefit of having a structure that efficiently keeps the keys in order.
You may need to think about two keys mapping to the same float...
P.S. Forgot to mention: if this is an occasional function, and you just need to find the nth largest item of a large number of items, you could consider implementing a selection algorithm (effectively, you do a sort, but don't actually bother sorting subparts of the list that you realise you don't need to sort because their order makes no difference to the position of the item you're looking for).

Categories

Resources