I have one "matching algorithm" method that wrote using loops and if conditions.
If it possible (and if it needed) to rewrite this code in Java 8 style?
private boolean matchIdsAndStatuses(List<Item> items, ItemResponse currentItemResponse, StatusValue statusValue) {
boolean isMatched = false;
if (CollectionUtils.isNonEmpty(items)) {
// Set of currentItemResponse ids
Set<Map.Entry<String, Status>> itemIds = currentItemResponse.getMapIdsAndStatuses().entrySet();
// List of inner items ids
List<String> innerItemIds =
items.stream().map(ItemBase::getInnerId).collect(Collectors.toList());
// Do we rewrite following block of code in Java 8 style?
// Iterate through inner items ids
for (String innerItemId: innerItemIds) {
// Iterate through currentItemResponse ids
for (Map.Entry<String, Status> itemId: itemIds) {
// Check if innerItemIds and statuses were matched
if (Objects.equals(innerItemId, itemId.getKey())
&& itemId.getValue().getStatusValue().equals(status)) {
isMatched = true;
break;
} else {
isMatched = false;
}
}
}
}
return isMatched;
}
Thank you.
If I understand correctly, you want to check that each of items is mapped to the given status in currentItemResponse.getMapIdsAndStatuses(). I think this will do what you want:
private boolean matchIdsAndStatuses(List<Item> items, ItemResponse currentItemResponse, StatusValue statusValue) {
return items.stream()
.map(ItemBase::getInnerId)
.map(currentItemResponse.getMapIdsAndStatuses()::get)
.filter(Objects::nonNull)
.map(Status::getStatusValue)
.filter(statusValue::equals)
.count() == items.size();
}
On second thought, I would recommend using instead the short-circuiting allMatch() operation. This will stop iterating as soon as a non-match is found:
return items.stream()
.map(ItemBase::getInnerId)
.map(currentItemResponse.getMapIdsAndStatuses()::get)
.allMatch(s -> s != null && s.getStatusValue().equals(status));
It is required to iterate through items and check if their ID matches the ItemResponse. In addition the item status has to be checked against the parameter. In order to simplify things the example uses different types, but the end result might look similar to the following:
private boolean matchIdsAndStatuses(List<String> items, Map<String, String> currentItemResponse, String status) {
return items.stream()
.map(innerItemId -> innerItemId)
.anyMatch(innerItemId -> currentItemResponse.keySet().contains(innerItemId)
&& currentItemResponse.get(innerItemId).contains(status));
}
First we extract the innerItemId, then we check if thats a valid key and finally we fetch the status by key and compare it with the parameter.
Related
I need to find an element's key that's in a list of sets of elements. What's a better (faster) way to do it? Here's my code:
// get tags from an ArrayList of resources
boolean tagFound = false;
HashSet<Tag> resourceTags = new HashSet<>();
for (Resource resource : list) {
Set<Tag> tmpTags = resource.getTags();
resourceTags.addAll(tmpTags);
}
// get tag keys from all tags
for (Tag resourceTag : resourceTags) {
if (resourceTag.getKey().equals(tag.getKey())) {
tagFound = true;
break;
}
}
You could simply get rid of the addAll overhead if all you're going to do is iterate again over it to find an occurrence.
for (Resource resource : list) {
for (Tag resourceTag : resource.getTags()) {
if (resourceTag.getKey().equals(tag.getKey())) {
tagFound = true;
break;
}
}
}
If this was to be written in a functional manner, it would look like:
boolean tagFound = list.stream()
.flatMap(r -> r.getTags().stream())
.anyMatch(t -> t.getKey().equals(tag.getKey()));
Note: On performance, in an R x T matrix you would have to pay O(R x T) runtime to search for an element unless they are hashed. In the latter case, if for example Tag was hashed you could have simply performed a contains based on the key to lookup in O(1) instead.
You can put the keys in the set instead of the tags themselves, then use the contains method. The code below assumes the keys are strings.
Set<String> resourceTagKeys = new HashSet<>();
for(Resource resource : list) {
for(Tag t : resource.getTags()) {
resourceTagKeys.add(t.getKey());
}
}
boolean tagFound = resourceTagKeys.contains(tag.getKey());
That said, it doesn't make much sense to build the set if you're just going to test one element. If you are going to test multiple keys, then you can build the set once, and query it in a separate method. Otherwise, this way is simpler:
boolean tagFound = false;
outer:
for(Resource resource : list) {
for(Tag t : resource.getTags()) {
if(t.getKey().equals(tag.getKey())) {
tagFound = true;
break outer;
}
}
}
If this is in a method returning tagFound, then you can avoid the labelled break by just writing return true; in the loop. If it's not in a method, you might consider refactoring it so that it is.
I have a complicated requirement where a list records has comments in it. We have a functionality of reporting where each and every change should be logged and reported. Hence as per our design, we create a whole new record even if a single field has been updated.
Now we wanted to get history of comments(reversed sorted by timestamp) stored in our db. After running query I got the list of comments but it contains duplicate entries because some other field was changed. It also contains null entries.
I wrote the following code to remove duplicate and null entries.
List<Comment> toRet = new ArrayList<>();
dbCommentHistory.forEach(ele -> {
//Directly copy if toRet is empty.
if (!toRet.isEmpty()) {
int lastIndex = toRet.size() - 1;
Comment lastAppended = toRet.get(lastIndex);
// If comment is null don't proceed
if (ele.getComment() == null) {
return;
}
// remove if we have same comment as last time
if (StringUtils.compare(ele.getComment(), lastAppended.getComment()) == 0) {
toRet.remove(lastIndex);
}
}
//add element to new list
toRet.add(ele);
});
This logic works fine and have been tested now, But I want to convert this code to use lambda, streams and other java 8's feature.
You can use the following snippet:
Collection<Comment> result = dbCommentHistory.stream()
.filter(c -> c.getComment() != null)
.collect(Collectors.toMap(Comment::getComment, Function.identity(), (first, second) -> second, LinkedHashMap::new))
.values();
If you need a List instead of a Collection you can use new ArrayList<>(result).
If you have implemented the equals() method in your Comment class like the following
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return Objects.equals(comment, ((Comment) o).comment);
}
you can just use this snippet:
List<Comment> result = dbCommentHistory.stream()
.filter(c -> c.getComment() != null)
.distinct()
.collect(Collectors.toList());
But this would keep the first comment, not the last.
If I'm understanding the logic in the question code you want to remove consecutive repeated comments but keep duplicates if there is some different comment in between in the input list.
In this case a simply using .distinct() (and once equals and hashCode) has been properly defined, won't work as intended as non-consecutive duplicates will be eliminated as well.
The more "streamy" solution here is to use a custom Collector that when folding elements into the accumulator removes the consecutive duplicates only.
static final Collector<Comment, List<Comment>, List<Comment>> COMMENT_COLLECTOR = Collector.of(
ArrayDeque::new, //// supplier.
(list, comment) -> { /// folder
if (list.isEmpty() || !Objects.equals(list.getLast().getComment(), comment.getComment()) {
list.addLast(comment);
}
}),
(list1, list2) -> { /// the combiner. we discard list2 first element if identical to last on list1.
if (list1.isEmpty()) {
return list2;
} else {
if (!list2.isEmpty()) {
if (!Objects.equals(list1.getLast().getComment(),
list2.getFirst().getComment()) {
list1.addAll(list2);
} else {
list1.addAll(list2.subList(1, list2.size());
}
}
return list1;
}
});
Notice that Deque (in java.util.*) is an extended type of List that have convenient operations to access the first and last element of the list. ArrayDeque is the nacked array based implementation (equivalent to ArrayList to List).
By default the collector will always receive the elements in the input stream order so this must work. I know it is not much less code but it is as good as it gets. If you define a Comment comparator static method that can handle null elements or comment with grace you can make it a bit more compact:
static boolean sameComment(final Comment a, final Comment b) {
if (a == b) {
return true;
} else if (a == null || b == null) {
return false;
} else {
Objects.equals(a.getComment(), b.getComment());
}
}
static final Collector<Comment, List<Comment>, List<Comment>> COMMENT_COLLECTOR = Collector.of(
ArrayDeque::new, //// supplier.
(list, comment) -> { /// folder
if (!sameComment(list.peekLast(), comment) {
list.addLast(comment);
}
}),
(list1, list2) -> { /// the combiner. we discard list2 first element if identical to last on list1.
if (list1.isEmpty()) {
return list2;
} else {
if (!sameComment(list1.peekLast(), list2.peekFirst()) {
list1.addAll(list2);
} else {
list1.addAll(list2.subList(1, list2.size());
}
return list1;
}
});
----------
Perhaps you would prefer to declare a proper (named) class that implements the Collector to make it more clear and avoid the definition of lambdas for each Collector action. or at least implement the lambdas passed to Collector.of by static methods to improve readability.
Now the code to do the actual work is rather trivial:
List<Comment> unique = dbCommentHistory.stream()
.collect(COMMENT_COLLECTOR);
That is it. However if it may become a bit more involved if you want to handle null comments (element) instances. The code above already handles the comment's string being null by considering it equals to another null string:
List<Comment> unique = dbCommentHistory.stream()
.filter(Objects::nonNull)
.collect(COMMENT_COLLECTOR);
Your code can be simplified a bit. Notice that this solution does not use stream/lambdas but it seems to be the most succinct option:
List<Comment> toRet = new ArrayList<>(dbCommentHistory.size());
Comment last = null;
for (final Comment ele : dbCommentHistory) {
if (ele != null && (last == null || !Objects.equals(last.getComment(), ele.getComment()))) {
toRet.add(last = ele);
}
}
The outcome is not exactly the same as the question code as in the latter null elements might be added to the toRet but it seems to me that you actually may want to remove the completely instead. Is easy to modify the code (make it a bit longer) to get the same output though.
If you insist in using a .forEach that would not be that difficult, in that case last whould need to be calculated at the beggining of the lambda. In this case you may want to use a ArrayDeque so that you can coveniently use peekLast:
Deque<Comment> toRet = new ArrayDeque<>(dbCommentHistory.size());
dbCommentHistory.forEach( ele -> {
if (ele != null) {
final Comment last = toRet.peekLast();
if (last == null || !Objects.equals(last.getComment(), ele.getComment())) {
toRet.addLast(ele);
}
}
});
So I extracted a List of Strings e.g {"ADD","DEL","CHG","DEL","NA","ADD","BLAH","YAK",.... } from JSON Request and passed them onto a hashset to avoid duplicates.
How do I get my function to return false if the HashSet has anything else other than "ADD" or "NA" or both ?
Any Help Appreciated.
Update:
In my code, I just added a NOT condition around all those values that I can possibly think of that I do not require.I need a more effective solution.
Also: My true() condition is that ADD must present at all times while NA is optional.No other values must be present other than the mandatory "ADD" and the optional "NA" .
eg:
{ADD} returns true
{ADD,NA} return true
{ADD,DEL} returns false
{DEL,YAK} return false, etc.
The below snippet doesn't have this check and i am looking for the best solution with least verbosity.
Here's the snippet. The List of Strings are passed as an argument to isAddOrNa().
private boolean isAddOrNa(List<AllActions> allActions) {
Set<String> actions = new HashSet<>();
for (com.demo.Action action : allActions) {
actions.add(action.getName());
}
return ((actions.size() <= 2) && (actions.contains("ADD") || actions.contains("NA"))) &&
!(actions.contains("DEL") || actions.contains("CHG") || actions.contains("BLAH") || actions.contains("YAK"));
}
private boolean isAddOrNa(List<AllActions> allActions) {
Set<String> actions = new HashSet<>();
for (com.demo.Action action : allActions) {
actions.add(action.getName());
}
return actions.contains("ADD") && ((actions.contains("NA") && actions.size() ==2) || (!actions.contains("NA") && actions.size() ==1));
}
Minimized the conditions. Does this help?
As I understand the question, you want a validation method for your Set. If the list from JSON was just { "ADD", "NA", "ADD" }, for example, it would be fine and your method should return true. If there are other values in the list, like in the example in the question, it should return false.
It’s not complicated when you know how:
private static boolean setHasOnlySpecifiedValues(
Set<String> specifiedValues, Set<String> setToCheck) {
// we check setToCheck by removing specifiedValues and seeing if there are any
// values left: however, the caller may be surprised if we remove elements from
// the set passed to us, so work on a copy instead
Set<String> workingCopy = new HashSet<>(setToCheck);
workingCopy.removeAll(specifiedValues);
if (workingCopy.isEmpty()) { // there were only specified values
return true;
} else {
System.out.println("The set contained other values: " + workingCopy);
return false;
}
}
The removeAll method in the Java collections framework is one of the so-called set operations (not because it works on sets, it works on lists and maps too). You may think of it as an implementation of the set difference operation.
Lets try the method out:
List<String> fromJson
= Arrays.asList("ADD", "DEL", "CHG", "DEL", "NA", "ADD", "BLAH", "YAK");
Set<String> setToCheck = new HashSet<>(fromJson);
Set<String> specifiedValues = new HashSet<>(Arrays.asList("ADD", "NA"));
// or in Java 9: Set<String> specifiedValues = Set.of("ADD", "NA");
boolean ok = setHasOnlySpecifiedValues(specifiedValues, setToCheck);
System.out.println("The result of the check is: " + ok);
This prints:
The set contained other values: [YAK, CHG, DEL, BLAH]
The result of the check is: false
In the end you probably don’t want the System.out.println() statement inside the checking method. I put it there for now so you can see the exact result of removeAll().
import com.google.common.collect.Sets;
String add= "ADD";
String NA ="NA"
final Set<String> Strings = Sets.newHashSet("ADD","DEL","CHG","DEL","NA","ADD","BLAH","YAK");
if (!((Strings .contains(add))||(Strings .contains(NA )))){
return false;
}
according to ur reuirement,
if(String.contains(add)){
if(String.contains(NA)){
return true;
}
}
return false;
if(hashSet.contains("ADD")||(hashset.contains("NA"))
return false;
Use it thanks
The solution of your problem is to check for anything other that "NA" and "ADD" in the input set. That can be achieved by adding the input set with your validation set, while doing so you will get false in case no new element is found in the input set and true otherwise :
import java.util.HashSet;
import java.util.Set;
public class HashSetTheory {
public static void main(String args[]){
String[] sourceData = {"ADD","DEL","CHG","DEL","NA","ADD","BLAH","YAK" }; // Output is false
//String[] sourceData = {"ADD","ADD","ADD","NA","NA","ADD","NA","ADD" }; // Output is true
//String[] sourceData = {"ADD","ADD","ADD" }; // Output is true
//String[] sourceData = { }; // Output is false
//String[] sourceData = { "NA" , "Something"}; // Output is false
Set<String> myRepo = new HashSet<>();
// Populating the set with input data
for(String data : sourceData){
myRepo.add(data);
}
Set<String> check = new HashSet<>();
check.add("NA");
check.add("ADD");
System.out.println("==>" + validateData(myRepo,check) );
}
public static boolean validateData(Set<String> myRepo,Set<String> check){
boolean retVal = true;
if(myRepo.size()==0){
return false;
}
for (String s : myRepo) {
// Adding an element in set returns false if it already exists
if(check.add(s)){
//This check is pass only if the set has some new item in it
retVal = false;
break;
}
}
return retVal;
}
}
It is beyond me how large your List was or what your intentions are but from what you have provided, a naive solution off the top of my head would be to do 3 searches on the HashSet for those selected words with a counter keeping track of the hits. then compare that against size() of the HashSet.
I didn't really know how to formulate the title of this question, so I'll just jump into an example.
Let's say that I want to iterate over a list of elements, and based on certain conditions, add said element to a new list.
Here I create a method that basically wants to check if an item is exclusive to the first list (isn't present in the second one). Now I know that for this particular silly example you could solve this using sets, but I'm just trying to illustrate a case where something like this would pop up
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> newItems = new ArrayList<>();
for (Item i: items) {
for (Item j: otherItems) {
if (i.equals(j))
//Missing code
}
newItems.add(i);
}
return newItems;
}
So here I only want to add the current Item i to newItems if it is not equal to a single item in otherItems. My first impulse was to put break; where it says //Missing Code, but that would only break out of the first loop and not impede the adding of i to newItems.
I am aware of a correct solution where you would use a boolean variable to consistently check the truth of the if statement, and then add Item i to newItems based on it's truth value at the end of the second loop. It would look something like this:
for (Item i: items) {
boolean check = true;
for (Item j: otherItems) {
if (i.equals(j))
check = false;
break; //To avoid unnecessary iterations
}
if (check)
newItems.add(i);
}
This seems incredibly bulky and also quite redundant however. Is there a more efficient and elegant way of doing this?
If I understand your question correctly, you need to create a list, where are collected items from items excluding items that are present in both items and otherItems. If yes, you can do it simply by List#removeAll():
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> res = new ArrayList<>(items); // create a copy of items
res.removeAll(otherItems); // remove items presented in otherItems
return res;
}
If there are other condition(s) to exclude items, use a stream, filter(s) and collector, as follows:
return items.stream()
.filter(i -> !otherItems.contains(i))
.filter( /* another condition */ )
.collect(Collectors.toList());
As pointed out by Wakachopo and AchmadJP, you can do what you describe using contains or binarySearch. Now that you say, these operations are only exemplary and you might have different conditions—well nothing stops you from using the same pattern, but now you may have to write the particular method yourself:
for(Item i: items) {
if(!hasMatchingCondition(i, otherItems) {
newItems.add(i);
}
}
static boolean hasMatchingCondition(Item i, List<Item> list) {
for(Item j: list) {
if(whatever condition regarding i and j) {
return true;
}
}
return false;
}
Clean and short-circuiting.
You can do the same in a single method using labeled statements, i.e
outer: for(Item i: items) {
for(Item j: list) {
if(whatever condition regarding i and j) {
continue outer;
}
}
newItems.add(i);
}
but labeled statements are considered a discouraged feature by some developers and perhaps more important, you may find another use for the hasMatchingCondition method somewhere.
Hmm, if the List is already sorted. The faster solution I think is using binary search as it is faster than sequential search.
for( Item i: items){
if(Collections.binarySearch(otherItems, i) < 0){
newItems.add(i);
}
}
If I have to, I would do this way:
for(Item i: items){
if(!otherItems.contains(i)){
newItems.add(i);
}
}
In this and other cases I think you could give them an upside down focus and assume that all items fulfill your conditions and then remove those which not. For this example:
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
List<Item> newItems = new ArrayList<>(items);
for (Item i: items) {
for (Item j: otherItems) {
if (i.equals(j)){
newItems.remove(i)
break;
}
}
return newItems;
}
As an alternative to the nested loop, in Java 8 you can use the Streams API to do the following:
public List<Item> newItems(List<Item> items, List<Item> otherItems) {
return items.stream()
.filter(i -> !otherItems.contains(i))
.collect(Collectors.toList());
}
Well, You can do something like;
CollectionUtils.removeAll(collection1, collections2);
This method returns a collection to you as well.
https://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/CollectionUtils.html
Map<String, Integer> successors = new HashMap <String, Integer> ();
// I have added some elements into the successors.
Collection<Integer> uniqueValues = successors.values();
Is there a way for me to find out in java if uniqueValues can show me that all the values in it are the same?
I planned on using the if(uniqueValues.contains(1))statement. But I just could not figure it out. Since this statement will say true if 1 is present and other values different from 1 are also present. I just want it to return true if 1 is the only value in the collections.
eg; {1,1,1,1,1,1,1,1,1,1,1,1,1} should return true.
But {1,2,1,3,1,4,2,4,22,1,1,1,4} should return false.
Some code along the lines of "Contains if and only if."
This will be of great help. Thanks in advance.
Do it the CPU-clock-wasting way:
if(new HashSet(successors.values()).size()>1) {...}
Well, you could do something like this (inefficiently),
boolean uniqueValues = new HashSet<Integer>(successors.values()).size() == 1;
Since that will check every value every time, a more efficient approach might be,
boolean uniqueValues = true;
Collection<Integer> values = successors.values();
Iterator<Integer> iter = values.iterator();
if (iter.hasNext()) {
int t = iter.next();
while (iter.hasNext()) {
int i = iter.next();
if (t != i) {
// stop if we find a different value
uniqueValues = false;
break;
}
}
}
Some sort of Set sounds like easiest solution
if(new HashSet<Integer>(successors.values)).size() == 1)
Because Set can contain only unique values, logical consequence of having input collection with same values is the Set of size one. Or you can of course introduce you own util method which will check this condition.