I have a list of labels which I want to filter and leave only labels "low" "lowest" "high"
I tried to implement this:
private List<Label> filterPriorityLabels(List<Label> labels)
{
for (ListIterator<Label> iter = labels.listIterator(); iter.hasNext();)
{
Label a = iter.next();
if (a.getName() != "low" | "lowest" | "high")
{
iter.remove();
}
}
return labels;
}
But I can't get the working example. How I can fix this code?
Don't compare String with != but !equals().
And a finer solution would be to use contains() method of List.
List<String> acceptableNames = Arrays.asList("low","lowest","high");
if (!acceptableNames.contains(a.getName()))
Here's a complete example based on the answer by #davidxxx:
private static final List<String> acceptableNames =
Arrays.asList("low", "lowest", "high");
private List<Label> filterPriorityLabels(List<Label> labels)
{
for (ListIterator<Label> iter = labels.listIterator(); iter.hasNext();)
{
final Label a = iter.next();
if (!acceptableNames.contains(a.getName())
{
iter.remove();
}
}
return labels;
}
If you're using Java 8, there's a nicer way using streams:
private static final List<String> acceptableNames =
Arrays.asList("low", "lowest", "high");
private List<Label> filterPriorityLabels(List<Label> labels)
{
return labels.stream()
.filter( p -> acceptableNames.contains(p.getName()) )
.collect(Collectors.toList());
}
Note, though, that unlike davidxxx's answer, this does not modify the original list and return it. Instead, it leaves the original list unchanged and returns a new list.
if (! (a.getName().equals("low") || a.getName().equals("lowest") || a.getName().equals("high")))
in Java, you compare Strings (and objects in general), with equals(), not ==
the or logical operator is ||, not |
it expects several boolean expressions as operands. Not Strings.
Also, the signature of your method leads to think that the method creates another list, containing the filtered elements of the original list, whereas it actually modifies the list passed as argument. It should return void, or create and return a copy.
Related
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);
}
}
});
I needed to create an arraylist without an element of another arraylist, but I need this new arraylist to keep updating. For example, an element of the old arraylist is removed, also remove in the new one.
But I did not want to remove the element of the two arraylist, only the old one, so as not to have much code
(My method "showPeople" is updated every 1 second)
My code:
ArrayList<Person> personList = new ArrayList<>();
private void method(){
personList.add(new People("Name"))
}
private void showPeople(){
ArrayList<Person> newPersonList =
new ArrayList<>(personList.stream()
.filter(person -> !person.getName().equals("Test"))
.collect(Collectors.toList()))
for (int i = 0; i < newPersonList.size(); i++){
gui.show(newPersonList.get(i).getName());
}
}
The problem is that when I create the new arraylist and remove an item from the old one, the new one does not update
You're making multiple copies of your list; instead, do something like:
List<Person> filterPeople(List<Person> people, #NotNull String name) {
return people.stream()
.filter(person -> !name.equals(person.getName()))
.collect(Collectors.toList());
}
If you're uncomfortable with the lack of guarantees on the the shape of the List, you can be explicit:
.collect(Collectors.toCollection(ArrayList::new));
It's still unclear what you're asking, however. I suggest you provide a minimal, complete, and verifiable example.
If you want the list without the element to keep updating, you can create a view of the list by extending AbstractList.
The API documentation contains instructions as to the methods you would need to override. If you don't want the list to be modifiable through the view, all you need to do is to override the get and size methods:
class ListView extends AbstractList<String> {
private final List<String> delegate; // Initialize in constructor.
public T get(int i) {
int pos = delegate.indexOf("Test");
if (pos < 0 || i < pos) return delegate.get(i);
return delegate.get(i + 1);
}
public int size() {
return delegate.size() - (delegate.contains("Test") ? 1 : 0);
}
}
This will repeatedly search for the "Test" element, because there is no way for the view to know if the delegate list has been updated underneath it.
Here's a handy method:
private static <T> List<T> CopyListWithoutItem(List<T> listToCopy, T itemToNotCopy) {
return listToCopy.stream().filter(item -> !item.equals(itemToNotCopy)).collect(Collectors.toList());
}
You can use that: List<String> elements = list.stream().distinct().collect(Collectors.toList());
That will remove duplicates.
I've got an custom List and want to check if it contains a special Item. TheList is populated with Rowlayout objects.
public RowLayout(String content, int number) {
this.content = content;
this.number = number;
}
Now i wanna check if my List<Roalayout> contains a special item at the content - position. How do I do that?
It doesn't work with just asking .contains'.
What i wanna check:
if (!List<RowLayout>.contains("insert here"){
//Do something
}
If you can edit the class RowLayout just override hashCode and equals with whatever equality you want for them.
If you can't and have java-8 for example, this could be done:
String content = ...
int number = ...
boolean isContained = yourList.stream()
.filter(x -> x.getContent().equals(content))
.filter(x -> x.getNumber() == number)
.findAny()
.isPresent();
You can obviously return the instance you are interested in from that Optional from findAny.
You just need to override equals for List.contains to work accordingly. List.contains says in the documentation:
Returns true if and only if this list contains at least one element e
such that(o==null ? e==null : o.equals(e)).
Your implementation of equals may look like this:
class RowLayout {
private String content;
private int number;
public boolean equals(Object o)
{
if (!(o instanceof RowLayout)) return false;
final RowLayout that = (RowLayout) o;
return this.content.equals(that.content) && this.number == that.number;
}
}
Don't forget to also override hashCode, else your class will not work in hash-based structures like HashSets or HashMaps.
Example usage:
myList.contains(new RowLayout("Hello", 99));
An alternative Java 8 solution if you only care about the content and don't care about the number would be to do this:
boolean isContained = myList.stream()
.map(RowLayout::getContent)
.anyMatch("some content");
A Rec object has a member variable called tag which is a String.
If I have a List of Recs, how could I de-dupe the list based on the tag member variable?
I just need to make sure that the List contains only one Rec with each tag value.
Something like the following, but I'm not sure what's the best algorithm to keep track counts, etc:
private List<Rec> deDupe(List<Rec> recs) {
for(Rec rec : recs) {
// How to check whether rec.tag exists in another Rec in this List
// and delete any duplicates from the List before returning it to
// the calling method?
}
return recs;
}
Store it temporarily in a HashMap<String,Rec>.
Create a HashMap<String,Rec>. Loop through all of your Rec objects. For each one, if the tag already exists as a key in the HashMap, then compare the two and decide which one to keep. If not, then put it in.
When you're done, the HashMap.values() method will give you all of your unique Rec objects.
Try this:
private List<Rec> deDupe(List<Rec> recs) {
Set<String> tags = new HashSet<String>();
List<Rec> result = new ArrayList<Rec>();
for(Rec rec : recs) {
if(!tags.contains(rec.tags) {
result.add(rec);
tags.add(rec.tag);
}
}
return result;
}
This checks each Rec against a Set of tags. If the set contains the tag already, it is a duplicate and we skip it. Otherwise we add the Rec to our result and add the tag to the set.
This becomes easier if Rec is .equals based on its tag value. Then you could write something like:
private List<Rec> deDupe( List<Rec> recs )
{
List<Rec> retList = new ArrayList<Rec>( recs.size() );
for ( Rec rec : recs )
{
if (!retList.contains(rec))
{
retList.add(rec);
}
}
return retList;
}
I would do that with the google collections. You can use the filter function, with a predicate that remember previous tags, and filters out Rec's with tag that has been there before.
Something like this:
private Iterable<Rec> deDupe(List<Rec> recs)
{
Predicate<Rec> filterDuplicatesByTagPredicate = new FilterDuplicatesByTagPredicate();
return Iterables.filter(recs, filterDuplicatesByTagPredicate);
}
private static class FilterDuplicatesByTagPredicate implements Predicate<Rec>
{
private Set<String> existingTags = Sets.newHashSet();
#Override
public boolean apply(Rec input)
{
String tag = input.getTag();
return existingTags.add(tag);
}
}
I slightly changed the method to return Iterable instead of List, but ofcourse you change that if that's important.
If you don't care about shuffling the data around (i.e you have a small list of small objects), you can do this:
private List<T> deDupe(List<T> thisListHasDupes){
Set<T> tempSet = new HashSet<T>();
for(T t:thisListHasDupes){
tempSet.add(t);
}
List<T> deDupedList = new ArrayList<T>();
deDupedList.addAll(tempSet);
return deDupedList;
}
Remember that implmenations of Set are going to want a consistent and valid equals operator. So if you have a custom object make sure that's taken care of.
Assuming I have
final Iterable<String> unsorted = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
What can I do to transform this unsorted list into this:
[PREFZ, PREFA, BAR, FOO, PREFOO, ZOO]
(a list which begin with known values that must appears first (here "PREFA" and "PREFZ") and the rest is alphabetically sorted)
I think there are some usefull classes in guava that can make the job (Ordering, Predicates...), but I have not yet found a solution...
I would keep separate lists.
One for known values and unknown values. And sort them separately, when you need them in a one list you can just concatenate them.
knownUnsorted.addAll(unsorted.size - 1, unknonwUnsorted);
I suggest filling List with your values and using Collections.sort(...).
Something like
Collections.sort(myList, new FunkyComparator());
using this:
class FunkyComparator implements Comparator {
private static Map<String,Integer> orderedExceptions =
new HashMap<String,Integer>(){{
put("PREFZ", Integer.valueOf(1));
put("PREFA", Integer.valueOf(2));
}};
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
Integer i1 = orderedExceptions.get(s1);
Integer i2 = orderedExceptions.get(s2);
if (i1 != null && i2 != null) {
return i1 - i2;
}
if (i1 != null) {
return -1;
}
if (i2 != null) {
return +1;
}
return s1.compareTo(s2);
}
}
Note: This is not the most efficient solution. It is just a simple, straightforward solution that gets the job done.
I would first use Collections.sort(list) to sort the list.
Then, I would remove the known items, and add them to the front.
String special = "PREFA";
if (list.remove(special)
list.add(0, special);
Or, if you have a list of array of these values you need in the front you could do:
String[] knownValues = {};
for (String s: knownValues) {
if (list.remove(s))
list.add(0, s);
}
Since I'm a fan of the guava lib, I wanted to find a solution using it. I don't know if it's efficient, neither if you find it as simple as others solution, but it's here:
final Iterable<String> all = asList("FOO", "BAR", "PREFA", "ZOO", "PREFOO", "PREFZ");
final List<String> mustAppearFirst = asList("PREFZ", "PREFA");
final Iterable<String> sorted =
concat(
Ordering.explicit(mustAppearFirst).sortedCopy(filter(all, in(mustAppearFirst))),
Ordering.<String>natural().sortedCopy(filter(all, not(in(mustAppearFirst)))));
You specifically mentioned guava; along with Sylvain M's answer, here's another way (more as an academic exercise and demonstration of guava's flexibility than anything else)
// List is not efficient here; for large problems, something like SkipList
// is more suitable
private static final List<String> KNOWN_INDEXES = asList("PREFZ", "PREFA");
private static final Function<Object, Integer> POSITION_IN_KNOWN_INDEXES
= new Function<Object, Integer>() {
public Integer apply(Object in) {
int index = KNOWN_INDEXES.indexOf(in);
return index == -1 ? null : index;
}
};
...
List<String> values = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
Collections.sort(values,
Ordering.natural().nullsLast().onResultOf(POSITION_IN_KNOWN_INDEXES).compound(Ordering.natural())
);
So, in other words, sort on natural order of the Integer returned by List.indexOf(), then break ties with natural order of the object itself.
Messy, perhaps, but fun.
I would also use Collections.sort(list) but I think I would use a Comparator and within the comparator you could define your own rules, e.g.
class MyComparator implements Comparator<String> {
public int compare(String o1, String o2) {
// Now you can define the behaviour for your sorting.
// For example your special cases should always come first,
// but if it is not a special case then just use the normal string comparison.
if (o1.equals(SPECIAL_CASE)) {
// Do something special
}
// etc.
return o1.compareTo(o2);
}
}
Then sort by doing:
Collections.sort(list, new MyComparator());