Java grouping by back to List of objects? - java

I have a simple pojo:
class Pojo
{
string key;
int value;
}
This is in a list:
List<Pojo> myList;
I can have multiple values per key, so for example:
A 5
A 7
B 3
So I want to group by the key and sum the values and put it back into a List<Pojo> with a single entry for A 12, B 3, etc.
This is what I have right now:
myMap.get("xxx")
.values()
.stream()
.collect(Collectors.groupingBy(Pojo::getKey), Collectors.summingLong(Pojo::getValue))
This gets me a Map<String, Int>.
Do I need to do something like:
myMap.get("xxx")
.values()
.stream()
.collect(Collectors.groupingBy(Pojo::getKey), Collectors.summingLong(Pojo::getValue))
.entrySet().stream.map((entry) -> new Pojo(..)).collect(Collectors.toList())
Or is there a cleaner way to do it?

There are several ways to do it. Sometimes the simplest is the best and most efficient. Here is one of them.
List<Pojo> list = List.of(new Pojo("A", 5), new Pojo("B", 3),
new Pojo("A", 7), new Pojo("C", 10), new Pojo("B", 12));
Map<String, Pojo> map = new HashMap<>();
The way this works is as follows:
it requires a simple setter in your class to set the value.
Iterate thru the list and if the map doesn't contain it, add it.
else use the just retreived object and add its value and the current object's value and set the sum in the Map's version of the object. So the map maintains a tally of the update without replacing the object in the map.
for (Pojo p : list) {
Pojo cp;
if ((cp = map.get(p.getKey())) != null) {
cp.setValue(cp.getValue() + p.getValue());
} else {
map.put(p.getKey(), p);
}
}
This next step could be eliminated if you don't mind working with type Collection.
List<Pojo> results = new ArrayList<>(map.values());
Now print them
results.forEach(Sytem.out::println);
Prints
A 12
B 15
C 10
On the other hand, if you prefer streams this works sans a setter. But it does create a new object to replace the old one.
Map<String, Pojo> map = list.stream()
.collect(Collectors.toMap(Pojo::getKey, s -> s,
(a, b) -> new Pojo(a.getKey(),
a.getValue() + b.getValue())));
map.values().forEach(Sytem.out::println);
The pojo class
class Pojo {
String key;
int value;
public Pojo(String key, int value) {
this.key = key;
this.value = value;
}
public int getValue() {
return value;
}
public String getKey() {
return key;
}
public void setValue(int v) {
this.value = v;
}
public String toString() {
return key + " " + value;
}
}

