Get Submap from Map using PredicateMap - java

I wanted to get a submap from predicateMap:
I have tried this:
public class first {
public static void main(String[] args)
{
TreeMap<String, String> myMap = new TreeMap<String, String>();
Predicate onlyStrings = new InstanceofPredicate( String.class );
myMap.put("Key1","1");
myMap.put("Key2","2");
myMap.put("Key3","3");
System.out.println("Before using submap: "+ myMap );
Predicate pred1 = new EqualPredicate( "1" );
Predicate pred2 = new EqualPredicate( "2" );
Predicate rule = new OrPredicate( pred1, pred2 );
Map map = PredicatedMap.decorate( myMap, onlyStrings, rule );
System.out.println("Before using submap: "+ map );
}
I am not able to get the desired submap which is the following:
Initial Map: {key1=1, key2=2, key3=3}
Output (submap): {key2=2, key3=3}
Can someone please help with this

It doesn't seems PredicatedMap do what you want to achive. It looks more like a validator when adding new values to map.
If you want to extract some values from a map base on predicate, Stream API from JDK should be enough.
If doesn't bother you to modify initial list:
myMap.entrySet().removeIf( e -> !(e.getValue().equals("1") || e.getValue().equals("2")));
If you want to keep initial list and create a new one:
Map<String, String> collect = myMap.entrySet().stream().filter(x -> x.getValue().equals("1") || x.getValue().equals("2"))
.collect(Collectors.toMap(e -> e.getKey(),e -> e.getValue()));
If you have a bigger list of value that you want to keep, you can create a set of them:
Set<String> values = Set.of("1","2");
and filter base on this set:
collect = myMap.entrySet().stream().filter(x -> values.contains(x.getValue()))
.collect(Collectors.toMap(e -> e.getKey(),e -> e.getValue()));
Or for the case with modifying initial list:
myMap.entrySet().removeIf( e -> !values.contains(e.getValue()));
Looks a bit clear if you extract values to keep as a set in my opinion.

Related

delete from parent hashmap from a nested hashmap condition

I have the following structure
HashMap<String, HashMap<String, String>> h = new HashMap<>();
HashMap<String, String>> h1 = new HashMap<>();
h1.put("key10", "value10")
h1.put("key11", "value11")
h1.put("date", "2018-10-18T00:00:57.907Z")
h.put("1#100", h1)
HashMap<String, String>> h2 = new HashMap<>();
h2.put("key20", "value20")
h2.put("key21", "value21")
h2.put("date", "2023-02-03T10:00:00.907Z")
h.put("2#000", h2)
Imagine I have many entries like the examples above.
In certain moment (scheduler) i have this requirement:
check all nested hash maps (for each/stream)
see if date condition is true
find parent key and delete from main hash map
In this exemple the final hash map will be
h2.put("key20", "value20")
h2.put("key21", "value21")
h2.put("date", "2023-02-03T10:00:00.907Z")
h.put("2#000", h2)
h2 => {key20 => value20, key21 => value21, date => 2023-02-03T10:00:00.907Z}
i have this code right now
h.forEach((k,v) -> {
v.entrySet()
.stream()
.filter(e -> e.getKey().equals("date"))
.filter(t -> Timestamp.from(Instant.now()).getTime() - Timestamp.valueOf(t.getValue()).getTime() > milisDiff)
//need now to access parent and delete with by k key
Can do in one step (lambda) or i need to have extra structure to collect parent keys and after proceed to delete within for each ?
This may do what you want. Just filter out bad elements and assign to the same map.
HashMap<String, HashMap<String, String>> h = new HashMap<>();
HashMap<String, String> h1 = new HashMap<>();
h1.put("key10", "value10");
h1.put("key11", "value11");
h1.put("date", "2018-10-18T00:00:57.907Z");
h.put("1#100", h1);
HashMap<String, String> h2 = new HashMap<>();
h2.put("key20", "value20");
h2.put("key21", "value21");
h2.put("date", "2023-02-04T10:00:00.907Z");
h.put("2#000", h2);
// any instant after `now` will pass the filter and be put in the map
Predicate<String> check = str -> Instant.parse(str)
.isAfter(Instant.now());
h = h.entrySet().stream()
.filter(e -> check.test(e.getValue().get("date")))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue,
(a,b)->a,
HashMap::new));
h.values().forEach(m -> {
m.entrySet().forEach(System.out::println);
});
prints
date=2023-02-04T10:00:00.907Z
key21=value21
key20=value20
My predicate simply deleted the date if it expired. Yours was a tighter threshold.
Updated
Here is another option in case building a new map takes too long. It uses an iterator to run thru the loop and modify the existing map by removing Maps with old dates.
Iterator<Entry<String,Map<String,String>>> it = h.entrySet().iterator();
while (it.hasNext()) {
Entry<String,Map<String, String>> e = it.next();
if (!check.test(e.getValue().get("date"))) {
it.remove();
}
}

Java group a map by value where value is a List

I have a
Map<String,List<User>>map = new HashMap<>();
map.put("projectA",Arrays.asList(new User(1,"Bob"),new User(2,"John"),new User(3,"Mo")));
map.put("projectB",Arrays.asList(new User(2,"John"),new User(3,"Mo")));
map.put("projectC",Arrays.asList(new User(3,"Mo")));
Can use String instead of User.
String is a project Name but the same users can relate to different projects.
I would like to get sth like Map<User, List<String>> where
the key will represent a distinct user and a value as a list of projects' names to which he/she relates.
Bob = [projectA]
John = [projectA, projectB]
Mo = [projectA, projectB, projectC]
TQ in advance for any piece of advice.
Just loop over the map's entries and the List inside of them:
public static void main(String[] args) {
Map<String, List<User>> map = new HashMap<>();
map.put("projectA", Arrays.asList(new User(1,"Bob"),new User(2,"John"),new User(3,"Mo")));
map.put("projectB",Arrays.asList(new User(2,"John"),new User(3,"Mo")));
map.put("projectC",Arrays.asList(new User(3,"Mo")));
Map<User, List<String>> result = new HashMap<>();
for(Map.Entry<String, List<User>> e:map.entrySet()) {
for(User u:e.getValue()) {
result.putIfAbsent(u, new ArrayList<>());
result.get(u).add(e.getKey());
}
}
System.out.println(result);
}
public static record User(int id, String name) {}
prints
{User[id=1, name=Bob]=[projectA], User[id=2, name=John]=[projectB, projectA], User[id=3, name=Mo]=[projectB, projectA, projectC]}
To reverse this Map, you need to iterate over its entries and for each distinct user create an entry containing a list of projects as a value in the resulting Map.
Java 8 computeIfAbsent()
This logic can be implemented using Java 8 methods Map.computeIfAbsent() and Map.forEach().
Map<String, List<User>> usersByProject = // initilizing the source map
Map<User, List<String>> projectsByUser = new HashMap<>();
usersByProject.forEach((project, users) ->
users.forEach(user -> projectsByUser.computeIfAbsent(user, k -> new ArrayList<>())
.add(project))
);
Stream API
Stream-based implementation would require a bit more effort.
The core logic remains the same. But there's one important peculiarity: we would need to generate from each entry of the source Map a sequence of new elements, containing references to a particular user and a project.
To carry this data we would need an auxiliary type, and a Java 16 record fits into this role very well. And quick and dirty alternative would be to use Map.Entry, but it's better to avoid resorting to this option because methods getKey()/getValue() are faceless, and it requires more effort to reason about the code. You can also define a regular class if you're using an earlier version of JDK.
public record UserProject(User user, String project) {}
That's how a stream-based solution might look like:
Map<String, List<User>> usersByProject = Map.of(
"projectA", List.of(new User(1, "Bob"), new User(2, "John"), new User(3, "Mo")),
"projectB", List.of(new User(2, "John"), new User(3, "Mo")),
"projectC", List.of(new User(3, "Mo"))
);
Map<User, List<String>> projectByUsers = usersByProject.entrySet().stream()
.flatMap(entry -> entry.getValue().stream().
map(user -> new UserProject(user, entry.getKey()))
)
.collect(Collectors.groupingBy(
UserProject::user,
Collectors.mapping(UserProject::project,
Collectors.toList())
));
projectsByUser.forEach((k, v) -> System.out.println(k + " -> " + v));
Output:
User[id=1, name=Bob] -> [projectA]
User[id=2, name=John] -> [projectA, projectB]
User[id=3, name=Mo] -> [projectA, projectC, projectB]

How can I convert this source code to lambda?

It consists of a map in the list object. I try to match lists with the same id by comparing them through loop statements. How can I convert to lambda?
List<Map<String, String>> combineList = new ArrayList<>(); // Temp List
for(Map titleMap : titleList) { // Name List
for(Map codeMap : codeList) { // Age List
if(titleMap.get("ID").equals(codeMap.get("ID"))) { // compare Id
Map<String,String> tempMap = new HashMap<>();
tempMap.put("ID", titleMap.get("ID"));
tempMap.put("NAME", titleMap.get("NAME"));
tempMap.put("AGE", codeMap.get("AGE"));
combineList.add(tempMap);
}
}
}
You are already doing it in efficient manner. So if you want you could change same code to just use stream().forEach or if want to use streams more do it as below:
titleList.stream()
.forEach(titleMap ->
combineList.addAll(
codeList.stream()
.filter(codeMap -> titleMap.get("ID").equals(codeMap.get("ID")))
.map(codeMap -> {
Map<String, Object> tempMap = new HashMap<>();
tempMap.put("ID", titleMap.get("ID"));
tempMap.put("NAME", titleMap.get("NAME"));
tempMap.put("ID", codeMap.get("ID"));
tempMap.put("AGE", codeMap.get("AGE"));
return tempMap;
})
.collect(Collectors.toList())
)
);
Notice that you have to filter from the codeList each time because your condition is that way. Try using a class in place of Map to be more efficient, cleaner and effective.

How to convert Stream<Map<Integer, String>> to map java 8

Here I am posting sample datastructure
I have a list List<Result> resultsList;
class Result {
String name;
Map<String,Integer> resultMap;
}
Now I would like to stream through this list and get the map.
resultList.stream().filter(result->"xxx".equals(result.getName()))
.map(result->result.getResultMap);
It returns Stream<Map<String,Integer>> but I need only Map<String,Integer>.
How to get it using java 8 streams?
Update:
As geneqew mentioned
This is how my datastructure looks
List<Result> resultsList;
Map<String, Integer> map1 = new HashMap<>();
map1.put("m1", 1);
Map<String, Integer> map2 = new HashMap<>();
map2.put("m2", 2);
Map<String, Integer> map3 = new HashMap<>();
map3.put("m3", 3);
results = Arrays.asList(
new Result("r1", map1),
new Result("r2", map2),
new Result("r3", map3)
);
I would like to retrieve single map based on name.
for (Result result: resultsList)
{
if ('xxx'.equals(result.getName())
{
return result.getResultMap();
}
}
Since you want to return the result map of the first Result element to pass your filter, you can obtain it with findFirst():
Optional<Map<String,Integer>> resultMap =
resultList.stream()
.filter(result->"xxx".equals(result.getName()))
.map(Result::getResultMap)
.findFirst();
You can extract the Map from the Optional this way:
Map<String,Integer> resultMap =
resultList.stream()
.filter(result->"xxx".equals(result.getName()))
.map(Result::getResultMap)
.findFirst()
.orElse(null);
if you're only looking for one item:
resultList.stream()
.filter(result -> "xxx".equals(result.getName()))
.map(Result::getResultMap)
.findAny();
if the filter could match more than one item then you'll need to flatten then toMap it:
resultList.stream()
.filter(result-> "xxx".equals(result.getName()))
.flatMap(result -> result.getResultMap().entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
if there can be duplicates then use the merge function to resolve collisions:
resultList.stream()
.filter(result -> "xxx".equals(result.getName()))
.flatMap(result -> result.getResultMap().entrySet().stream())
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (l, r) -> l));
Since you only wanted the map that matches the results' name then:
results.stream()
.filter(r-> r.getName().equals("r2"))
.map(r-> r.getResultMap())
.findFirst()
.orElse(null);
given you have a sample content of:
List<Result> results;
Map<String, Integer> map1 = new HashMap<>();
map1.put("m1", 1);
Map<String, Integer> map2 = new HashMap<>();
map2.put("m2", 2);
Map<String, Integer> map3 = new HashMap<>();
map3.put("m3", 3);
results = Arrays.asList(
new Result("r1", map1),
new Result("r2", map2),
new Result("r3", map3)
);
A bit of explanation, you got a stream because the last operation in your stream is a map; assuming in your list its possible to have more than 1 result with the same name, findFirst will return the first match if found otherwise an empty optional is returned; Finally orElse to get terminate the stream, providing a null value on empty match.
So I want to explain why you receive stream and not a map. The reason of this is because in the beginning you have List with Result objects that you filter by some criteria (in your case "xxx".equals(result.getName())).
Now you can have as result zero, one or more elements that will pass this criteria! Java does not know how many elements will pass at compile time and that is why you get Stream.
Imagine situation that you have two Result objects that have the same name 'xxx' then you will have two maps. The question is what you want to do? If you get only one of the maps you will loose information. If you want to get all of them, please try something like this:
List<Map<String,Integer>> listWithResultMaps = resultList.stream()
.filter(result->"xxx".equals(result.getName()))
.map(result->result.getResultMap())
.collect(Collectors.toList());
Now in this listWithResultMaps you can process all maps that you have as result of your filter.
Good Luck!

Convert java list to map using stream with indexes

I'm trying to learn how to use the Java 8 collections and I was wondering if there was a way to convert my list to a map using a java stream.
List<PrimaryCareDTO> batchList = new ArrayList<>();
PrimaryCareDTO obj = new PrimaryCareDTO();
obj.setProviderId("123");
obj.setLocatorCode("abc");
batchList.add(obj);
obj = new PrimaryCareDTO();
obj.setProviderId("456");
obj.setLocatorCode("def");
batchList.add(obj);
I'm wondering how I would go about creating my list above into a map using a stream. I know how to use the foreach etc with puts, but I was just wondering if there was a more elegant way to build the map using a stream. (I'm aware the syntax below is not correct, I'm new to streams and not sure how to write it)
AtomicInteger index = new AtomicInteger(0);
Map<String, Object> result = batchList.stream()
.map("providerId" + index.getAndIncrement(), PrimaryCareDTO::getProviderId)
.map("locatorCode" + index.get(), PrimaryCareDTO::getLocatorCode);
The goal is to represent the following.
Map<String, Object> map = new HashMap<>();
//Group a
map.put("providerId1", "123");
map.put("locatorCode1", "abc");
//Group b
map.put("providerId2", "456");
map.put("locatorCode2", "def");
...
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry;
...
AtomicInteger index = new AtomicInteger(0);
List<SimpleEntry<String, String>> providerIds =
batchList.stream()
.map(e -> new SimpleEntry<>("providerId" + index.incrementAndGet(), e.getProviderId()))
.collect(Collectors.toList());
index.set(0);
List<SimpleEntry<String, String>> locatorCodes =
batchList.stream()
.map(e -> new SimpleEntry<>("locatorCode" + index.incrementAndGet(), e.getLocatorCode()))
.collect(Collectors.toList());
Map<String, String> map = Stream.of(providerIds,
locatorCodes)
.flatMap(e -> e.stream())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
First it creates two lists, using Entry (from Map) to represent String-String tuples:
list with tuples providerId# as 'key' with the values e.g. "123"
list with tuples locatorCode# as 'key' with the values e.g. "abc"
It then creates a stream containing these two lists as 'elements', which are then concatenated with flatMap() to get a single long stream of Entry,
(The reason the first two can't stay stream and I have to go through a List and back to stream is because the two invocations of index.incrementAndGet() would otherwise only be evaluated when the streams are consumed, which is after index.set(0);.)
It then creates new key-value pairs with the counter and puts them into a map (with Collectors.toMap().
You would have to steam twice as you want to add two of the properties to map
AtomicInteger index = new AtomicInteger(1);
Map<String, String> result1 = batchList.stream()
.collect(Collectors
.toMap(ignored -> "providerId" + index.getAndIncrement(), PrimaryCareDTO::getProviderId)
);
index.set(1);
Map<String, String> result2 = batchList.stream()
.collect(Collectors
.toMap(ignored -> "locatorCode" + index.getAndIncrement(), PrimaryCareDTO::getLocatorCode)
);
Map<String, String> result = new HashMap<>();
result.putAll(result1);
result.putAll(result2);

Categories

Resources