簡體   English   中英

用於合並ArrayList中對象的高效算法

[英]Efficient algorithm for merging objects in ArrayList

我有一個自定義對象的ArrayList(DTO),DTO的結構:

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

我有兩個任務:

  • 刪除數組中的重復項(似乎沒問題,我應該使用HashSet)
  • 在ArrayList中查找具有相同id字段的對象並將它們合並到一個對象中(我應該合並字段類別和觸發器)並使用合並對象創建最終List。

這項任務最有效的方法是什么? 我也很有興趣在我的算法中使用Lambda表達式。

使用流API通過指定鍵合並對象非常容易。 首先,在您的Entity類中定義一個merge方法,如下所示:

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();
}

這里我們通過getId方法的結果對Entity元素進行分組,而下游收集器只在遇到相同的id時調用Entity.merge() (我們需要另外展開Optional )。 此解決方案中的Entity不需要特殊的hashCode()equals()實現。

請注意,此解決方案會修改現有的未合並的Entity對象。 如果不合需要,請在merge()方法中創建一個新Entity並返回它(如@ Marco13答案中所述)。

創建Map<Integer, DTO>並將您的id作為鍵和對象放入DTO。 在放入map之前,只需檢查它是否已包含該鍵,如果它確實包含該鍵,則取出該鍵的DTO對象,並將類別和觸發器與舊對象合並。

正如Naman Gala回答中所建議的,一種可能的解決方案是使用從ID到實體的Map ,並在它們具有相同ID時手動合並實體。

這是在mergeById方法中實現的,其中有一些虛擬/示例輸入

  • 必須合並兩個實體(由於相同的ID)
  • 兩個實體是相等的(它們也將被“合並”,產生與其中一個輸入相同的結果)

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 + "]";
    }

}

輸出是

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]]

關於使用lambdas執行此操作的請求:可能編寫一些棘手的entities.stream().collect(...)應用程序。 但由於這不是問題的主要目標,我將把這部分答案留給別人(但不會遺漏這個小提示:只是因為你不能意味着你必須這樣做。有時,一個循環很好)。

另請注意,這很容易推廣,可能會從數據庫中提供一些詞匯。 但我認為應該回答問題的要點。

基於DTO中的id字段實現equalshashCode ,並將DTO存儲在Set 這應該解決你的兩個問題; 如果現在定義了DTO的相等方式,則Set不能存在具有相同id重復項。

編輯:

由於您的要求是根據新DTO的值合並現有DTO的類別和觸發器,因此用於存儲DTO的更合適的數據結構將是Map<DTO, DTO> (因為從A中檢索元素是很麻煩的。 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> ,在這種情況下你不需要在DTO類中覆蓋equalshashCode

如果您堅持使用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每個對象,從原始列表中找到相應的對象並合並內部集合。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM