I want to create a new map from the keyset() of an existing HashMap that maps a key to its index in the map.
Here's my attempt, which works:
Map<String, Integer> keysToIndex = new HashMap<>();
Integer index = 0;
for (String obj: hashMap.keySet()) {
keysToIndex.put(obj, index);
index += 1;
}
Does Java 8 provide features to create a map of each key's index, or is there a more idiomatic way to write this snippet?
Sets aren't ordered, so the "index" of an element in a Set is arbitrary (albeit unique). The "index" is simply the current size of the map, so you can use the result map's state to provide the index:
Map<String, Integer> keysToIndex = new HashMap<>();
hashMap.keySet().forEach(k -> keysToIndex.put(k, keysToIndex.size()));
This is more or less what your code is doing, but more compact since no extra variables or instructions are needed.
Incidentally, you can drop one more method call by using forEach() directly on the source map (and ignoring the value argument of the BiConsumer):
Map<String, Integer> keysToIndex = new HashMap<>();
hashMap.forEach((k, v) -> keysToIndex.put(k, keysToIndex.size()));
Related
Is there a way to "apply" null safety to a multiple assignment from a null? For example, this piece of code would obviously throw:
// verbosity is for clarity
def (a,b) = new HashMap<String, List<String>>().get("nothing")
but it would be neat to have it define a and b with null value.
So far I came up with
def (a,b) = new HashMap<String, List<String>>().get("nothing").with {[it?.get(0), it?.get(1)]}
But that's really ugly...
This is the safe bet, if you have both missing keys and null values:
def (a,b) = new HashMap<String, List<String>>().get("nothing") ?: []
The next one is from Groovy, but modifies the underlying map (so only
works for mutable maps and whether you are fine with modifying it) and
it only gives the default for missing keys:
def (a,b) = new HashMap<String, List<String>>().get("nothing", [])
Similar (also from Groovy and also modifying the underlying map):
withDefault. This option is great if you plan to pass the map around
to other places, that would have to deal with the default all over again
(also only defaults for missing keys):
def (a,b) = new HashMap<String, List<String>>().withDefault{[]}.get("nothing")
And the next one is from Java, but that also only falls back, if the key
is missing:
def (a,b) = new HashMap<String, List<String>>().getOrDefault("nothing",[])
Normally I think of Groovy's inject method as equivalent to Java 8's reduce, but I seem to have hit an unusual situation.
Say I have a POJO (or POGO) called Book
class Book {
int id
String name
}
If I have a collection of books and want to convert them to a map where the keys are the ids and the values are the books, then in Groovy it's easy enough to write:
Map bookMap = books.inject([:]) { map, b ->
map[b.id] = b
map
}
i.e., for each book, add it to the map under the book's id and return the map.
In Java 8, the same operation would take a completely different approach. Either this:
Map<Integer, Book> bookMap = books.stream()
.collect(Collectors.toMap(Book::getId, b -> b));
or, equivalently,
bookMap = books.stream()
.collect(Collectors.toMap(Book::getId, Function.identity()));
the difference being a matter of style.
What I'm wondering, however, is if there is a reduce operation in Java 8 that would be similar to the inject from Groovy. I can't just mimic what I did in Groovy, because in Java 8 the signature for reduce is:
T reduce(T identity, BinaryOperator<T> accumulator)
The BinaryOperator means that both elements of the lambda expression must be of the same type. If it was a BiFunction, I could make the lambda's first argument a HashMap<Integer, Book> and the second argument a Book, but I can't do that with a BinaryOperator. I know there's a three-argument version of reduce, but that doesn't seem to help either.
Am I missing something obvious? Is it just that inject is more general that reduce? Since I already have an idiomatic way of solving the problem in Java, this isn't critical, but I was struck by the differences here.
Yo Ken! :-D
You need the 3 parameter form of reduce, so given:
List<Book> books = Arrays.asList(
new Book(1, "Book One"),
new Book(2, "Tim's memoirs"),
new Book(3, "Harry Potter and the sarcastic cat")
);
You can do:
Map<Integer, Book> reduce = books.stream().reduce(
new HashMap<Integer, Book>(),
(map, value) -> {
map.put(value.id, value);
return map;
},
(a, b) -> {
a.putAll(b);
return a;
}
);
To give:
{
1=Book{id=1, name='Book One'},
2=Book{id=2, name='Tim's memoirs'},
3=Book{id=3, name='Harry Potter and the sarcastic cat'}
}
The first parameter is the thing to collect into:
new HashMap<Integer, Book>(),
The second parameter is a BiFunction that takes the current accumulator, and the next element in the stream, and combines them somehow:
(map, value) -> {
map.put(value.id, value);
return map;
},
The third binary operator in that reduce call:
(a, b) -> {
a.putAll(b);
return a;
}
Is how to join all the resulting maps back together assuming you are running a parallel stream...
put and putAll returning void make it a fugly mess :-( But I guess chaining wasn't a popular thing back in the late 90s...
I'm trying to use the Groovy way of creating a TreeMap<String, List<Data>> with default values so I easily add data to a new list if the key isn't already present.
TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>) [:].withDefault { [] }
As you can see, I have the requirement to use a TreeMap and withDefault only returns a Map instance, so I need to cast.
When I attempt to add a new list to the map,
myData[newKey].add(newData)
myData[newKey] is null. However, if I change my Map initilization to remove the TreeMap cast (and change the type to just Map instead of TreeMap), myData[newKey].add(newData) works as expected.
What's the reasoning for this? Can I not use withDefault if I cast the map?
The problem isn't just about the cast. It also has to do with the declared type. The problem can be simplified to something like this:
def map1 = [:].withDefault { 0 }
TreeMap map2 = map1
When that is executed map1 is an instance of groovy.lang.MapWithDefault and map2 is an instance of java.util.TreeMap. They are 2 separate objects on the heap, not just 2 references pointing to the same object. map2 will not have any default behavior associated with it. It is as if you had done this:
def map1 = [:].withDefault { 0 }
TreeMap map2 = new TreeMap(map1)
That is what is happening with your code. The cast and the generics just makes it less clear with your code.
This:
TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>) [:].withDefault { [] }
Can be broken down to this:
def tmpMap = [:].withDefault { [] }
TreeMap<String, List<Data>> myData = (TreeMap<String, List<Data>>)tmpMap
I hope that helps.
EDIT:
Another way to see the same thing happening is to do something like this:
Set names = new HashSet()
ArrayList namesList = names
When the second line executes a new ArrayList is created as if you had done ArrayList namesList = new ArrayList(names). That looks different than what you have in your code, but the same sort of thing is happening. You have a reference with a static type associated with it and are pointing that reference at an object of a different type and Groovy is creating an instance of your declared type. In this simple example above, that declared type is ArrayList. In your example that declared type is TreeMap<String, List<Data>>.
This is my code so far, what am I doing wrong? I need to create a HashMap of <Integer,ArrayList<String>> which will map each word of the test data keyed to the length of the word. Then, display the test String and iterate through the keys and display the words ordered by length. I am not entirely sure what I am doing wrong. Should I use an iterator as opposed to a for-each loop, or would that not make much of a difference?
This is my code thus far
HashMap<Integer,String>map = new HashMap<Integer,String>();
// adds values
map.put(2, "The");
map.put(8, "Superbowl");
Instead of an HashMap, you should use a SortedMap: "A Map that further provides a total ordering on its keys" (JDK 1.7 API doc). A TreeMap would do.
A sorted map will return its keys in sorted order. Since your keys are Integer (hence Comparable), their natural ordering is used.
To list the words by increasing length:
SortedMap<Integer, List<String>> wordsByLength; // filled somewhere
// Iterates over entries in increasing key order
Set<Map.Entry<Integer, List<String>> entries = wordsByLength.entrySet();
for ( Map.Entry<Integer, List<String>> entry : entries ) {
int length = entry.getKey();
List<String> words = entry.getValue();
// do what you need to do
}
Since order is not gauranteed in a HashMap you would need to pull out the keys and sort them before using the sorted key list to loop through the values like so:
Map<Integer,String>map = new HashMap<Integer,String>();
map.put(2, "The");
map.put(8, "Superbowl");
List<Integer> keyList = new ArrayList<Integer>(map.keySet());
Collections.sort(keyList);
for (Integer key : keyList) {
System.out.println(map.get(key));
}
However in your use case where words are the same length you would end up overwriting previous entries in the Map which would not be what you want. A better solution would be to store the words in a List and sort them using a Comparator that compared String lengths like this:
List<String> words = new ArrayList<String>();
// Loop through adding words if they don't already exist in the List
// sort the List by word length
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.valueOf(s1.length()).compareTo(s2.length());
}
});
//Now loop through sorted List and print words
for (String word : words) {
System.out.println(word);
}
Let say we have :
Map hm = new HashMap();
How to avoid putting duplicate values(Emplyees) in this HashMap?
I assume you are coding in Java, so:
if(!myMap.containsKey(myKey)){
myMap.put(myKey, myValue);
}
The good thing with HashMap is that the containsKey method takes constant time (or constant amortized time) regardless of the number of elements in your map so you can call it without bothering of the time it may take!
If you use an other language, the logic remains the same.
I think duplicate values in Map can be removed using this generic method if your userdefined object is overridden with equals and hashcode from object class
public static <K, V > Map<K,V> genericMethodtoDeleteMapduplicate(Map<K, V> pMap) {
Map<K,V> mapWithoutDup=new HashMap<>();
Set<V> totalvaluesPresent=new HashSet<>();
for (Map.Entry<K, V> a : pMap.entrySet()) {
if(totalvaluesPresent.add(a.getValue())){
mapWithoutDup.put(a.getKey(), a.getValue());
}
}
return mapWithoutDup;
}
Not sure what language you are using but in java for Hashmap their are:
boolean containsKey(Object key)
- Returns true if this map contains a mapping for the
specified key.
and
boolean containsValue(Object value)
- Returns true if this map maps one or more keys to the
specified value.
Just call which ever one makes more sense for you to check if the entry is already in the map, if it is then you know its a duplicate, otherwise put it in. I'm certain whatever language you are using will have something similar!!