Is there a better way to retrieve values from a Map - java

I've the following code
final Map<String, Location> map = new HashMap<>();
map.put("1", new Location("a", null));
map.put("2", new Location("b", null));
map.put("3", new Location("c", null));
final List<String> list = new ArrayList<>();
for (final Location s : map.values()) {
list.add(s.getId());
}
The result is a,b,c (as expected) when I print the list.
for (final String string : list) {
System.out.println(string);
}
Is there a better way of getting the Id's without using the for loop in Java6.
As per Java8, referencing the code form #rohit-jain answer
final List<String> list = map.values().stream().map(loc -> loc.getId()).collect(Collectors.toList());
Is there anything this consise in java6?

Not sure about efficiency (because it wouldn't affect much), but if you want to do that using lambdas, it can be like this:
final Map<String, Location> locationMap = new HashMap<>();
locationMap.put("1", new Location("a", null));
locationMap.put("2", new Location("b", null));
locationMap.put("3", new Location("c", null));
final List<String> list = locationMap.values().stream()
.map(loc -> loc.getId())
.collect(Collectors.toList());
System.out.println(list); // can be `[a, b, c]` or `[b, c, a]`, etc
But, just because you don't see for loop here doesn't mean that it isn't iterating over the values of the map. It does, but just hides the iteration logic.
Or, if you just want to print the value (one time usage), you can even avoid creating a list there:
locationMap.values().stream().map(loc -> loc.getId())
.forEach(System.out::println);

EDIT: The question was modified, and now does no longer seem to refer to efficiency. Now, this answer does not really fit any more, but for now, I'll leave it here, maybe someone finds it helpful
First a general hint: You said
The result is a,b,c (as expected) when I print the list.
However, you should not expect that. The HashMap is not sorted in any way. The order of the elments could be different. And also important: If you added more elements to the map, then the order of the elements that have previously be contained in the map may change!
If you want the order of the elements during the iteration to be the same as the insertion order, you should use a LinkedHashMap instead of a HashMap. It preserves the iteration order, and there, your expectation about the output would be met.
Interestingly, this leads to the question about the performance:
Iterating over a LinkedHashMap in fact can be (noticably) faster than iterating over a Map. Here's a small microbenchmark, which, as always, should be taken with a grain of salt:
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
public class MapIteration
{
public static void main(String[] args)
{
long sum = 0;
for (int size=100000; size<=1000000; size += 100000)
{
Map<String, Integer> mapA =
new HashMap<String, Integer>();
fillMap(mapA, size);
Map<String, Integer> mapB =
new LinkedHashMap<String, Integer>();
fillMap(mapB, size);
int runs = 100;
long beforeA = System.nanoTime();
for (int i=0; i<runs; i++)
{
sum += computeValueSum(mapA);
}
long afterA = System.nanoTime();
double durationA = (afterA - beforeA) / 1e6;
long beforeB = System.nanoTime();
for (int i=0; i<runs; i++)
{
sum += computeValueSum(mapB);
}
long afterB = System.nanoTime();
double durationB = (afterB - beforeB) / 1e6;
System.out.printf(
"Normal size %10d duration %10.3f\n", size, durationA);
System.out.printf(
"Linked size %10d duration %10.3f\n", size, durationB);
}
System.out.println(sum);
}
private static void fillMap(Map<String, Integer> map, int n)
{
Random random = new Random(0);
for (int i=0; i<n; i++)
{
map.put(String.valueOf(i), random.nextInt(n));
}
}
private static long computeValueSum(Map<?, Integer> map)
{
long sum = 0;
for (Integer i : map.values())
{
sum += i;
}
return sum;
}
}
For me, it prints...
...
Normal size 1000000 duration 2611,288
Linked size 1000000 duration 1796,030
Again, this should not be taken for granted, unless it is verified with a proper Micobenchmarking framework, but frankly: The LinkedHashMap is 30% faster, give or take a few, and I doubt that a Micobenchmarking framework will tell me the opposite here.
In general, I basically always use LinkedHashMap instead of a plain HashMap. But not because of the performance, but mainly because of the consistent iteration order. Regarding the performance: The insertions and deletions in a LinkedHashMap may be a tad more expensive than for a HashMap, but these performance differences are negligible in most cases.

If you use Eclipse Collections, you can write the following:
MutableMap<String, Location> map = Maps.mutable.empty();
map.put("1", new Location("a", null));
map.put("2", new Location("b", null));
map.put("3", new Location("c", null));
List<String> list = map.collect(Location::getId).toSortedList();
Bag<String> bag = map.collect(Location::getId);
Assert.assertEquals(Lists.mutable.with("a", "b", "c"), list);
Assert.assertEquals(Bags.mutable.with("a", "b", "c"), bag);
The following code will also work with Java 5 - 7:
Function<Location, String> function = new Function<Location, String>()
{
public String valueOf(Location location)
{
return location.getId();
}
};
List<String> list = map.collect(function).toSortedList();
Bag<String> bag = map.collect(function);
Assert.assertEquals(Lists.mutable.with("a", "b", "c"), list);
Assert.assertEquals(Bags.mutable.with("a", "b", "c"), bag);
Note: I am a committer for Eclipse Collections

If you like to use a Java 6 compatible version, then you could use Guava and its Function interface:
public class ExtractLocationId implements Function<Location, String> {
#Override
public String apply(final Location location) {
return location.getId();
}
}
And use it like this:
final List<String> list =
FluentIterable.from(map.values()).transform(new ExtractLocationId()).toList();
It needs more code than the Java 8 Lambda version (due to the own Function implementation) but it supports older Java versions.

You cannot run away from iteration over map at some point.
For maps most efficient way is to iterate over "entrySet()".

Related

Split 1 ArrayList into multiple ones based on condition

I have a big ArrayList containing strings. I want to split it, based on a condition that an element meets. For example, it could be the string length if ArrayList contained String. What is the most efficient (not the easiest) way to do that ?
['a', 'bc', 'defe', 'dsa', 'bb']
after would result to :
['a'], ['bc', 'bb'], ['dsa'], ['defe']
It's easy and fairly efficient to do it using Java 8 streams:
Collection<List<String>> output = input.stream()
.collect(Collectors.groupingBy(String::length))
.values();
If you run it with this input:
List<String> input = Arrays.asList("a", "bc", "defe", "dsa", "bb");
You will get this output:
[[a], [bc, bb], [dsa], [defe]]
A non-stream version would do the same thing, i.e. build a Map<K, List<V>> where V is your value type (e.g. String in your case), and K is the type of the grouping value (e.g. Integer for the length).
Doing it yourself (like shown in answer by palako) might be slightly more efficient at runtime, but likely not in any way that matters.
Staying with Java 8, that would be this:
Map<Integer, List<String>> map = new HashMap<>();
for (String value : input)
map.computeIfAbsent(value.length(), ArrayList::new).add(value);
Collection<List<String>> output = map.values();
For earlier versions of Java, you can't use computeIfAbsent(), so:
Map<Integer, List<String>> map = new HashMap<Integer, List<String>>();
for (String value : input) {
Integer length = Integer.valueOf(value.length()); // box only once
List<String> list = map.get(length);
if (list == null)
map.put(length, list = new ArrayList<String>());
list.add(value);
}
Collection<List<String>> output = map.values();
The most efficient way is to iterate the original list just once. What you do is you create buckets and add to those buckets.
public class Q1 {
public static void main(String[] args) {
String[] original = {"a","bc","defe","dsa","bb"};
List<String> originalValues = new ArrayList<String>(Arrays.asList(original));
Map<Integer, List<String>> orderedValues = new HashMap<Integer, List<String>>();
Iterator<String> it = originalValues.iterator();
while (it.hasNext()) {
String currentElement = it.next();
int length = currentElement.length();
if(!orderedValues.containsKey(length)) {
orderedValues.put(length, new ArrayList<String>());
}
orderedValues.get(length).add(currentElement);
}
System.out.println(orderedValues.values());
}
}
You might be tempted to use an array of arrays instead of a Map, and use the size of the string as the index to the array position, but then you need to watch out for a case where you don't have strings of a certain length. Imagine a case where you only have a string in the original list, but it has 100 characters. You would have 99 empty positions and one string in the array at position 100.

Get top 3 counts from list using stream

I am trying to convert java7 program into java 8. I want below output using stream API.
public List<String> getTopThreeWeatherCondition7() {
List<String> _Top3WeatherList = new ArrayList<String>();
Map<String, Integer> _WeatherCondMap = getWeatherCondition7();
List<Integer> _WeatherCondList = new ArrayList<Integer>(_WeatherCondMap.values());
Collections.sort(_WeatherCondList, Collections.reverseOrder());
List<Integer> _TopThreeWeathersList = _WeatherCondList.subList(0, 3);
Set<String> _WeatherCondSet = _WeatherCondMap.keySet();
Integer count = 0;
for (String _WeatherCond : _WeatherCondSet) {
count = _WeatherCondMap.get(_WeatherCond);
for (Integer _TopThreeWeather : _TopThreeWeathersList) {
if (_TopThreeWeather == count) {
_Top3WeatherList.add(_WeatherCond);
}
}
}
_WeatherCondList = null;
_WeatherCondMap = null;
_TopThreeWeathersList = null;
_WeatherCondSet = null;
return _Top3WeatherList;
}
I strongly suggests to adhere to Java coding conventions. Start variable names with a lower case letter instead of _+upper case letter. Second, don’t assign local variables to null after use. That’s obsolete and distracts from the actual purpose of the code. Also, don’t initialize variables with an unused default (like the count = 0). In this specific case, you should also declare the variable within the inner loop, where it is actually used.
Note also that you are comparing Integer references rather than values. In this specific case it might work as the objects originate from the same map, but you should avoid that. It’s not clear whether there might be duplicate values; in that case, this loop will not do the right thing. Also, you should not iterate over the keySet(), just to perform a get lookup for every key, as there is entrySet() allowing to iterate over key and value together.
Since you said, this code ought to be a “Java 7 program” you should mind the existence of the “diamond operator” (<>) which removes the need to repeat type arguments when creating new instances of generic classes.
Instead of sorting the values only and searching for the associated keys, you should sort the entries in the first place.
So a clean Java 7 variant of your original code would be:
static final Comparator<Map.Entry<String, Integer>> BY_VALUE_REVERSED=
new Comparator<Map.Entry<String, Integer>>() {
public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
return Integer.compare(o2.getValue(), o1.getValue());
}
};
public List<String> getTopThreeWeatherCondition7() {
List<String> top3WeatherList = new ArrayList<>();
Map<String, Integer> weatherCondMap = getWeatherCondition7();
List<Map.Entry<String, Integer>> entryList=new ArrayList<>(weatherCondMap.entrySet());
Collections.sort(entryList, BY_VALUE_REVERSED);
List<Map.Entry<String, Integer>> topThreeEntries = entryList.subList(0, 3);
for(Map.Entry<String, Integer> entry: topThreeEntries) {
top3WeatherList.add(entry.getKey());
}
return top3WeatherList;
}
This also handles duplicates correctly. Only if there is a tie on the third place, just one of the valid candidates will be chosen.
Only if you have a clean starting point, you may look, how this can benefit from Java 8 features
Instead of copying the content to a List to sort it, you can create a Stream right from the Map and tell the stream to sort
You can create a comparator much easier, or even use one of the new builtin comparators
You can chain the task of limiting the result to three elements, map to the key and collect to the result List right to the stream of the previous steps:
public List<String> getTopThreeWeatherCondition7() {
Map<String, Integer> weatherCondMap = getWeatherCondition7();
List<String> top3WeatherList =
weatherCondMap.entrySet().stream()
.sorted(Collections.reverseOrder(Map.Entry.comparingByValue()))
.limit(3)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
return top3WeatherList;
}

Finding number of values in a HashMap?

What is the best / most efficient way to find the total number of Values in a HashMap.
I do not mean the .size() method as it counts the number of keys. I want the total number of values in all the keys.
I want to do so as my key is a String, but my value is a List.
In Java 8, you can also utilize the Stream API:
int total = map.values()
.stream()
.mapToInt(List::size) // or (l -> l.size())
.sum()
This has the advantage that you don't have to repeat the List<Foo> type for a for variable, as in the pre-Java 8 solution:
int total = 0;
for (List<Foo> list : map.values())
{
total += list.size();
}
System.out.println(total);
In addition to that, although not advised, you could also use that value inline without needing a temp variable:
System.out.println(map.values().stream().mapToInt(List::size).sum());
Easiest would be, iterate and add over list sizes.
int total = 0;
for (List<Foo> l : map.values()) {
total += l.size();
}
// here is the total values size
Starting with Java 8, given a Map<K, List<V>> map you could use the Stream API and have:
int size = map.values().stream().mapToInt(List::size).sum();
This creates a Stream of the values with stream(), maps each of them to their size with mapToInt, where the mapper is the method reference List::size refering to List#size(), and sum the results with sum().
Say you have a map
Map<String, List<Object>> map = new HashMap<>();
You can do this by calling the values() method and calling size() method for all the lists:
int total = 0;
Collection<List<Object>> listOfValues = map.values();
for (List<Object> oneList : listOfValues) {
total += oneList.size();
}
If I understand the question correctly, you have a Map<String, List<Something>>, and you want to count the total number of items in all the Lists in the Map's values. Java 8 offers a pretty easy way of doing this by streaming the values, mapping them to their size() and then just summing them:
Map<String, List<Something>> map = ...;
int totalSize = map.values().stream().mapToInt(List::size).sum());
With Eclipse Collections, the following will work using MutableMap.
MutableMap<String, List<String>> map =
Maps.mutable.of("key1", Lists.mutable.of("a", "b", "c"),
"key2", Lists.mutable.of("d", "e", "f", "g"));
long total = map.sumOfInt(List::size);
Note: I am a committer for Eclipse Collections.
import java.util.HashMap;
public class Solution {
public static void main(String args[]) {
int total = 0;
HashMap<String,String> a = new HashMap<String,String>();
a.put("1.","1");
a.put("2.","11");
a.put("3.","21");
a.put("4.","1211");
a.put("5.","111221");
for (String l : a.values()) {
total ++;
}
System.out.println(total);
}
}

Sort Map by Top Scores

I have a map that stores a players name and there score on which i update and things fine.
public static Map<String, Integer> map = new HashMap<String, Integer>();
After the first round of my game finishes i want to remove the lowest scores from the map. For instance, There could be 8 players in the map 4 of whom have a score of 10 and the other 4 have a score of 0 how would i split the map in half based on the top scorers and remove the bottom 4? Keep in mind the 8 players is not defined, it could be any number
No one seems to have picked up on this not being a Mapping. If you look at high scores, names can be duplicates. What you want is an NavigableSet where
class HighScore implements Comparable<HighScore> {
private static final AtomicLong NEXT_ID = new AtomicLong(1);
protected final String name;
protected final long id = NEXT_ID.getAndIncrement();
protected final int score;
// ...
public int compareTo(HighScore o) {
int diff = score - o.score;
if (diff != 0) { return diff; }
long idDiff = id - o.id;
if (idDiff < 0) { return -1; }
else if (idDiff > 0) { return 1; }
else { return 0; }
}
}
And then you can just pollFirst() to remove.
Below is what might help you. I populated the Map with same values for key and values, to see the order when it is printed. The below example includes
a way of getting the top half view without removing the bottom half from original map
and also removing bottom half from original map.
Unless the requirement is to remove do not need to remove bottom half, still get the headMap that is backed by the original map.
import java.util.NavigableMap;
import java.util.TreeMap;
public class HalfTheMap {
static void addValues(TreeMap<String, Integer> map)
{
map.put("11", 11);
map.put("33", 33);
map.put("77", 77);
map.put("44", 44);
map.put("55", 55);
map.put("22", 22);
//map.put("66", 66);
}
public static void main(String[] args) {
TreeMap<String, Integer> map = new TreeMap<String, Integer>();
addValues(map);
System.out.printf("Original Map Initial Values : %s\n",map);
int size = map.size();
int midIndex = (size/2) - 1;
System.out.printf("size : %d \nmid : %d\n", size, midIndex);
// retrieve key of middle element
String midKey = (String)map.keySet().toArray()[midIndex];
// Top half view of the original map
NavigableMap<String, Integer> topMap = map.headMap(midKey, true);
System.out.printf("Top half map : %s\n", topMap);
// remove the bottom half from original map.
map.tailMap(midKey, false).clear();
System.out.printf("Original map after bottom half removed : %s\n", map);
}
}
Prints :
Original Map Initial Values : {11=11, 22=22, 33=33, 44=44, 55=55, 77=77}
size : 6
mid : 2
Top half map : {11=11, 22=22, 33=33}
Original map after bottom half removed : {11=11, 22=22, 33=33}
I am leaving the fine tuning of halving when size is odd value to you and any other fine tuning that are appropriate to your needs.
NOTE : I have noticed that my example using keys and values of same value in each entry seems to show that it could be a solution, infact not exactly. Though it demonstrates the usage of some important methods to solve the problem.
As answered by David Ehrmann above, changing the collections to Set rather than Map and using a class that modal name and score could be a better solution.
Hope this helps.
But there's a simplier way to do this.
You can use custom sorter algorithm on TreeMap.
For example: TreeMap map = new TreeMap(Your own comparator);
You can write your own comparator implementing the Comparator interface.
Example (taken from StackOverflow, don't know more precisely):
class ValueComparator implements Comparator {
Map<String, Integer> base;
public ValueComparator(Map<String, Integer> base) {
this.base = base;
}
public int compare(String a, String b) {
if (base.get(a) >= base.get(b)) {
return -1;
} else {
return 1;
} // returning 0 would merge keys
}
}
Then, you only have to edit this code, to ascending order, then you only have to remove the first X. elements from.
I found this post that might help. One you cannot sort a Hashmap because there is no definitive order to it. If you want to sort through a LinkedHashMap though, this is how you do it. (A LinkedHashMap just has a definitive iterative order)
public LinkedHashMap sortHashMapByValues(HashMap passedMap) {
List mapKeys = new ArrayList(passedMap.keySet());
List mapValues = new ArrayList(passedMap.values());
Collections.sort(mapValues);
Collections.sort(mapKeys);
LinkedHashMap sortedMap = new LinkedHashMap();
Iterator valueIt = mapValues.iterator();
while (valueIt.hasNext()) {
Object val = valueIt.next();
Iterator keyIt = mapKeys.iterator();
while (keyIt.hasNext()) {
Object key = keyIt.next();
String comp1 = passedMap.get(key).toString();
String comp2 = val.toString();
if (comp1.equals(comp2)){
passedMap.remove(key);
mapKeys.remove(key);
sortedMap.put((String)key, (Double)val);
break;
}
}
}
return sortedMap;
}
If you want to remove the lowest value on the other hand, I doubt this is the easiest thing you could do something like this to sort them.
public static Entry<String, Integer> removeLowest(LinkedHashMap<String, Integer> map){
Entry<String, Integer> lowest = null;
for(Entry<String,Integer> e: map){
if(lowest==null || e.getValue().compareTo(lowest.getValue()) < 0){
lowest = e;
}
}
return lowest;
}
PS: Don't forget to accept my answer if it works for you.
Update: If you want to remove say half of the map. You would sort it first then do this.
public static LinkedHashMap<String, Integer> getTopHalf(LinkedHashMap<String, Integer> map){
LinkedHashMap<String, Integer> sorted = sortHashMapByValues(map);
LinkedHashMap<String, Integer> out = new LinkedHashMap<String, Integer>();
Iterator<Entry<String,Integer>> it = sorted.entrySet().iterator();
for(int i = 0; i<map.size()/2; i++){
Entry<String, Integer> e = it.next();
out.put(e.getKey(), e.getValue());
}
return out;
}

How select first N items in Java TreeMap?

Given this map
SortedMap<Integer, String> myMap = new TreeMap<Integer, String>();
Instead of a for loop is there a utility function to copy first N items to a destination map?
Using the power of Java 8+:
TreeMap<Integer, String> myNewMap = myMap.entrySet().stream()
.limit(3)
.collect(TreeMap::new, (m, e) -> m.put(e.getKey(), e.getValue()), Map::putAll);
Maybe, but not as part of the standard Java API. And: the utility would use a loop inside.
So you'll need a loop, but you can create your own "utility" by doing it all in a static method in a utility class:
public static SortedMap<K,V> putFirstEntries(int max, SortedMap<K,V> source) {
int count = 0;
TreeMap<K,V> target = new TreeMap<K,V>();
for (Map.Entry<K,V> entry:source.entrySet()) {
if (count >= max) break;
target.put(entry.getKey(), entry.getValue());
count++;
}
return target;
}
The complexity is still O(n) (I doubt, that one can achieve O(1)) but you use it like a tool without "seeing" the loop:
SortedMap<Integer, String> firstFive = Util.putFirstEntries(5, sourceMap);
There's SortedMap.headMap() however you'd have to pass a key for the element to go up to. You could iterate N elements over Map.keySet() to find it, e.g.:
Integer toKey = null;
int i = 0;
for (Integer key : myMap.keySet()) {
if (i++ == N) {
toKey = key;
break;
}
}
// be careful that toKey isn't null because N is < 0 or >= myMap.size()
SortedMap<Integer, String> copyMap = myMap.headMap(toKey);
You can also use an ordored iterator to get the first x records, orderer by descending id for instance :
Iterator<Integer> iterator = myMap.descendingKeySet().iterator();
You can use the putAll(Map t) function to copy the items from the map to specified map.But it copies all the items. You cannot copy fixed number of items.
http://download.oracle.com/javase/1.4.2/docs/api/java/util/Map.html#putAll%28java.util.Map%29

Categories

Resources