It should work when using Collectors::reducing.
myMap.get("xxx").values().stream()
.collect(Collectors.groupingBy(Pojo::getKey, Collectors.reducing(
new Pojo(null, 0),
(a, b) -> new Pojo(b.getKey(), a.getValue() + b.getValue())))
.values()
The call to groupingBy would return a Map<String, List<Pojo>>, but providing a reducing collector as second argument to groupingBy takes the values of the list and reduces them to a single element. The result is a Map<String, Pojo>, and in order to get a Collection<Pojo>, we simply need to call values() on the Map.

Related

Collect all the Keys from a HashMap associated with the Lowest into a List

How to sort HashMap entries by Value and print all the Keys mapped the Lowest Value
Here is my HashMap.
HashMap<String, Integer> map = new HashMap<>();
map.put("John", 1);
map.put("Matthew", 12);
map.put("Clara", 53);
map.put("Keith", 2);
Expected output:
John
I am trying to sort them, so I can get "John" to be collected in the list.
List<String> keys = res.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue())
.limit(1)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Now, let's consider another map.
HashMap<String, Integer> map = new HashMap<>();
map.put("John", 2);
map.put("Matthew", 12);
map.put("Clara", 53);
map.put("Keith", 2);
Expected output:
John, Keith
How can I get both John and Keith to be added to the list in case of a tie (i.e. both associated with the lowest value) ?
Collect the entries into a Map<Integer, List<Map.Entry>> grouping by their values, get the entries that correspond to the minimum value, then convert those entries to a list of their keys:
List<String> lowestValuedNames = map.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getValue))
.get(Collections.min(map.values()))
.stream()
.map(Map.Entry::getKey)
.collect(Collectors.toList());
See live demo.
Not particularly efficient, but done using one line.
It can be done without sorting, as well as without creating an intermediate map (and performing an additional iteration over its entries to find the entry with the lowest key). I.e. we can do better in terms of both memory allocation and performance.
For that, we need to maintain a collection of entries having the lowest value encountered so far and compare the value of every encountered entry with the value of the first element in the collection. If the collection is empty or if values are the same, we need to add the next entry. If the value of the next entry is lower, we need to clear the collection and then add the entry. And the value of the next entry is greater than we should ignore it.
To implement this approach with streams, we can create a custom collector using static method Collector.of().
That's how it can be implemented:
public static void main(String[] args) {
Map<String, Integer> people =
Map.of("John", 2, "Matthew", 12,
"Clara", 53, "Keith", 2);
List<String> result = people.entrySet().stream()
.collect(Collector.of(
ArrayDeque::new,
(Deque<Map.Entry<String, Integer>> deque, Map.Entry<String, Integer> next) -> {
if (!deque.isEmpty() && deque.peek().getValue() > next.getValue()) deque.clear();
if (deque.isEmpty() || deque.peek().getValue().equals(next.getValue())) deque.add(next);
},
(left, right) -> {
if (left.isEmpty()) return right;
if (right.isEmpty()) return left;
if (left.peek().getValue() < right.peek().getValue()) return left;
if (left.peek().getValue() > right.peek().getValue()) return right;
left.addAll(right);
return left;
},
deque -> deque.stream().map(Map.Entry::getKey).collect(Collectors.toList())
));
System.out.println(result);
}
The collector is still performing the final iteration over the resulting collection of entries to extract the keys. We can omit this step and simplify the code by encapsulating the logic that currently resides in the collector into a separate class. And the instance of that class would be used by the collector as a mutable container instead of a collection and will be responsible for doing all the housekeeping.
class EntryContainer implements Consumer<Map.Entry<String, Integer>> {
private int value;
private List<String> names = new ArrayList<>();
#Override
public void accept(Map.Entry<String, Integer> next) {
if (!names.isEmpty() && next.getValue() < value) names.clear();
if (names.isEmpty() || value == next.getValue()) {
names.add(next.getKey());
value = next.getValue();
}
}
public EntryContainer merge(EntryContainer other) {
if (names.isEmpty() || !other.names.isEmpty() && other.value < value) return other;
if (other.names.isEmpty() || value < other.value) return this;
names.addAll(other.names);
return this;
}
public List<String> getNames() {
return names;
}
}
Now we can apply it. The collector no longer looks intimidating, and we don't need a finisher function, instead the collection of names is being handed out by the collector's container at the end of the stream execution:
public static void main(String[] args) {
Map<String, Integer> people =
Map.of("John", 2, "Matthew", 12,
"Clara", 53, "Keith", 2);
List<String> result = people.entrySet().stream()
.collect(Collector.of(
EntryContainer::new,
EntryContainer::accept,
EntryContainer::merge
))
.getNames();
System.out.println(result);
}
Output:
[Keith, John]

Check if multiple objects have same value of a field in java and remove duplicates based on other fields

So I have a list of objects. Suppose they have 2 fields startDate, endDate(data type is timestamp). So if startDate is equal to startDate of another object then I have to choose the object with higher endDate. How can I achieve this efficiently. I can use 2 for loops but that would have a high time complexity. Any better way of doing it? Thanks
Stream over your list, collect to map using your objects startdate as key and use a merging function to decide to which object to map if two or more objects have the same startdate by comparing the enddates. Something like:
Collection<YourObject> result =
yourList.stream()
.collect(Collectors.toMap(YourObject::getStartDate,
Function.identity(),
(a, b) -> a.getEndDate().after(b.getEndDate()) ? a : b))
.values();
Here's an example that uses Integer instead of a date to make it easier to read but the principal is the same. Just change the comparison operators to suit and ensure your date class is usable as a map key.
Test class:
class Test {
final Integer start;
final Integer end;
public Test(Integer s, Integer e) {
this.start = s;
this.end = e;
}
#Override
public String toString() {
return start + " " + end;
}
}
Code example using a few instances of the Test class:
List<Test> l = Arrays.asList(new Test(1, 2), new Test(3, 4), new Test(1, 3), new Test(1, 1));
Map<Integer, Test> m = l.stream()
.collect(
Collectors.toMap(
o -> o.start,
Function.identity(),
(e, r) -> r.end > e.end ? r : e));
m.values().forEach(System.out::println);
Output:
1 3
3 4
You can use an HashMap for example and take advantage of the compute method:
hashMap.compute(newObject.getStartDate(), (key, value) ->
if (value == null) {
newObject;
} else if (value.getEndDate().after(newObject.getEndDate())) {
value;
} else {
newObject;
}
)

Java 8 stream sum entries for duplicate keys

I am using Java 8 streams to group a list of entries by a certain key and then sorting the groups by date. What I would like to do in addition is to "collapse" any two entries within a group that have the same date and sum them up. I have a class like this (stripped down for example purposes)
class Thing {
private String key;
private Date activityDate;
private float value;
...
}
Then I'm grouping them like so:
Map<String, List<Thing>> thingsByKey = thingList.stream().collect(
Collectors.groupingBy(
Thing::getKey,
TreeMap::new,
Collectors.mapping(Function.identity(), toSortedList())
));
private static Collector<Thing,?,List<Thing>> toSortedList() {
return Collectors.collectingAndThen(toList(),
l -> l.stream().sorted(Comparator.comparing(Thing::getActivityDate)).collect(toList()));
}
What I would like to do is, if any two Thing entries have the exact same date, sum up the values for those and collapse them down such that,
Thing1
Date=1/1/2017
Value=10
Thing2
Date=1/1/2017
Value=20
Turns into 30 for 1/1/2017.
What's the best way to accomplish something like that?
I have slightly change your Thing class to use LocalData and added a very simple toString:
#Override
public String toString() {
return " value = " + value;
}
If I understood correctly, than this is what you need:
Map<String, TreeMap<LocalDate, Thing>> result = Arrays
.asList(new Thing("a", LocalDate.now().minusDays(1), 12f), new Thing("a", LocalDate.now(), 12f), new Thing("a", LocalDate.now(), 13f))
.stream()
.collect(Collectors.groupingBy(Thing::getKey,
Collectors.toMap(Thing::getActivityDate, Function.identity(),
(Thing left, Thing right) -> new Thing(left.getKey(), left.getActivityDate(), left.getValue() + right.getValue()),
TreeMap::new)));
System.out.println(result); // {a={2017-06-24= value = 12.0, 2017-06-25= value = 25.0}}
This can be accomplished using the toMap collector:
Map<Date, Thing> thingsByDate = things.stream().collect(Collectors.toMap(
Thing::getActivityDate,
Function.identity(),
(thing1, thing2) -> new Thing(null, thing1.getActivityDate(), thing1.getValue()+thing2.getValue())
);
You may then do with this map as you wish.

How to translate list A,B to keyed map of tuples with guava

I apologize if this question is a duplicate, searching was difficult as I was unsure of the proper name for what I'm trying to accomplish. The simplest explanation would be
List<A>, List<B> into Map<Key, Tuple<A,B>> where A.Key matched B.Key
To clarify: I have a list of A object and B object that share a key. I'd like to then correlate these two lists into a map where the key matches into a map of key, and tuple A,B.
I've played around with many ideas on how to do this in my head, but most of them end with me feeling like I've misused the library (such as Maps.uniqueIndex, and Iterables.transform). Can anyone point me in the right direction?
There are no tuple (pair etc.) implementations in Guava. (It's another discussion if it's good idea to implementation tuples in Java at all.) The natural mapping I would suggest is to use a Multimap:
List<A> as = Lists.newArrayList(new A(1, "a"), new A(3, "c"), new A(2, "b"));
List<B> bs = Lists.newArrayList(new B(1, 2), new B(3, 6), new B(5, 10));
Function<WithKey, Object> toKey = new Function<WithKey, Object>() {
#Override public Object apply(WithKey input) { return input.key(); }
};
ImmutableListMultimap<Object, AbstractWithKey> index =
Multimaps.index(Iterables.concat(as, bs), toKey);
or
Multimap<Object, WithKey> m = ArrayListMultimap.create();
for (WithKey w : Iterables.concat(as, bs)) m.put(w.key(), w);
You have to check your invariants before using the multimap (or while your iterating over the multimap entries) for example there could be keys with only a A or B instance. (This shouldn't be a performance issue as it can be done lazily with Iterables.filter.)
Duplicates of one type is another issue. You could check them or use a HashMultimap to ignore them. You could even build a multimap with a constrainted set for values that checks that a value is unique (see Multimaps.newSetMultimap(Map> map, Supplier> factory) and Constraints.constrainedSet(Set set, Constraint constraint)). This has the advantage that it fails fast.
With these A and B implementations:
interface WithKey {
Object key();
}
abstract class AbstractWithKey implements WithKey {
Object key;
Object v;
#Override public Object key() { return key; }
#Override public String toString() {
return MoreObjects.toStringHelper(this).add("k", key).add("v", v).toString();
}
}
class A extends AbstractWithKey {
public A(int i, String v) {
key = i;
this.v = v;
}
}
class B extends AbstractWithKey {
public B(int i, int v) {
key = i;
this.v = v;
}
}
the output is:
{1=[A{k=1, v=a}, B{k=1, v=2}], 2=[A{k=2, v=b}], 3=[A{k=3, v=c}, B{k=3,
v=6}], 5=[B{k=5, v=10}]}
Update:
If you have to end up with your tuple instances, you can transform the Multimap.
Multimap<Object, WithKey> m = ArrayListMultimap.create();
for (WithKey w : Iterables.concat(as, bs)) m.put(w.key(), w);
Function<Collection<WithKey>, Tuple> f =
new Function<Collection<WithKey>, Tuple>(){
#Override public Tuple apply(Collection<WithKey> input) {
Iterator<WithKey> iterator = input.iterator();
return new Tuple(iterator.next(), iterator.next());
} };
Map<Object, Tuple> result = Maps.transformValues(m.asMap(), f);
Output ((a,b) is the tuple syntax):
{1=(A{k=1, v=a},B{k=1, v=2}), 3=(A{k=3, v=c},B{k=3, v=6})}
Are you guaranteed that keys are unique? (That is, that no two A's have the same key?)
If so, I'd write something like the following:
Map<Key, A> aMap = Maps.uniqueIndex(theAs, aKeyFunction); // Guava!
Map<Key, B> bMap = Maps.uniqueIndex(theBs, bKeyFunction);
Map<Key, AWithMatchingB> joinedMap = Maps.newHashMap();
for(Map.Entry<Key, A> aEntry : aMap.entrySet()) {
joinedMap.put(aEntry.getKey(), AWithMatchingB.match(
aEntry.getValue(), bMap.get(aEntry.getKey())));
}
If you're not guaranteed that aMap.keySet().equals(bMap.keySet()), then you'd modify this appropriately: check whether or not there's a matching B or not, etc.
Sorting the lists by key and transforming the two lists to tuples without much help from Guava is quite readable:
Comparator<WithKey>c = new Comparator<WithKey>(){
#Override public int compare(WithKey o1, WithKey o2) {
return o1.key().compareTo(o2.key());
}
};
Collections.sort(as, c);
Collections.sort(bs, c);
Preconditions.checkArgument(as.size() == bs.size());
Iterator<A> aIt = as.iterator();
Iterator<B> bIt = bs.iterator();
Map<Integer, Tuple> ts = Maps.newHashMap();
while(aIt.hasNext()) {
A a = aIt.next();
B b = bIt.next();
Preconditions.checkArgument(a.key().equals(b.key()));
ts.put(a.key(), new Tuple(a, b));
}
Output ((a,b) is the tuple syntax):
{1=(A{k=1, v=a},B{k=1, v=2}), 3=(A{k=3, v=c},B{k=3, v=6})}
This can be implemented nicer when Guava supports zip similar to Python:
sa = [(1, "a"), (3, "c")]
sb = [(1, 2), (3, 6)]
sa.sort()
sb.sort()
vs = [(a[0], (a,b)) for (a, b) in zip(sa, sb)]

