用于合并ArrayList中对象的高效算法

时间:2015-08-03 09:15:10

标签: java algorithm arraylist java-8

我有一个自定义对象的ArrayList(DTO),DTO的结构:

private String id;
private String text;
private String query;
private String locatorId;
private Collection<String> categories;
private Collection<String> triggers;

我有两项任务:

  • 删除数组中的重复项(似乎没问题,我应该使用HashSet)
  • 使用相同的id字段在ArrayList中查找对象并将它们合并到一个对象中(我应该合并字段类别和触发器)并使用合并对象创建最终List。

这项任务最有效的方法是什么?我在算法中使用 Lambda 表达式也很有意思。

5 个答案:

答案 0 :(得分:4)

使用流API按指定键合并对象非常容易。首先,在merge类中定义Entity方法,如下所示:

public Entity merge(Entity other) {
    this.categories.addAll(other.categories);
    this.triggers.addAll(other.triggers);
    return this;
}

然后您可以构建自定义分组收集器:

import static java.util.stream.Collectors.*;

public static Collection<Entity> mergeAll(Collection<Entity> input) {
    return input.stream()
                .collect(groupingBy(Entity::getId,
                    collectingAndThen(reducing(Entity::merge), Optional::get)))
                .values();
}

在这里,我们按Entity方法的结果对getId元素进行分组,下游收集器在遇到相同的Entity.merge()时调用id(我们需要在{{ 1}}另外)。此解决方案中Optional无需特殊hashCode()equals()实施。

请注意,此解决方案会修改现有的未合并Entity个对象。如果不合适,请在Entity方法中创建一个新的Entity并将其返回(如@ Marco13答案中所示)。

答案 1 :(得分:2)

创建Map<Integer, DTO>并将您的ID作为密钥和对象放置为DTO。在放入map之前,只需检查它是否已包含该键,如果它确实包含该键,则取出该键的DTO对象,并将类别和触发器与旧对象合并。

答案 2 :(得分:2)

正如answer by Naman Gala中所建议的,一种可能的解决方案是使用ID中的some case class case class Foo(id: Int, year: Int) 实体,并在实体具有相同ID时手动合并。

这是在Map方法中实现的,其中一些虚拟/示例输入

  • 必须合并两个实体(由于ID相同)
  • 两个实体相等(它们也将“合并”,产生与其中一个输入相同的结果)

mergeById

输出

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;


public class MergeById
{
    public static void main(String[] args)
    {
        List<Entity> entities = new ArrayList<Entity>();
        entities.add(new Entity("0", "A", "X", "-1", 
            Arrays.asList("C0", "C1"), Arrays.asList("T0", "T1")));
        entities.add(new Entity("0", "A", "X", "-1", 
            Arrays.asList("C2", "C3"), Arrays.asList("T2")));
        entities.add(new Entity("1", "B", "Y", "-2", 
            Arrays.asList("C0"), Arrays.asList("T0", "T1")));
        entities.add(new Entity("1", "B", "Y", "-2", 
            Arrays.asList("C0"), Arrays.asList("T0", "T1")));
        entities.add(new Entity("2", "C", "Z", "-3", 
            Arrays.asList("C0", "C1"), Arrays.asList("T1")));

        System.out.println("Before merge:");
        for (Entity entity : entities)
        {
            System.out.println(entity);
        }

        List<Entity> merged = mergeById(entities);

        System.out.println("After  merge:");
        for (Entity entity : merged)
        {
            System.out.println(entity);
        }
    }

    private static List<Entity> mergeById(Iterable<? extends Entity> entities)
    {
        Map<String, Entity> merged = new HashMap<String, Entity>();
        for (Entity entity : entities)
        {
            String id = entity.getId();
            Entity present = merged.get(id);
            if (present == null)
            {
                merged.put(id, entity);
            }
            else
            {
                merged.put(id, Entity.merge(present, entity));
            }
        }
        return new ArrayList<Entity>(merged.values());
    }

}


class Entity
{
    private String id;
    private String text;
    private String query;
    private String locatorId;
    private Collection<String> categories;
    private Collection<String> triggers;

    Entity()
    {
        categories = new LinkedHashSet<String>();
        triggers = new LinkedHashSet<String>();
    }

