Map grouping the key based on same value - java

I have a Map <Long, List<SerialDate>>:
The SerialDate has two fields: Number,String
1 -> (Number1|String1, Number2|String2)
2 -> (Number1|String1, Number2|String2)
3 -> (Number3|String3)
The code is like:
Map<Long, List<SerialMaskData>> upcDataMap = new HashMap<>();//
for(SerialMappingData data : serialDataList3p){
Long upc = data.getUpcNumber();
String mask = data.getSerialMask();
String algorithm = data.getSerialAlgorithm();//
SerialMaskData serialMaskData = new SerialMaskData(mask,algorithm);//
if(!upcDataMap.containsKey(upc)){
List<SerialMaskData> list = new ArrayList<>();//
list.add(serialMaskData);//
upcDataMap.put(upc, list);
}else{
upcDataMap.get(upc).add(serialMaskData);
}
}
What I want is to do a classification:
[1,2], (Number1|String1, Number2|String2), groupId1 // because they have the same value format.
[3], (Number3|String3), groupId2
I have tried to build a reverse Map<List<Number|String>, Long>, but it was a bad idea to use List as a map key. I think it can be done with stream.groupingby but not sure how to do it.

You have a Map<Long,List<SerialDate>>, and for the described grouping, you would like to have a Map<List<SerialDate>,List<Long>>, right?
As you already found out yourself, having an instance of List as the key for a Map is not the best idea …
But if SerialDate provides a proper implementation of Object::toString, you can get something equivalent that should be sufficient for your purpose.
Try this:
final Map<Long,List<SerialDate>> input = …
final Map<String,List<Long>> output = new HashMap<>();
for( final var entry : input.entrySet() )
{
final var key = entry.getValue()
.stream()
.map( SerialDate::toString )
.sorted()
.collect( joining() );
final var values = output.computeIfAbsent( key, k -> new ArrayList<Long>() );
values.add( entry.getKey();
}
Basically, the code converts the list into a String, and uses that String as the key for the output map. The sorting is required because two lists are (assumed to be) semantically equal if they contain the same elements, independent from their order, while for Strings the order of their 'elements' is crucial for equality.
Simply calling toString() on the List instance does not work as it will not call toString() on the element.

Related

Least verbose way of calling Method on each object in two lists with a matching element

I have two lists that are populated from two different sources
List<MyObject> updatedObjs;
List<MyObject> currentObjs;
All of the elements in updatedObjs have the same string identifier as an element within the currentObjs list. This string Identifier can be simply accessed by getID() which returns a String.
What I would like to do is to find each match in the currentObjs list for each element in updatedObjs and then call a method passing the matching objects from both lists in.
I have figured out code for finding the match in both lists:
updatedObjs.stream().filter(updatedObj -> currentObjs.stream().anyMatch(currentObj -> updatedObj.getID().equals(currentObj.getID()))).collect(Collectors.toList());
But this will just find the matches...is there any way of calling anyMatch and then calling a method on both matching objects?
So I want to called a method like myMethod(currentObj, updatedObj);
I know I can do this with nested for loops but I am looking for a more elegant solution if one exists. Thanks
I think you need it:
updatedObjs.forEach(s -> currentObjs.stream().filter(s1 -> s.getId().equals(s2.getId)).forEach(s1 -> yourMethod(s, s1)));
My approach would be to create a Map whose key is the id and whose values are all the objects from each list that have that key. Then iterate over the entries in the map and call your myMethod() on each list that has more than 1 entry.
List<MyObj> updatedObjs = new ArrayList<>(); // assuming this has values
List<MyObj> currentObjs = new ArrayList<>(); // assuming this has values
List<MyObj> allObjs = new ArrayList( updatedObjs ); // create new list with references to the objects in updatedObjs
allObjs.addAll( currentObjs ); // add everything from currentObjs to our new list
// map of id -> list of all elements with that id
Map<String,List<MyObj>> idMap = allObjs
.stream()
.collect( Collectors.groupingBy( obj -> obj.getID() ) );
Now there's two different approaches below to calling the method
// you could stream the entry set, filter down to only entries with more than one value in the list and run the method on each
idMap.entrySet()
.stream()
.filter( ( mapEntry ) -> mapEntry.getValue().size() > 1 )
.forEach( mapEntry -> mapEntry.getValue().forEach( obj -> obj.getID() ) );
// I prefer a for-loop for this instead of streaming the entry set because it is a lot easier to read
for ( Map.Entry<String,List<MyObj>> entry : idMap.entrySet() ) { // iterate each entry
String key = entry.getKey();
List<MyObj> value = entry.getValue();
if ( value.size() > 1 ) {
// has more than one entry with the given key, so run myMethod() on each
value.forEach( obj -> obj.getID() );
}
}
By iterating over the first collection then the second, you're basically having an expensive algorithm O(n²)
You might want to consider something more straight forward like having a Map<String, List<MyObj>>
This hereunder is O(n)
Map<String, List<MyObj>> map = updatedObjs.stream()
.collect(Collectors.groupingBy(MyObj::getId));
currentObjs.forEach(obj -> map.get(obj.getId).add(obj)); // No Stream
map.forEach((k, v) -> myMethod(v.get(1), v.get(0));
That out of the way, the fact that you have two lists maintaining the state of the same entity is probably a bad idea. You should probably rethink your design from scratch.
Try the following approach:
void perform(List<MyObject> currentObjects, List<MyObject> updatedObjects) {
Map<String, MyObject> currentObjById = currentObjects.stream()
.collect(Collectors.toMap(MyObject::getID, Function.identity()));
Map<String, MyObject> updatedObjById = updatedObjects.stream()
.collect(Collectors.toMap(MyObject::getID, Function.identity()));
for (Map.Entry<String, MyObject> entry : currentObjById.entrySet()) {
MyObject updatedObj = updatedObjById.get(entry.getKey());
if (updatedObj != null) {
myMethod(entry.getValue(), updatedObj);
}
}
}
It is more efficient than the solutions which use anyStream. This will work provided that you have no two elements with the same ID in each of the lists.

Java HashMap key fits to a pattern?

I have a Map dataset, and I want to iterate through the keys and search for matches.
So I want to find the maps element, where the key fits to this pattern:
String searchedKey = "A?C"; // ? means it can be any character
Map<String, MyObject> myMap = new HashMap<>();
myMap.put("ABC", MyObject(1));
myMap.put("CDF", MyObject(2));
myMap.put("ADS", MyObject(3));
for (Map.Entry<String,MyObject> entry : myMap.entrySet()) {
// in this case, I want to find the first element, because it's key fits the searchedKey, where ? can be anything
}
How can I do this?
Thanks!
You could do something like this to return a list of found MyObjects. Note I changed ? to . for any character.
String searchedKey = "A.C"; // ? means it can be any character
Map<String, MyObject> myMap = new HashMap<>();
myMap.put("ABC", new MyObject(1));
myMap.put("CDF", new MyObject(2));
myMap.put("ARS", new MyObject(3));
myMap.put("VS", new MyObject(4));
myMap.put("AQC", new MyObject(3));
myMap.put("DS", new MyObject(3));
myMap.put("ASC", new MyObject(10));
List<Map.Entry<String,MyObject>> list = myMap.entrySet().stream()
.filter(e -> e.getKey().matches(searchedKey))
.collect(Collectors.toList());
list.forEach(System.out::println);
Prints
ASC=10
ABC=1
AQC=3
The MyObject class
class MyObject {
int val;
public MyObject(int v) {
this.val = v;
}
public String toString() {
return val + "";
}
}
You could use Regex-Patterns that allow to search Strings for matchings of a logical sequence using String#matches(String).
Here is a page that might help you create and test a regex for your needs. You might also have to construct your pattern flexible during runtime, depending on how your search works.
Tho keep in mind that a HashMap does not keep the order in which the keys were inserted. keySet() does not return them in a fixed order. If you need them ordered, you could use a LinkedHashMap

Pluck only relevent keys from a map

I have an int arry input, for example : [1,3,4].
I also have a fixed/constant map of :
1 -> A
2 -> B
3 -> C
4 -> D
5 -> E
I want my output to be a corresponding string of all the relevant keys.
For our example of input [1,3,4], the output should be : "A,C,D".
What's the most efficient way of achieving that?
My idea was to iterate over the whole map, each time.
The problem with that, is that I have a remote call in android that fetches a long list of data items, and doing that for each item in the list seems a bit.. inefficient. Maybe there's something more efficient and/or more elegant. Perhaps using Patterns
Assuming the array is defined as below along with the HashMap:
int arr[] = { 1, 3, 4 };
HashMap<Integer, String> hmap = new HashMap<>();
// data in the map
hmap.put(1, "A"); hmap.put(2, "B"); hmap.put(3, "C"); hmap.put(4, "D"); hmap.put(5, "E");
Instead of iterating over the entire map, you can iterate over the array
String[] array = Arrays.stream(arr).mapToObj(i -> hmap.get(i))
.filter(Objects::nonNull)
.toArray(String[]::new);
This gives the output :
A C D
As per your comment, to join it as one String you can use :
String str = Arrays.stream(arr).mapToObj(i -> hmap.get(i))
.filter(Objects::nonNull)
.collect(Collectors.joining("/"));
You can iterate over the list of input instead of a map. That's the benefit of using a Map by the way, it has a lookup time of O(1) with proper hashing implemented. It would somewhat like :
Map<Integer, String> data = new HashMap<>();
List<Integer> query = new ArrayList<>(); // your query such as "1,3,4"
List<String> information = new ArrayList<>();
for (Integer integer : query) {
String s = data.get(integer); // looking up here
information.add(s);
}
which with the help of java-stream can be changed to
List<String> information = query.stream()
.map(data::get) // looking up here
.collect(Collectors.toList()); // returns "A,C,D"
Note: I have used String and Integer for just a representation, you can use your actual data types there instead.

Get a List of Long values from a Map for a specific Key in Java

I have a Map: Map<String, Object> data
The content is:
{"id_list":["2147041","2155271","2155281"],
"remoteHost":"127.0.0.1",
"userId":"user",
"agencyId":1}
I want to store in a Long List, all the values with Key: id_list
It would be:
list:[2147041,2155271,2155281]
Is there a way to do that?
I've got:
List<Long> list = new ArrayList<Long>(data.get("id_list") );
It looks like the values in the collection of id_list are String objects, so you should be able to do it with a loop that performs conversions:
Iterable<String> idStrings = (Iterable<String>)data.get("id_list");
List<Long> list = new ArrayList<Long>();
for (String id : idStrings) {
list.add(Long.valueOf(id));
}
new ArrayList<Long>(Arrays.asList(data.get("id_list")));
Assuming that is an array in your hashMap. Otherwise you'd have to cast it as an (Iterable<String>)data.get("id_list"); and add each String one by one.
You can use Arrays.asList to instantiate from that array
List<Long> list = Arrays.asList(data.get("id_list"));
or if you want a new ArrayList instead of just a genericl List
List<Long> list = new ArrayList<Long>(Arrays.asList(data.get("id_list")));
If running on Java 8 or later, you may use a BiFunction for this:
private BiFunction<Map<String, Object>, String, List<Long>> convertToLong() {
return (thatMap, targetKey) -> thatMap.get(targetKey).stream()
.map(id -> Long.valueOf(id))
.collect(Collectors.toList());
}
// The call
String targetKey = "id_list";
// prepare the values of this accordingly.
Map<String, Object> thatMap = new HashMap<>();
List<Long> converted = convertToLong().apply(thatMap, targetKey)
// Assumption for the output: your map contains the sample data provided here.
System.out.println("converted: " + converted); //=> [2147041, 2155271, 2155281]

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.

Categories

Resources