Map implementation with duplicate keys

I want to have a map with duplicate keys.
I know there are many map implementations (Eclipse shows me about 50), so I bet there must be one that allows this. I know it's easy to write your own map that does this, but I would rather use some existing solution.
Maybe something in commons-collections or google-collections?
You are searching for a multimap, and indeed both commons-collections and Guava have several implementations for that. Multimaps allow for multiple keys by maintaining a collection of values per key, i.e. you can put a single object into the map, but you retrieve a collection.
If you can use Java 5, I would prefer Guava's Multimap as it is generics-aware.
We don't need to depend on the Google Collections external library. You can simply implement the following Map:
Map<String, ArrayList<String>> hashMap = new HashMap<String, ArrayList>();
public static void main(String... arg) {
// Add data with duplicate keys
addValues("A", "a1");
addValues("A", "a2");
addValues("B", "b");
// View data.
Iterator it = hashMap.keySet().iterator();
ArrayList tempList = null;
while (it.hasNext()) {
String key = it.next().toString();
tempList = hashMap.get(key);
if (tempList != null) {
for (String value: tempList) {
System.out.println("Key : "+key+ " , Value : "+value);
}
}
}
}
private void addValues(String key, String value) {
ArrayList tempList = null;
if (hashMap.containsKey(key)) {
tempList = hashMap.get(key);
if(tempList == null)
tempList = new ArrayList();
tempList.add(value);
} else {
tempList = new ArrayList();
tempList.add(value);
}
hashMap.put(key,tempList);
}
Please make sure to fine tune the code.
Multimap<Integer, String> multimap = ArrayListMultimap.create();
multimap.put(1, "A");
multimap.put(1, "B");
multimap.put(1, "C");
multimap.put(1, "A");
multimap.put(2, "A");
multimap.put(2, "B");
multimap.put(2, "C");
multimap.put(3, "A");
System.out.println(multimap.get(1));
System.out.println(multimap.get(2));
System.out.println(multimap.get(3));
Output is:
[A,B,C,A]
[A,B,C]
[A]
Note: we need to import library files.
http://www.java2s.com/Code/Jar/g/Downloadgooglecollectionsjar.htm
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
or https://commons.apache.org/proper/commons-collections/download_collections.cgi
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;
You could simply pass an array of values for the value in a regular HashMap, thus simulating duplicate keys, and it would be up to you to decide what data to use.
You may also just use a MultiMap, although I do not like the idea of duplicate keys myself.
If you want iterate about a list of key-value-pairs (as you wrote in the comment), then a List or an array should be better. First combine your keys and values:
public class Pair
{
public Class1 key;
public Class2 value;
public Pair(Class1 key, Class2 value)
{
this.key = key;
this.value = value;
}
}
Replace Class1 and Class2 with the types you want to use for keys and values.
Now you can put them into an array or a list and iterate over them:
Pair[] pairs = new Pair[10];
...
for (Pair pair : pairs)
{
...
}
This problem can be solved with a list of map entry List<Map.Entry<K,V>>. We don't need to use neither external libraries nor new implementation of Map. A map entry can be created like this:
Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<String, Integer>("key", 1);
[June, 2021]
org.springframework.util.MultiValueMap
commons.apache.org - org.apache.commons.collections4
Learn from my mistakes...please don't implement this on your own.
Guava multimap is the way to go.
A common enhancement required in multimaps is to disallow duplicate keys-value pairs.
Implementing/changing this in a your implementation can be annoying.
In Guava its as simple as:
HashMultimap<String, Integer> no_dupe_key_plus_val = HashMultimap.create();
ArrayListMultimap<String, Integer> allow_dupe_key_plus_val = ArrayListMultimap.create();
No fancy libs required.
Maps are defined by a unique key, so dont bend them, use a list. Streams are mighty.
import java.util.AbstractMap.SimpleImmutableEntry;
List<SimpleImmutableEntry<String, String>> nameToLocationMap = Arrays.asList(
new SimpleImmutableEntry<>("A", "A1"),
new SimpleImmutableEntry<>("A", "A2"),
new SimpleImmutableEntry<>("B", "B1"),
new SimpleImmutableEntry<>("B", "B1"),
);
And thats it.
Usage examples:
List<String> allBsLocations = nameToLocationMap.stream()
.filter(x -> x.getKey().equals("B"))
.map(x -> x.getValue())
.collect(Collectors.toList());
nameToLocationMap.stream().forEach(x ->
do stuff with: x.getKey()...x.getValue()...
You can use a TreeMap with a custom Comparator in order to treat each key as unequal to the others. It would also preserve the insertion order in your map, just like a LinkedHashMap. So, the net result would be like a LinkedHashMap which allows duplicate keys!
This is a very simple implementation without the need of any third party dependencies or complications of MultiMaps.
import java.util.Map;
import java.util.TreeMap;
...
...
//Define a TreeMap with a custom Comparator
Map<Integer, String> map = new TreeMap<>((a, b) -> 1); // See notes 1 and 2
//Populate the map
map.put(1, "One");
map.put(3, "Three");
map.put(1, "One One");
map.put(7, "Seven");
map.put(2, "Two");
map.put(1, "One One One");
//Display the map entries:
map.entrySet().forEach(System.out::println);
//See note number 3 for the following:
Map<Integer, String> sortedTreeMap = map.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue,
(x, y) -> x, () -> new TreeMap<>((a, b) -> 1)
));
//Display the entries of this sorted TreeMap:
sortedTreeMap.entrySet().forEach(System.out::println);
...
Notes:
You can also use any positive integer in place of 1 in the comparator's definition here.
If you use any negative integer instead, then it will reverse the insertion order in your map.
If you also want to sort this map based on the keys (which is the default behavior of a TreeMap), then you may do this operation on the current map.
I had a slightly different variant of this issue: It was required to associate two different values with same key. Just posting it here in case it helps others, I have introduced a HashMap as the value:
/* #param frameTypeHash: Key -> Integer (frameID), Value -> HashMap (innerMap)
#param innerMap: Key -> String (extIP), Value -> String
If the key exists, retrieve the stored HashMap innerMap
and put the constructed key, value pair
*/
if (frameTypeHash.containsKey(frameID)){
//Key exists, add the key/value to innerHashMap
HashMap innerMap = (HashMap)frameTypeHash.get(frameID);
innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);
} else {
HashMap<String, String> innerMap = new HashMap<String, String>();
innerMap.put(extIP, connName+":"+frameType+":"+interfaceName);
// This means the key doesn't exists, adding it for the first time
frameTypeHash.put(frameID, innerMap );
}
}
In the above code the key frameID is read from a input file's first string in each line, the value for frameTypeHash is constructed by splitting the remaining line and was stored as String object originally, over a period of time the file started having multiple lines (with different values) associated with same frameID key, so frameTypeHash was overwritten with last line as the value. I replaced the String object with another HashMap object as the value field, this helped in maintaining single key to different value mapping.
class DuplicateMap<K, V>
{
enum MapType
{
Hash,LinkedHash
}
int HashCode = 0;
Map<Key<K>,V> map = null;
DuplicateMap()
{
map = new HashMap<Key<K>,V>();
}
DuplicateMap( MapType maptype )
{
if ( maptype == MapType.Hash ) {
map = new HashMap<Key<K>,V>();
}
else if ( maptype == MapType.LinkedHash ) {
map = new LinkedHashMap<Key<K>,V>();
}
else
map = new HashMap<Key<K>,V>();
}
V put( K key, V value )
{
return map.put( new Key<K>( key , HashCode++ ), value );
}
void putAll( Map<K, V> map1 )
{
Map<Key<K>,V> map2 = new LinkedHashMap<Key<K>,V>();
for ( Entry<K, V> entry : map1.entrySet() ) {
map2.put( new Key<K>( entry.getKey() , HashCode++ ), entry.getValue());
}
map.putAll(map2);
}
Set<Entry<K, V>> entrySet()
{
Set<Entry<K, V>> entry = new LinkedHashSet<Map.Entry<K,V>>();
for ( final Entry<Key<K>, V> entry1 : map.entrySet() ) {
entry.add( new Entry<K, V>(){
private K Key = entry1.getKey().Key();
private V Value = entry1.getValue();
#Override
public K getKey() {
return Key;
}
#Override
public V getValue() {
return Value;
}
#Override
public V setValue(V value) {
return null;
}});
}
return entry;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("{");
boolean FirstIteration = true;
for ( Entry<K, V> entry : entrySet() ) {
builder.append( ( (FirstIteration)? "" : "," ) + ((entry.getKey()==null) ? null :entry.getKey().toString() ) + "=" + ((entry.getValue()==null) ? null :entry.getValue().toString() ) );
FirstIteration = false;
}
builder.append("}");
return builder.toString();
}
class Key<K1>
{
K1 Key;
int HashCode;
public Key(K1 key, int hashCode) {
super();
Key = key;
HashCode = hashCode;
}
public K1 Key() {
return Key;
}
#Override
public String toString() {
return Key.toString() ;
}
#Override
public int hashCode() {
return HashCode;
}
}
1, Map<String, List<String>> map = new HashMap<>();
this verbose solution has multiple drawbacks and is prone to errors. It
implies that we need to instantiate a Collection for every value, check for
its presence before adding or removing a value, delete it manually when no
values are left, etcetera.
2, org.apache.commons.collections4.MultiMap interface
3, com.google.common.collect.Multimap interface
java-map-duplicate-keys
what about such a MultiMap impl?
public class MultiMap<K, V> extends HashMap<K, Set<V>> {
private static final long serialVersionUID = 1L;
private Map<K, Set<V>> innerMap = new HashMap<>();
public Set<V> put(K key, V value) {
Set<V> valuesOld = this.innerMap.get(key);
HashSet<V> valuesNewTotal = new HashSet<>();
if (valuesOld != null) {
valuesNewTotal.addAll(valuesOld);
}
valuesNewTotal.add(value);
this.innerMap.put(key, valuesNewTotal);
return valuesOld;
}
public void putAll(K key, Set<V> values) {
for (V value : values) {
put(key, value);
}
}
#Override
public Set<V> put(K key, Set<V> value) {
Set<V> valuesOld = this.innerMap.get(key);
putAll(key, value);
return valuesOld;
}
#Override
public void putAll(Map<? extends K, ? extends Set<V>> mapOfValues) {
for (Map.Entry<? extends K, ? extends Set<V>> valueEntry : mapOfValues.entrySet()) {
K key = valueEntry.getKey();
Set<V> value = valueEntry.getValue();
putAll(key, value);
}
}
#Override
public Set<V> putIfAbsent(K key, Set<V> value) {
Set<V> valueOld = this.innerMap.get(key);
if (valueOld == null) {
putAll(key, value);
}
return valueOld;
}
#Override
public Set<V> get(Object key) {
return this.innerMap.get(key);
}
#Override
etc. etc. override all public methods size(), clear() .....
}
Could you also explain the context for which you are trying to implement a map with duplicate keys? I am sure there could be a better solution. Maps are intended to keep unique keys for good reason. Though if you really wanted to do it; you can always extend the class write a simple custom map class which has a collision mitigation function and would enable you to keep multiple entries with same keys.
Note: You must implement collision mitigation function such that, colliding keys are converted to unique set "always". Something simple like, appending key with object hashcode or something?
just to be complete, Apache Commons Collections also has a MultiMap. The downside of course is that Apache Commons does not use Generics.
With a bit hack you can use HashSet with duplicate keys. WARNING: this is heavily HashSet implementation dependant.
class MultiKeyPair {
Object key;
Object value;
public MultiKeyPair(Object key, Object value) {
this.key = key;
this.value = value;
}
#Override
public int hashCode() {
return key.hashCode();
}
}
class MultiKeyList extends MultiKeyPair {
ArrayList<MultiKeyPair> list = new ArrayList<MultiKeyPair>();
public MultiKeyList(Object key) {
super(key, null);
}
#Override
public boolean equals(Object obj) {
list.add((MultiKeyPair) obj);
return false;
}
}
public static void main(String[] args) {
HashSet<MultiKeyPair> set = new HashSet<MultiKeyPair>();
set.add(new MultiKeyPair("A","a1"));
set.add(new MultiKeyPair("A","a2"));
set.add(new MultiKeyPair("B","b1"));
set.add(new MultiKeyPair("A","a3"));
MultiKeyList o = new MultiKeyList("A");
set.contains(o);
for (MultiKeyPair pair : o.list) {
System.out.println(pair.value);
}
}
If there are duplicate keys then a key may correspond to more than one value. The obvious solution is to map the key to a list of these values.
For example in Python:
map = dict()
map["driver"] = list()
map["driver"].append("john")
map["driver"].append("mike")
print map["driver"] # It shows john and mike
print map["driver"][0] # It shows john
print map["driver"][1] # It shows mike
I used this:
java.util.List<java.util.Map.Entry<String,Integer>> pairList= new java.util.ArrayList<>();
Just use simple Set with Pair. This Set will exclude pairs with the same key-value. Also you can iterate it.
val set = hashSetOf<Pair<String, String>>()
set.add(Pair("1", "a"))
set.add(Pair("1", "b"))
set.add(Pair("1", "b")) // Duplicate
set.add(Pair("2", "a"))
set.add(Pair("2", "b"))
set.forEach { pair -> println(pair) }
result: (1, a),(2, b),(1, b),(2, a)

Categories

Resources