【问题标题】:Common element between 2 collections in java [closed]java中2个集合之间的公共元素[关闭]
【发布时间】:2023-04-07 06:11:01
【问题描述】:

我正在尝试在元素类型为 Object 的 2 个列表之间找到共同元素。

例如

class Student {
 String firstName;
 String lastName;
 float gpa;
... other attributes
}

类似

class Employee{
     String firstName;
     String lastName;
     float salary;
    ... other attributes
    }

我需要找出与学生名字相同的员工的条目。

我做了以下

    Set<String> studentFirstNames = studentList.stream().
map(student-> student.getFirstName()).
collect(Collectors.toSet());

List<Employee> employeeAndStudents = employeeList.stream()
.filter(employee-> studentFirstNames.contains(employee.getFirstName())
.collect(Collectors.toList());

这可行,但是我正在尝试找到一个解决方案,其中公共属性不限于单个字段(例如名字和姓氏)。

列表可以包含许多元素,但无论大小如何,我想知道使用 FP 是否存在更好的解决方案。

【问题讨论】:

  • 这些列表有多大?
  • 我编辑了这个问题。该列表最多可能包含 10K 个元素,并且应用程序对性能很敏感。
  • 如果每个列表中没有重复项,我建议使用集合而不是列表。这将大大提高效率。
  • 您能否提供一个使用 set 的示例代码?我不确定我是否理解它
  • 您只需为过滤器编写另一个谓词。或者您是在问如何根据常用属性自动创建一个?因为这不是 Java 中的事情,至少在不使用库的情况下不会。

标签: java functional-programming


【解决方案1】:

如果您不想添加任何新类,并且可以灵活地修改它们(实现接口),那么此解决方案将起作用。首先定义一个包含要比较的字段(getter)的接口:

interface ComparisionType {
    String getFirstName();
    String getLastName();
    int getAge();
}

接下来,在您的 StudentEmployee 类中实现它,如下所示:

@Builder
@Getter
@Setter
@ToString
class Student implements ComparisionType {
    String firstName;
    String lastName;
    int age;
    float gpa;
}

@Builder
@Getter
@Setter
@ToString
class Employee implements ComparisionType {
    String firstName;
    String lastName;
    int age;
    float salary;
}

以下是示例可执行代码(java-doc 说明每个方法的作用)。

public class StackOverflowTest {

    public static void main(String[] args) {
        List<ComparisionType> students = List.of(
                Student.builder().firstName("firstName").lastName("lastName00").age(35).build(),
                Student.builder().firstName("firstName1").lastName("lastName11").age(22).build(),
                Student.builder().firstName("firstName2").lastName("lastName44").age(30).build());

        List<ComparisionType> employees = List.of(
                Employee.builder().firstName("firstName2").lastName("lastName11").age(12).build(),
                Employee.builder().firstName("firstName3").lastName("lastName22").age(23).build(),
                Employee.builder().firstName("firstName4").lastName("lastName33").age(35).build());

        List<Student> commonStudents = getCommon(students, employees, Student.class, ComparisionType::getAge);
        System.out.println(commonStudents);

        List<Employee> commonEmployees = getCommon(students, employees, Employee.class, ComparisionType::getLastName);
        System.out.println(commonEmployees);
    }

    /**
     * Supply the 2 lists in first and second argument
     * Third argument is which type of common objects you like as the output
     * Fourth argument is which field do you want to compare to determine similar objects
     */
    @SuppressWarnings("unchecked")
    private static <T> List<T> getCommon(List<ComparisionType> students, List<ComparisionType> employees, Class<T> clazz,
            Function<ComparisionType, Object> function) {
        Map.Entry<List<ComparisionType>, List<ComparisionType>> comparingAndComparedTo = getComparingAndComparedTo(students, employees, clazz);

        return (List<T>) comparingAndComparedTo.getKey().stream()
                .filter(isMatch(comparingAndComparedTo.getValue(), function))
                .collect(Collectors.toList());
    }

    /**
     * Identifies the list to loop through
     * to compare against the other list.
     */
    private static <T> Map.Entry<List<ComparisionType>, List<ComparisionType>> getComparingAndComparedTo(List<ComparisionType> list1,
            List<ComparisionType> list2, Class<T> clazz) {
        return Optional.of(list1)
                .filter(list -> list.get(0).getClass().equals(clazz)) // <-- Add better class check here
                .map(list -> Map.entry(list1, list2))
                .orElse(Map.entry(list2, list1));
    }

    /**
     * Takes a list and checks if a field of an element
     * matches the supplied field (from the function).
     */
    private static Predicate<ComparisionType> isMatch(List<ComparisionType> list, Function<ComparisionType, Object> function) {
        return type -> {
            return list.stream()
                    .map(function)
                    .anyMatch(field -> field.equals(function.apply(type)));
        };
    }
}

getCommon 方法将为您提供所需类型的对象列表,并包含您要比较的字段,因此您可以灵活选择。

注意:我尚未测试此代码的性能。如果您设法计算出性能指标,请随时提及,我很想知道。

PS:我使用的是 Java11。

【讨论】:

    【解决方案2】:

    使用两个哈希函数怎么样?

    Function<Student,Integer> studentHashFunction = s -> Objects.hash(s.getFirstName(), s.getLastName());
    Function<Employee,Integer> employeeHashFunction = e -> Objects.hash(e.getFirstName(), e.getLastName());
    
    Set<Integer> studentHash = studentList
       .stream()
       .map(studentHashFunction)
       .collect(Collectors.toSet());
    
    List<Employee> employeeAndStudents = employeeList
       .stream()
       .filter(employee-> studentHash.contains(employeeHashFunction.apply(employee)))
       .collect(Collectors.toList());
    

    【讨论】:

      【解决方案3】:

      您可能想要创建一个自定义的“均衡器”类,该类从要检查的任一类型中获取一个对象并将其委托给另一个类的字段。

      class Equalizer {
         private final String firstName;
         private final String lastName;
         Equalizer(Student student){
           this.firstName  = student.getFirstName;
           this.lastName  = student.getLastName;
         }
      
         Equalizer(Employee employee){
           this.firstName  = employee.getFirstName;
           this.lastName  = employee.getLastName;
         }
      
        // have equals() and hashcode() implemented by your IDE based on the fields.
      }
      

      然后将第一个集合转换为Equalizer 对象。

       Set<Equalizer> equalizers = studentList.stream()
            .map(student-> new Equalizer(student))
            .collect(Collectors.toSet());
      

      然后你可以对它们进行比较:

      List<Employee> employeeAndStudents = employeeList.stream()
          .filter(employee-> equalizers.contains(new Equalizer(employee))
          .collect(Collectors.toList());
      

      【讨论】:

      • 好的..这应该可以。但是我想看看是否有办法避免创建新类并动态确定标准(例如名字和姓氏、姓名和邮政编码等)。
      • @Raman 锤子不是因为你买了螺丝刀就没有用... ;o)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-13
      • 2012-12-25
      • 2013-09-22
      • 2019-05-03
      • 2016-10-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多