Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

More advanced version of Collections.frequency()

So, let's imagine I have the following List:

List<Foo> myList = getListFromSomePlace();
int frequency = Collections.frequency(myList, someFoo);

This will count all someFoo matching elements.

However, if I have a more "complex" version:

List<Foo> myList = getListFromSomePlace();
int frequency = /* get number of Elements in the List whose getInternalFoo() match a certain value */

One way to do so would be to override the equalsmethod in the Foo class, but I would really like to avoid putting custom behavior in the Foo class, specially because I might want to get frequency based on different properties from the Foo class, and I can only have one version of the overriden equals method.

Functions like Collections.sort would allow me to pass a custom Comparator that will do exactly what I need, but Collections.frequency doesn't provide this.

With Java8, I would use a stream and some Lambda expression to solve this issue, but I would like to see if there is a simple solution that will work with Java 7. I am looking for something that doesn't involve coding the custom frequency method myself, but using some existing API. Is there something?

like image 473
Martin Avatar asked Feb 24 '15 23:02

Martin


2 Answers

I don't think you can avoid writing your own method. Make it private if you don't want to pollute your API.

public static <T> int frequency(Collection<T> c, T o, Comparator<T> comp) {
    int freq = 0;
    for(T e : c) {
        if(o == null ? e == null : comp.compare(o, e) == 0) {
            ++freq;
        }
    }
    return freq;
}
like image 84
Kevin Krumwiede Avatar answered Sep 26 '22 11:09

Kevin Krumwiede


Just 'decorate' your someFoo by overriding equals() as per your requirements:

List<Foo> myList = getListFromSomePlace();
final Foo someFoo = getSomeFooToGetItsFrequency();

int frequency = Collections.frequency(myList, new Foo() {
    @Override
    public boolean equals(Object another) {
        if (another == someFoo) {
            return true;
        }
        if ((another == null) || (someFoo == null)) {
            return false;
        }
        if (another.getClass() != someFoo.getClass()) {
            return false;
        }
        Foo anotherFoo = (Foo) another;

        // Compare someFoo to anotherFoo as you wish here

        return comparisonResult;
    }
});

Now, this works because Collections.frequency() implementation checks if the object argument equals() every element of the list and not the other way round. If the latter were true, the returned frequency would be always 0.

As you mentioned you 'might want to get frequency based on different properties from the Foo class', you could move the first part of the equals() method of the anonymous inner class to a generic abstract class:

public abstract class ComplexFrequency<T> {

    private final T self;

    public ComplexFrequency(T self) {
        this.self = self;
    }

    @Override
    public boolean equals(Object another) {
        if (another == this.self) {
            return true;
        }
        if ((another == null) || (this.self == null)) {
            return false;
        }
        if (another.getClass() != this.self.getClass()) {
            return false;
        }

        // Let subclasses compare both objects
        return this.equals(this.self, (T) another);
    }

    protected abstract boolean equals(T self, T another);
}

Then, create a subclass of ComplexFrequency that compares as you wish:

public class FooComparingPropertyA extends ComplexFrequency<Foo> {

    public FooComparingPropertyA(Foo someFoo) {
        super(someFoo);
    }

    @Override
    protected boolean equals(Foo self, Foo another) {
        // check equality based on propertyA
    }
}

And finally, 'decorate' your someFoo using this subclass and pass the 'decorated' instance to Collections.frequency():

List<Foo> myList = getListFromSomePlace();
Foo someFoo = getSomeFooToGetItsFrequency();

int frequency = Collections.frequency(myList, new FooComparingPropertyA(someFoo));
like image 30
fps Avatar answered Sep 26 '22 11:09

fps