[英]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;
我有兩個任務:
這項任務最有效的方法是什么? 我也很有興趣在我的算法中使用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
方法中實現的,其中有一些虛擬/示例輸入
。
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
字段實現equals
和hashCode
,並將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
類中覆蓋equals
和hashCode
。
如果您堅持使用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.