根据特定规则过滤集合的最佳方法

时间:2013-11-13 07:23:19

标签: java collections predicate

我正在尝试根据某些规则找到从列表中过滤掉某些项目的最佳方法。例如,我们有

public class Person{
    String name;
    String sex;
    String dob;
    String contactNo;
    Person(String name, String sex, String dob, String contactNo) {
        this.name = name;
        this.sex = sex;
        this.dob = dob;
        this.contactNo = contactNo;
    }
}

List<Person> persons = Arrays.asList(new Person("Bob", "male", "19800101", "12345"),                
        new Person("John", "male", "19810101", "12345"),
        new Person("Tom", "male", "19820101", "12345"),
        new Person("Helen", "female", "19800101", "12345"),
        new Person("Jack", "male", "19830101", "12345"),
        new Person("Suan", "female", "19850101", "12345"));

我想删除那对具有相同dob和contactNo的男性和女性(在上面的示例中删除Bob和Helen)。我使用嵌套循环实现了如下所示,但它看起来很丑陋。请问有更好的方法吗?我可以实现谓词来做到这一点吗?

public void filterPersons() {       
    List<Person> filtered = new ArrayList<Person>();

    for (Person p: persons) {
        boolean pairFound = false;
        for (Person t: persons) {
            if ((p.sex.equals("male") && t.sex.equals("female")) || (p.sex.equals("female") && t.sex.equals("male"))) {
                if (p.dob.equals(t.dob) && p.contactNo.equals(t.contactNo)) {                       
                    pairFound = true;
                    break;
                }
            }
        }
        if (!pairFound) {filtered.add(p);}          
    }

    System.out.println("filtered size is: " + filtered.size());
    for (Person p: filtered) {
        System.out.println(p.name);
    }
}

非常感谢。

我已经改写了上面的方法,如下所示,看起来更好imho:

public void testFilter() {      
    Predicate<Person> isPairFound = new Predicate<Person>() {
        @Override public boolean apply(Person p) {              
            boolean pairFound = false;
            for (Person t: persons) {
                if ((p.sex.equals("male") && t.sex.equals("female")) || 
                        (p.sex.equals("female") && t.sex.equals("male"))) {
                    if (p.dob.equals(t.dob) && p.contactNo.equals(t.contactNo)) {                       
                        pairFound = true;
                        break;
                    }
                }
            }
            return pairFound;
        }
    };

    Iterable<Person> filtered = Iterables.filter(persons, isPairFound);     
    for (Person p: filtered) {
        System.out.println(p.name);
    }
}

3 个答案:

答案 0 :(得分:1)

我认为嵌套的for循环并不特别难看。您正在根据有效的任意标准查找列表中项目之间的匹配,因此您需要将每个条目与其他所有条目进行比较。

您可以考虑的一个改进是将迭代代码与比较逻辑分开。这是你使用谓词的地方。要做到这一点,你需要一个带有两个对象而不是一个对象的谓词。

public interface PredicateComparator<T> {
    boolean compare(T o1, T o2);
}

您的代码现在看起来像这样

public void filterPersons() {

    PredicateComparator<Person> predicate = new PredicateComparator<Person>() {
        public boolean compare(Person o1, Person o2) {
            // comparison logic in here
        }

    };

    List<Person> filtered = new ArrayList<Person>();
    for (Person p : persons) {
        for (Person t : persons) {
            if (predicate.compare(p, t)) {
                filtered.add(p);
            }
        }
    }

    System.out.println("filtered size is: " + filtered.size());
    for (Person p: filtered) {
        System.out.println(p.name);
    }
} 

答案 1 :(得分:0)

您可以使用hashmap删除重复项。地图中的每个条目都表示

(DOB+ContactNo -> persons index in the original list)

功能

public void filterPersons() {       
    List<Person> filtered = new ArrayList<Person>(persons); // COPY the whole list
    HashMap<String,Integer> map = new HashMap<String,Integer>();
    int count=-1;

    for (Person p: persons) {
        count++;
        String g = p.sex; 
        String g_opp = g.equals("male")? "female":"male";

        if(!map.contains(p.dob+p.contactNo+g_opp))
        {
            // if not exists, add to map
            map.put(p.dob+p.contactNo,count+g);
        }
        else
        {
            // if duplicate found in map, remove both people from list
            filtered.remove(count);
            filtered.remove(map.get(p.dob+p.contactNo+g));

            // now filtered has 2 less elements, update count
            count -= 2;
        }
   }
}   

答案 2 :(得分:0)

是否只有一种方法可以确定两个人之间的身份? 如果是这样,最好通过覆盖'equals'和'hashcode'来封装它。

执行此操作后,您可以采用以下方法之一:

  1. 如果您正在创建 Person实例的集合,并希望确保在多次添加同一个人时只保留集合中的单个实例 - 请使用Set接口作为底层集合(可能还有HashSet实现)。正确地使用equals和hashcode,该集合将不允许重复。

  2. 如果您一个集合(意味着您无法控制它的创建,因此无法使用上述方法来验证它是否构造没有重复)并且想要过滤你可以简单地将重复的实例提供给HashSet的构造函数,如下所示:

    Collection<Integer> containsRepeatingNumbers = Arrays.asList(1,2,3,4,3,3,3,3);
    Set<Integer> alldistincts = new HashSet<>(containsRepeatingNumbers);
    System.out.println(alldistincts);   //[1, 2, 3, 4]
    
  3. 顺便说一句,如果您预计将来有多个身份标准,您可以使用提出的策略here