    Entity(String id, String text, String query, String locatorId,
        Collection<String> categories, Collection<String> triggers)
    {
        this.id = id;
        this.text = text;
        this.query = query;
        this.locatorId = locatorId;
        this.categories = categories;
        this.triggers = triggers;
    }

    String getId()
    {
        return id;
    }

    static Entity merge(Entity e0, Entity e1)
    {
        if (!Objects.equals(e0.id, e1.id))
        {
            throw new IllegalArgumentException("Different id");
        }
        if (!Objects.equals(e0.text, e1.text))
        {
            throw new IllegalArgumentException("Different text");
        }
        if (!Objects.equals(e0.query, e1.query))
        {
            throw new IllegalArgumentException("Different query");
        }
        if (!Objects.equals(e0.locatorId, e1.locatorId))
        {
            throw new IllegalArgumentException("Different id");
        }
        Entity e = new Entity(e0.id, e0.text, e0.query, e0.locatorId, 
            new LinkedHashSet<String>(), new LinkedHashSet<String>());
        e.categories.addAll(e0.categories);
        e.categories.addAll(e1.categories);
        e.triggers.addAll(e0.triggers);
        e.triggers.addAll(e1.triggers);
        return e;
    }

    @Override
    public String toString()
    {
        return "Entity [id=" + id + ", text=" + text + ", query=" + query +
            ", locatorId=" + locatorId + ", categories=" + categories +
            ", triggers=" + triggers + "]";
    }

}

关于使用lambdas执行此操作的请求:可能编写一些棘手的Before merge: Entity [id=0, text=A, query=X, locatorId=-1, categories=[C0, C1], triggers=[T0, T1]] Entity [id=0, text=A, query=X, locatorId=-1, categories=[C2, C3], triggers=[T2]] Entity [id=1, text=B, query=Y, locatorId=-2, categories=[C0], triggers=[T0, T1]] Entity [id=1, text=B, query=Y, locatorId=-2, categories=[C0], triggers=[T0, T1]] Entity [id=2, text=C, query=Z, locatorId=-3, categories=[C0, C1], triggers=[T1]] After merge: Entity [id=0, text=A, query=X, locatorId=-1, categories=[C0, C1, C2, C3], triggers=[T0, T1, T2]] Entity [id=1, text=B, query=Y, locatorId=-2, categories=[C0], triggers=[T0, T1]] Entity [id=2, text=C, query=Z, locatorId=-3, categories=[C0, C1], triggers=[T1]] 应用程序。但由于这不是问题的主要目标,我将把这部分答案留给别人(但不会遗漏这个小提示:只是因为你不能意味着你必须这样做。有时,一个循环很好)。

另请注意,这很容易推广,可能会从数据库中提供一些词汇。但我认为应该回答问题的要点。

答案 3 :(得分:1)

根据DTO中的equals字段实施hashCodeid,并将DTO存储在Set中。这应该解决你的两个问题;鉴于现在定义了DTO的相等方式,id中不存在具有相同Set的重复项。

编辑:

由于您的要求是根据新DTO的值合并现有DTO的类别和触发器,因此用于存储DTO的更合适的数据结构将是Map<DTO, DTO>(因为它很麻烦一旦你把元素放进去,就从Set中检索元素。另外,我认为DTO中的类别和触发器应定义为Set s,禁止重复;这将使合并操作更加简单:

private Set<String> categories;
private Set<String> triggers;

假设DTO为上述字段提供了访问者(getCategories / getTriggers)(并且字段永远不会是null),现在可以在以下方式:

public static void mergeOrPut(Map<DTO,DTO> dtos, DTO dto) {
    if (dtos.containsKey(dto)) {
        DTO existing = dtos.get(dto);
        existing.getCategories().addAll(dto.getCategories());
        existing.getTriggers().addAll(dto.getTriggers());
    } else {
        dtos.put(dto, dto);
    }
}

也可以轻松修改上述代码以使用Map<Integer, DTO>,在这种情况下,您无需覆盖equals类中的hashCodeDTO

答案 4 :(得分:1)

如果您坚持使用lambda表达式,则可以执行以下操作:

Set<X> x = new TreeSet<>((o1, o2) -> 
        ((X)o1).getId().equals(((X)o2).getId()) ? 0 : 1);

List<X> list = new ArrayList<>(set.addAll(x));

这将根据其ID创建具有唯一对象的集合。接下来,对于list中的每个对象,从原始列表中找到相应的对象并合并内部集合。