How to put the string stored in the treeMap in java - java

I have a String a = "1x^2 2x^3 3x^4 4x^4 " and I want to store the power in each term as key and coefficient as value to treeMap. Since there is two same power with different coefficients, I need to sum up coefficients so that the result is {2=1,3=2,4=7}. Because treemap.put(key) can only override the previous value with the same key so I could only get the result as{2=1,3=2,4=4}. How to solve that?

It is recommended to use Map::merge function to accumulate the values for multiple identical keys:
Map<Integer, Integer> terms = new TreeMap<>();
for (String term : a.split("\\s+")) {
String[] pair = term.split("x\\^");
terms.merge(Integer.valueOf(pair[1]), Integer.valueOf(pair[0]), Integer::sum);
}
System.out.println(terms);
Output:
{2=1, 3=2, 4=7}
Using Stream API this can be resolved using Collectors.toMap in a similar way using Integer::sum as a merge function:
String a = "1x^2 2x^3 3x^4 4x^4 ";
Map<Integer, Integer> terms = Arrays.stream(a.split("\\s+")) // Stream<String>
.map(t -> t.split("x\\^")) // Stream<String[]>
.collect(Collectors.toMap(
t -> Integer.valueOf(t[1]), // power as key
t -> Integer.valueOf(t[0]), // coefficient as value
Integer::sum, // add coefficients for the same power
TreeMap::new // supply TreeMap as result
));
System.out.println(terms);
Output:
{2=1, 3=2, 4=7}

Related

Convert Map<Integer, List<Strings> to Map<String, List<Integer>

