Count number of items with property

穿精又带淫゛_ 提交于 2019-12-04 19:25:57

问题


I have list List<Custom> where Custom is like

class Custom{
    public int id;
    public String name;
}

How to get number of items which have name "Tom" ? Is there easier way than a for loop ?


回答1:


This can now be done easily with Java 8 streams — no extra libraries required.

List<Custom> list = /*...*/;
long numMatches = list.stream()
                      .filter(c -> "Tom".equals(c.name))
                      .count();



回答2:


If you are going to filter by name in several places, and particularly if you are going to chain that filter with others determined at runtime, Google Guava predicates may help you:

public static Predicate<Custom> nameIs(final String name) {
    return new Predicate<Custom>() {
        @Override public boolean apply(Custom t) {
            return t.name.equals(name);
        }
    };
}

Once you've coded that predicate, filtering and counting will take only one line of code.

int size = Collections2.filter(customList, nameIs("Tom")).size();

As you can see, the verbose construction of a predicate (functional style) will not always be more readable, faster or save you lines of code compared with loops (imperative style). Actually, Guava documentation explicitly states that imperative style should be used by default. But predicates are a nice tool to have anyway.




回答3:


Personally, I like using the Apache Commons Collection lib when I can. (But the one on sourceforge since it uses generics) It lets you do some pretty elegant things such as mapping lists or filtering lists (in a scheme-ish way). You would end up writing something like this:

int count = CollectionUtils.countMatches(myList, new Predicate<Custom>(){
    public boolean evaluate(Custom c){ return "Tom".equals(c.name); }
}

The only downside is that since Java doesn't have first-order functions, you have to write little objects like that Predicate instance. It would be cleaner if you could write anonymous functions. i.e. in scala, it would be this:

val myList = List("a", "a", "b", "c", "a")
val c = myList.count{ "a".equals(_) }  // is 3



回答4:


There is no easier solution with the standard collections. You have to iterate over the list and count the occurrences of the name.




回答5:


Either you keep track as you add or remove items from the list. This could take the place of a hashmap Name->Count . Where when you add an item you increment the count for that name, and when you remove it you decrement the count.

Or you iterate over the collection with a loop checking for the name in question.

Depending on the behavior of your application one of these methods will be faster but without more information it is hard to tell which.




回答6:


Easier probably not. Now you could store your objects in a Map <String, List<Custom>> instead where the key is the name. To get the number of items where name == "Tom" you can then simply do:

List<Custom> l = myMap.get("Tom");
int count = 0;
if (l != null) {
    count = l.size();
}



回答7:


You could also sort the list before looping, then use divide-and-conquer to find matches, then count. It really depends on your needs, like how many elements? Are there a lot of insertions after a search? etc




回答8:


The way it is defined now will always require looping over the list.

Creating a secondary index with a map of names to list of ids is one good idea.

One more option would be to make sure the list is ordered by name, in which case all "Tom"s would be stored next to each other. Then you could find the fist "Tom" in O(log(n)) time with a binary search, and just keep counting from there until you reach a non-"Tom" or end of the list. The insert operation would have O(n) complexity as you need to move all elements after the insert location by one position, so consider this carefully :-)




回答9:


What about this? :

package test;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class CustomArrayBuilder extends ArrayList<Custom> {

    Map<String, Integer> namesMap = new HashMap<String, Integer>();

    public CustomArrayBuilder(Collection<? extends Custom> c) {
        super(c);
        this.prepareAddAll(c);
    }

    public int getDifferentNamesAmount() {
        return this.namesMap.size();
    }

    public int getNameAmount(String name) {
        Integer integer = this.namesMap.get(name);
        return (integer != null) ? integer : 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Custom set(int index, Custom element) {
        Custom custom = super.set(index, element);
        prepareSet(custom, element);
        return custom;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean add(Custom e) {
        this.prepareAdd(e);
        return super.add(e);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void add(int index, Custom element) {
        this.prepareAdd(element);
        super.add(index, element);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Custom remove(int index) {
        Custom custom = super.remove(index);
        this.prepareRemove(custom);
        return custom;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear() {
        super.clear();
        this.namesMap.clear();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addAll(Collection<? extends Custom> c) {
        this.prepareAddAll(c);
        return super.addAll(c);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean addAll(int index, Collection<? extends Custom> c) {
        this.prepareAddAll(c);
        return super.addAll(index, c);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean remove(Object o) {
        if (super.remove(o)) {
            this.prepareRemove((Custom) o);
            return true;
        } else {
            return false;
        }
    }

    private void prepareSet(Custom oldCustom, Custom newCustom) {
        if (oldCustom != null && !oldCustom.name.equals(newCustom.name)) {
            this.prepareRemove(oldCustom);
            this.prepareAdd(newCustom);
        }
    }

    private void prepareAdd(Custom custom) {
        if (custom != null) {
            Integer integer = this.namesMap.get(custom.name);
            this.namesMap.put(custom.name, (integer != null) ? integer + 1 : 1);
        }
    }

    private void prepareAddAll(Collection<? extends Custom> c) {
        for (Custom custom : c) {
            this.prepareAdd(custom);
        }
    }

    private void prepareRemove(Custom custom) {
        if (custom != null) {
            Integer integer = this.namesMap.get(custom.name);
            this.namesMap.put(custom.name, (integer != null && integer > 0) ? integer - 1 : 0);
        }
    }
}

Usage:

package test;

import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(String[] args) {

        List<Custom> list = new ArrayList<Custom>() {{
            add(new Custom("A"));
            add(new Custom("B"));
            add(new Custom("C"));
            add(new Custom("A"));
            add(new Custom("A"));
            add(new Custom("B"));
        }};

        CustomArrayBuilder customs = new CustomArrayBuilder(list);
        Custom custom = new Custom("B");
        customs.add(custom);
        customs.add(custom);
        customs.remove(custom);
        customs.remove(custom);
        customs.remove(custom);

        System.out.println("A: " + customs.getNameAmount("A"));
        System.out.println("B: " + customs.getNameAmount("B"));
        System.out.println("C: " + customs.getNameAmount("C"));
        System.out.println("Z: " + customs.getNameAmount("Z"));
        System.out.println("Total different names: " + customs.getDifferentNamesAmount());
    }
}

Output:

A: 3
B: 2
C: 1
Z: 0
Total different names: 3

It could be usefull when you often use your count operations. Note: You shouldn't change name of custom object, it should be final:

package test;

class Custom {
    public int id;
    final public String name;

    public Custom(String name) {
        this.name = name;
    }
}

Or you must do something with list too, when you are changing name of some Custom object from the list.




回答10:


You can use count() from Eclipse Collections.

MutableList<Custom> customList = Lists.mutable.empty();
int count = customList.count(each -> "Tom".equals(each.getName()));

If you can't change customList from List:

List<Custom> customList = new ArrayList<>();
int count = ListAdapter.adapt(customList).count(each -> "Tom".equals(each.getName()));

If you have a method which checks for a name you can also use countWith():

MutableList<Custom> customList = Lists.mutable.empty();
int count = customList.countWith(Custom::isNamed, "Tom");

class Custom
{
    public int id;
    public String name;

    public boolean isNamed(String nameToCheck)
    {
        return nameToCheck.equals(this.name);
    }
}

Note: I am a contributor to Eclipse Collections.



来源:https://stackoverflow.com/questions/9165222/count-number-of-items-with-property

标签

工具导航Map