【问题标题】:Divide a Set into Subsets of objects having equal field?将一个集合划分为具有相等字段的对象的子集?
【发布时间】:2023-03-31 09:57:01
【问题描述】:

我目前正在尝试在 java 中实现一个数据结构,并希望将一组输入对象划分为具有相同字段的不同对象子集。

An example use case:

我们希望将人员列表划分为特定日期出生人员的子列表。

Input: person1 1990 年出生,person2 2000 年出生,person3 1990 年出生。

Output:

1 -> 人 1,人 3

2 -> 人2

public Map<Integer, List<Foo>> getIntToFooMap(List<Foo> foos) { 
    Map<Integer, List<Foo>> map = new TreeMap<>(); // need keys to be automatically ordered.
    List<Foo> foosWithSameSetId = new ArrayList<>();
    if (!foos.isEmpty) { 
       for (Foo foo: foos) {
           for (Foo foo2: foos) {
               if (foo.getSetId().equals(foo2.getSetId())) {
                   foosWithSameSetId.add(foo2);
               }
           }
        map.put(foo.getSetId(), foosWithSameSetId);
        foosWithSameSetId.clear();
       }
    }
  return map;
 }

上面的代码不是最优的,时间复杂度是二次的,也不是线程安全的。 有人能告诉我一个更好的方法将 List 或 Set 划分为具有相等字段的对象子集,在本例中为 setId

【问题讨论】:

    标签: java algorithm oop data-structures treemap


    【解决方案1】:

    首先,不需要嵌套循环。您只需获取或创建当前foosetId 的集合,并将foo 添加到其中:

    for (Foo foo : foos) {
        map.computeIfAbsent(foo.getSetId(), i -> new ArrayList<>()).add(foo);
    }
    

    相当于:

    for (Foo foo : foos) {
        List<Foo> list = map.get(foo.getSetId());
        if(null == list) list = new ArrayList<>();
        list.add(foo);
    }
    

    现在,您需要记住时间complexity for your map implementation

    作为替代方案,groupingBy 流收集器可以让您的代码更加简洁:

    return foos.stream().collect(
            Collectors.groupingBy(Foo::getSetId, TreeMap::new, Collectors.toList()));
    

    【讨论】:

      【解决方案2】:

      尝试使用哈希图,它应该为您处理哈希,以使您的运行时间降低到接近线性。

      【讨论】:

        【解决方案3】:

        Answer by ernest_k 正确且清晰。这里有更多代码,一个完整的例子。

        首先,为Person定义一个类。

        package work.basil.example;
        
        import java.time.LocalDate;
        import java.util.Objects;
        
        public class Person
        {
            // ----------|  Member fields  |----------------------
            public String name;
            public LocalDate birthdate;
        
            // ----------|  Constructor  |----------------------
            public Person ( String name , LocalDate birthdate )
            {
                this.name = Objects.requireNonNull( name );
                this.birthdate = Objects.requireNonNull( birthdate );
            }
        
            // ----------|  Object  |----------------------
        
            @Override
            public String toString ( )
            {
                return "Person{ " +
                        "name='" + name + '\'' +
                        " | birthdate=" + birthdate +
                        " }";
            }
        
            @Override
            public boolean equals ( Object o )
            {
                if ( this == o ) return true;
                if ( o == null || getClass() != o.getClass() ) return false;
                Person person = ( Person ) o;
                return name.equals( person.name ) &&
                        birthdate.equals( person.birthdate );
            }
        
            @Override
            public int hashCode ( )
            {
                return Objects.hash( name , birthdate );
            }
        }
        

        填充一些示例数据。 Java 9 添加的Set.of 语法为我们提供了简单的文字语法。

        Set < Person > persons = Set.of(
                new Person( "Alice" , LocalDate.of( 1990 , Month.JANUARY , 23 ) ) ,
                new Person( "Bob" , LocalDate.of( 2000 , Month.FEBRUARY , 24 ) ) ,
                new Person( "Carol" , LocalDate.of( 1990 , Month.MARCH , 25 ) )
        );
        

        并发

        定义我们要填充的Map。您提到并发是一个问题,即跨线程共享此映射。因此,我们应该使用与 Java 捆绑的两个实现 ConcurrentMap 接口的类之一。

        这是我制作的图表,显示了与 Java 捆绑在一起的各种 Map 实现的属性。

        这里我们选择使用ConcurrentHashMap。如果您有大量对象,或者如果您想按特定顺序维护密钥(在我们的例子中为 Year),则替代方案 ConcurrentSkipListMap 可能会更好。

        Map < Year, List < Person > > yearToListOfPersonsMap = new ConcurrentHashMap <>();
        

        数据类型

        注意数据类型的适当使用。 Java 提供了一个Year 类来表示一整年。所以我们应该使用它。这样做可以让我们的代码更加自文档化。

        Person 类中,我们使用LocalDate 表示出生日期。该类适用于没有时间和时区的仅日期值。

        多地图

        我们将从每个人的生日中提取年份作为我们地图的关键。该值是Person 对象的List,我们将相关人员对象添加到其中。

        如果您想从收集中消除任何可能的重复 Person 对象,您可以在此处使用 Set 而不是 List

        Java 8 中添加的Map::computeIfAbsent 方法完成了被称为multimap 的工作,这是一个映射,其中一个键指向一组值而不是单个值。或者,您可以使用来自第三方的 multipmap 实现,例如 Google Guava 库。

        for ( Person person : persons )
        {
            yearToListOfPersonsMap.computeIfAbsent(
                    Year.from( person.birthdate ) ,
                    ( ( Year key ) -> new ArrayList <>() )
            ).add( person )
            ;
        }
        

        转储到控制台。

        System.out.println( "yearToListOfPersonsMap = " + yearToListOfPersonsMap );
        

        yearToListOfPersonsMap = {2000=[Person{ name='Bob' |生日=2000-02-24 }], 1990=[人{姓名='爱丽丝'|生日=1990-01-23 },人{姓名='卡罗尔'|生日=1990-03-25 }]}

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-12-25
          • 2023-04-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多