I'm having a hard time converting a Map containing some integers as keys and a list of random strings as values.
E.g. :
1 = ["a", "b", "c"]
2 = ["a", "b", "z"]
3 = ["z"]
I want to transform it into a Map of distinct strings as keys and lists the integers as values.
E.g. :
a = [1, 2]
b = [1, 2]
c = [1]
z = [2,3]
Here's what I have so far:
Map<Integer, List<String>> integerListMap; // initial list is already populated
List<String> distinctStrings = new ArrayList<>();
SortedMap<String, List<Integer>> stringListSortedMap = new TreeMap<>();
for(Integer i: integers) {
integerListMap.put(i, strings);
distinctStrings.addAll(strings);
}
distinctStrings = distinctStrings.stream().distinct().collect(Collectors.toList());
for(String s : distinctStrings) {
distinctStrings.put(s, ???);
}
Iterate over your source map's value and put each value into the target map.
final Map<String, List<Integer>> target = new HashMap<>();
for (final Map.Entry<Integer, List<String>> entry = source.entrySet()) {
for (final String s : entry.getValue()) {
target.computeIfAbsent(s, k -> new ArrayList<>()).add(entry.getKey());
}
}
Or with the Stream API by abusing Map.Entry to build the inverse:
final Map<String, List<Integer>> target = source.entrySet()
.stream()
.flatMap(e -> e.getValue().stream().map(s -> Map.entry(s, e.getKey()))
.collect(Collectors.groupingBy(e::getKey, Collectors.mapping(e::getValue, Collectors.toList())));
Although this might not be as clear as introducing a new custom type to hold the inverted mapping.
Another alternative would be using a bidirectial map. Many libraries come implementations of these, such as Guava.
There's no need to apply distinct() since you're storing the data into the Map and keys are guaranteed to be unique.
You can flatten the entries of the source map, so that only one string (let's call it name) and a single integer (let's call it number) would correspond to a stream element, and then group the data by string.
To implement this problem using streams, we can utilize flatMap() operation to perform one-to-many transformation. And it's a good practice to define a custom type for that purpose as a Java 16 record, or a class (you can also use a Map.Entry, but note that approach of using a custom type is more advantages because it allows writing self-documenting code).
In order to collect the data into a TreeMap you can make use of the three-args version of groupingBy() which allows to specify mapFactory.
record NameNumber(String name, Integer number) {}
Map<Integer, List<String>> dataByProvider = Map.of(
1, List.of("a", "b", "c"),
2, List.of("a", "b", "z"),
3, List.of("z")
);
NavigableMap<String, List<Integer>> numbersByName = dataByProvider.entrySet().stream()
.flatMap(entry -> entry.getValue().stream()
.map(name -> new NameNumber(name, entry.getKey()))
)
.collect(Collectors.groupingBy(
NameNumber::name,
TreeMap::new,
Collectors.mapping(NameNumber::number, Collectors.toList())
));
numbersByName.forEach((name, numbers) -> System.out.println(name + " -> " + numbers));
Output:
a -> [2, 1]
b -> [2, 1]
c -> [1]
z -> [3, 2]
Sidenote: while using TreeMap it's more beneficial to use NavigableMap as an abstract type because it allows to access methods like higherKey(), lowerKey(), firstEntry(), lastEntry(), etc. which are declared in the SortedMap interface.

Generate a Map from a list using Streams with Java 8

I have a list of String.
I want to store each string as key and the string's length as value in a Map (say HashMap).
I'm not able to achieve it.
List<String> ls = Arrays.asList("James", "Sam", "Scot", "Elich");
Map<String,Integer> map = new HashMap<>();
Function<String, Map<String, Integer>> fs = new Function<>() {
#Override
public Map<String, Integer> apply(String s) {
map.put(s,s.length());
return map;
}
};
Map<String, Integer> nmap = ls
.stream()
.map(fs).
.collect(Collectors.toMap()); //Lost here
System.out.println(nmap);
All strings are unique.
There's no need to wrap each and every string with its own map, as the function you've created does.
Instead, you need to provide proper arguments while calling Collectors.toMap() :
keyMapper - a function responsible for extracting a key from the stream element.
valueMapper - a function that generates a value from the stream element.
Hence, you need the stream element itself to be a key we can use Function.identity(), which is more descriptive than lambda str -> str, but does precisely the same.
Map<String,Integer> lengthByStr = ls.stream()
.collect(Collectors.toMap(
Function.identity(), // extracting a key
String::length // extracting a value
));
In case when the source list might contain duplicates, you need to provide the third argument - mergeFunction that will be responsible for resolving duplicates.
Map<String,Integer> lengthByStr = ls.stream()
.collect(Collectors.toMap(
Function.identity(), // key
String::length, // value
(left, right) -> left // resolving duplicates
));
You said there would be no duplicate Strings. But if one gets by you can use distinct() (which internally uses set) to ensure it doesn't cause issues.
a-> a is a shorthand for using the stream value. Essentially a lambda that returns its argument.
distinct() removes any duplicate strings
Map<String, Integer> result = names.stream().distinct()
.collect(Collectors.toMap(a -> a, String::length));
If you want to get the length of a String, you can do it immediately as someString.length(). But suppose you want to get a map of all the Strings keyed by a particular length. You can do it using Collectors.groupingBy() which by default puts duplicates in a list. In this case, the duplicate would be the length of the String.
use the length of the string as a key.
the value will be a List<String> to hold all strings that match that length.
List<String> names = List.of("James", "Sam", "Scot",
"Elich", "lucy", "Jennifer","Bob", "Joe", "William");
Map<Integer, List<String>> lengthMap = names.stream()
.distinct()
.collect(Collectors.groupingBy(String::length));
lengthMap.entrySet().forEach(System.out::println);
prints
3=[Sam, Bob, Joe]
4=[Scot, lucy]
5=[James, Elich]
7=[William]
8=[Jennifer]

How to find max key with some conditions in HashMap using Java 8 / Streams?

Suppose I have following data :
Question :
I want to find zscore of largest orderId where Pair is 'AB', OrderType is 'Buy' and status is 'InProgress'.
NOTE: I stored this data into HashMap name is orderBook where Key is orderId and Value is OrderModel (PairName, OrderType, Status, zscore).
Solution 1 :
int maxOrderId = 0 ;
getOrderBook().entrySet().stream()
.filter(e -> e.getValue().getPairName().equals("AB")
&& e.getValue().getCompletedStatus().equals("InProgress")
&& e.getValue().getOrderType().equals("Buy"))
.forEach(o -> {
if (maxOrderId < o.getKey()) {
maxOrderId = o.getKey();
}
});
double zscore = getOrderBook().get(maxOrderId).getzScore();
System.out.println("Order ID :"+ maxOrderId +", Zscore :"+zscore);
output : Order ID : 5, Zscore : -2.5
I can find zscore using above code but I want to find in one go.
So How can I find the zscore of largest OrderId using Java 8 / streams in one line ?
Is there any better way than my code ?
What you're looking for is the max method:
Optional<Entry<Long,Order>> maxIdEntry = getOrderBook()
.entrySet()
.stream()
.filter(/* your filter logic */)
.max(Comparator.comparing(Entry::getKey));
This yields an Optional, so either use the isPresent() and get() methods or the ifPresent(Consumer<T> consumer) method for processing the result
You can use max() using Comparator to get largest OrderId and use map of Optional to map zScore .
double zscore = getOrderBook()
.entrySet()
.stream()
.filter(e -> e.getValue().getPairName().equals("AB")
&& e.getValue().getCompletedStatus().equals("InProgress")
&& e.getValue().getOrderType().equals("Buy"))
.max(Comparator.comparing(Entry::getKey))
.map(e -> e.getValue().getzScore())
.orElse(0);
The already existing answer are excellent. There are more ways:
How about using TreeMap which is able to keep the keys sorted? As long as the key is ex. a String, you don't even need to pass a Comparator.
// create a copy of HashMap as a TreeMap
NavigableMap<String, Order> navigableMap = new TreeMap<>(getOrderBook());
// remove unwanted entries (inverted condition)
navigableMap.entrySet().removeIf(e ->
!e.getValue().getPairName().equals("AB") ||
!e.getValue().getCompletedStatus().equals("InProgress") ||
!e.getValue().getOrderType().equals("Buy"));
// NavigableMap::lastEntry gets an entry with the highest key (by the comparator)
double zscore = sortedMap.lastEntry().getValue().getzScore();

Testing for negative values in a TreeMap

TreeMap Format:
TreeMap<String, ArrayList<Double>> mapName = new TreeMap<>();
I'm trying to test the ArrayList<Double> for negative values. This is for a method so my string values aren't always consistent. I've tried to pull the values by using mapName.Values() but this is probably the wrong way to go about it.
If you want to know if there is a single negative number in the whole treeMap:
mapName.values().stream()
.flatMap(Collection::stream)
.anyMatch(d -> d < 0);
If you want to put your hand on the lists which have at least one negative number:
mapName.entrySet().stream()
.filter(entry -> entry.getValue().stream().anyMatch(d -> d < 0))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

Lambda to populate Map

I am trying to fill up a map with words and the number of their occurrences. I am trying to write a lambda to do it, like so:
Consumer<String> wordCount = word -> map.computeIfAbsent(word, (w) -> (new Integer(1) + 1).intValue());
map is Map<String, Integer>. It should just insert the word in the map as a key if it is absent and if it is present it should increase its integer value by 1. This one is not correct syntax-wise.
You can't increment the count using computeIfAbsent, since it will only be computed the first time.
You probably meant:
map.compute(word, (w, i) -> i == null ? 1 : i + 1);
This is what Collectors are for.
Assuming you have some Stream<String> words:
Map<String, Long> countedWords = words
.collect(Collectors
.groupingBy(
Function.identity(),
Collectors.counting());
It doesn't compile because you can't call a method on a primitive:
new Integer(1) -> 1 // unboxing was applied
(1 + 1).intValue() // incorrect
I would write it with Map#put and Map#getOrDefault:
Consumer<String> consumer = word -> map.put(word, map.getOrDefault(word, 0) + 1);

Categories