如何从Java的ArrayList中删除重复项

5

我希望根据productId和priceTagId删除重复的条目。如果我们删除了重复项,需要添加数量。

在productDetails列表中,相同的productId存在但数量不同,如果需要将数量添加到一个中

"productDetails" : [
        {
            "productId" : "5764dfb7d991390e25edff74",
            "quantity" : 2,
            "netQty" : "10mg",
            "priceTagId" : 1,
            "alertAvailablity" : "Success"
        },
        {
            "productId" : "5764dfb7d991390e25edff74",
            "quantity" : 4,
            "netQty" : "10mg",
            "priceTagId" : 1,
            "alertAvailablity" : "Success"
        },
        {
            "productId" : "5764dfb7d991390e25edff74",
            "quantity" : 6,
            "netQty" : "30mg",
            "priceTagId" : 3,
            "alertAvailablity" : "Success"
        },
        {
            "productId" : "5764dfb7d991390e25edff74",
            "quantity" : 8,
            "netQty" : "30mg",
            "priceTagId" : 3,
            "alertAvailablity" : "Success"
        },
        {
            "productId" : "2345dfb7d991390e25edf659",
            "quantity" : 8,
            "netQty" : "30mg",
            "priceTagId" : 3,
            "alertAvailablity" : "Success"
        }
    ],

我得到的最终输出是

"productDetails" : [
        {
            "productId" : "5764dfb7d991390e25edff74",
            "quantity" : 6,
            "netQty" : "10mg",
            "priceTagId" : 1,
            "alertAvailablity" : "Success"
        },
        {
            "productId" : "5764dfb7d991390e25edff74",
            "quantity" : 14,
            "netQty" : "30mg",
            "priceTagId" : 3,
            "alertAvailablity" : "Success"
        },
        {
            "productId" : "2345dfb7d991390e25edf659",
            "quantity" : 8,
            "netQty" : "30mg",
            "priceTagId" : 3,
            "alertAvailablity" : "Success"
        }

    ],

基于productId和priceTagId,我需要删除重复项并在删除的重复项中添加数量。
private List<ProductDetail> removeDuplicateProducts(List<ProductDetail> productDetails) throws BaseException {
    for (ProductDetail eachProductDetail : productDetails) {
        for (ProductDetail eachInnerProductDetail : productDetails) {
            if(eachProductDetail.getProductId().equals(eachInnerProductDetail.getProductId()))
            {
                if(eachProductDetail.getPriceTagId().equals(eachInnerProductDetail.getPriceTagId()))
                {
                    eachProductDetail.setQuantity(eachProductDetail.getQuantity()+eachInnerProductDetail.getQuantity());
                    productDetails.clear();
                }
            }

        }
    }           
    return productDetails;
}

但我不明白为什么?出了什么问题吗?

1
为什么不把它们放在一个集合中呢? - Bálint
如果您不想要重复项,请使用Set。 - Tizianoreica
2
Set建议很好,但没有考虑添加数量的逻辑。 - Rian O'Dwyer
你需要使用productId + netQty作为键,quantity作为值来构建一个映射表(HashMap)。遍历数组:如果以productId和netQty为键的项不在映射表中,则将其添加到映射表中。如果它已经在映射表中,则将该项的数量加到现有项上。 - Zaphod Beeblebrox
1
如果你正在组合相关元素,则并不是“删除重复项”。 - Andy Turner
显示剩余2条评论
6个回答

3
最有效的解决方案是使用一个Map,其中键是您认为使产品相同的所有字段的组合,而值则包含任何其他信息。 在您的情况下,您可以这样做:
private Collection<ProductDetail> accumulateDuplicateProducts(List<ProductDetail> productDetails) {
    // use a map to quickly find entries which match.
    // using a linked HashMap means the order of addition is preserved.
    Map<String, ProductDetail> productMap = new LinkedHashMap<>();
    for (ProductDetail pd : productDetails) {
        // build a composite key of the fields you want to match on.
        String key = pd.getProductId() + " " + pd.getPriceTag();
        // if the Strings match they should be merged.
        // if there was no previous entry, use the current one.
        // if there was a previous entry call merge() to combine them.
        productMap.compute(key, (k, pd2) -> pd2 == null ? pd : merge(pd, pd2));
    }
    return productMap.values();
}

private static ProductDetail merge(ProductDetail pd, ProductDetail pd2) {
    // combine two ProductDetails
}

注意:如果您使用两个嵌套循环,则时间复杂度为 O(n) 而不是 O(n^2)

但我不明白为什么?有什么问题吗?

您面临的一个问题是:

productDetails.clear();

你面临的另一个问题是,你将每个条目与每个条目进行比较,例如假设你有两个匹配的条目AB

A is compared with A so A *= 2
A is compared with B do A += B
B is compared with A so B += A
B is compared with B so B *= 2

你最终会得到两个条目,因为你没有删除其中一个。

我不太明白,如果您有时间,能否详细解释一下您的代码? - Aravi S
其实我的目的是我正在做一个购物网站,当用户将同一产品添加到购物车中时,我只需要更新数量。 - Aravi S
@AraviS 我已经在我的回答中加入了注释。对于同一产品进行数量累积是一种很常见的模式。在交易中,你可能会有负数数量 ;) - Peter Lawrey
我理解PD2的复杂性问题,但如何进行合并呢? - Aravi S
@AraviS 这听起来像是一个关于如何在屏幕或者网页浏览器上显示的不同问题。 - Peter Lawrey
显示剩余2条评论

1
我会创建一个ProductKey类:

class ProductKey {
  private final Integer productId;
  private final Integer priceTagId;
  //constructor, getters, equals, hashcode
}

然后将所有产品放入一个Map<ProductKey, List<ProductDetail>>中,其中键是上述类的一个实例,值是与ProductKey匹配的所有产品的列表。
然后通过对每个列表的元素进行合并,如求和数量等来合并每个列表。
您还可以可能在一步中运行这两个步骤。

0

你可以使用 set 来移除重复项,并在 ProductDetail 类的 equals 方法中更改数量。

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class Test {
public static void main(String[] args) {
    List<ProductDetail> productDetails = new ArrayList<ProductDetail>();
    ProductDetail p1 = new ProductDetail("1", "pt1", 10);
    ProductDetail p2 = new ProductDetail("1", "pt1", 40);
    ProductDetail p3 = new ProductDetail("2", "pt1", 30);

    productDetails.add(p1);
    productDetails.add(p2);
    productDetails.add(p3);

    List<ProductDetail> list = removeDuplicateProducts(productDetails);
    for (ProductDetail p : list) {
        System.out.println(p);
    }

}

private static List<ProductDetail> removeDuplicateProducts(
        List<ProductDetail> productDetails) {

    Set<ProductDetail> set = new LinkedHashSet<ProductDetail>(
            productDetails);

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

    return list;
}

private static class ProductDetail {
    public ProductDetail(String productId, String priceTagId, int quantity) {
        this.productId = productId;
        this.priceTagId = priceTagId;
        this.quantity = quantity;
    }

    String productId;
    String priceTagId;
    int quantity;

    public String getProductId() {
        return productId;
    }

    public void setProductId(String productId) {
        this.productId = productId;
    }

    public String getPriceTagId() {
        return priceTagId;
    }

    public void setPriceTagId(String priceTagId) {
        this.priceTagId = priceTagId;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    @Override
    public String toString() {
        return (this.productId+"--"+this.priceTagId+"--"+this.quantity);

    }

    @Override
    public int hashCode() {
        return (this.priceTagId.hashCode()*this.priceTagId.hashCode());
    }

    @Override
    public boolean equals(Object obj) {
        ProductDetail p1 = (ProductDetail) obj;
        if ((p1.getPriceTagId().equals(this.getPriceTagId()) && p1
                .getProductId().equals(this.getProductId()))) {
            p1.setQuantity(this.getQuantity() + p1.getQuantity());
            return true;
        }
        return false;
    }
}

}


你能用 foreach 分享它吗? - Aravi S
你的第一个回答我能理解,但这个不正确。 - Aravi S
第一个答案不适用于处理大量记录。我测试了上述代码,它可以正常工作。你试过吗? - Selvakumar Ponnusamy
Anna,我没懂。你能否把它们结合起来,或者提供一些有效的代码? - Aravi S
在removeDuplicateProducts()方法中,您应该在哪里添加equals()方法? - Aravi S
修改了答案。当您向Set添加元素时,Set会内部调用hashcode和equals方法。 - Selvakumar Ponnusamy

0
在这个例子中,我会使用一个映射表。让我们看看为什么:
private List<ProductDetail> removeDuplicateProducts(List<ProductDetail> productDetails) throws BaseException {

相同的方法签名也可以。现在,不同之处在于

//This map will contain the final set of elements
Map<Integer, ProductDetail> map = new HashMap<Integer, ProductDetail>();

for (ProductDetail item: productDetails){
    //If map already contains the same productId it adds the quantities but doesn't add the same productId again
    if(map.containsKey(item.getProductId())){
        ProductDetail mapItem = map.get(item.getProductId());
        mapItem.setQuantity(mapItem.getQuantity() + item.getQuantity());
    }
    //If map doesn't contain the same productId, it's added for the first time
    else{
        mapItem.put(item.getProductId(), item);
    }
}

//At this point the map only contains a set of different productId. Now it will be dumped into a list and returned.
return new ArrayList<String>(map.values());

希望它有所帮助


0
在你的代码中,如果你使用像这样的“for each”语句,那么列表的一个元素总是会与自己比较一次,这将导致错误的结果。你应该使用索引来访问列表的元素。
for (int i = 0; i <= list.size() - 1; i++) {
 for (int j = i + 1; j <= list.size() - 1; j++) {
  <enter your if statements here>
 }
}

记得使用list.remove(indexOfTheDuplicate)来删除重复元素,list.clear()会删除列表中的所有元素。


0
您可以更改方法,返回一个包含所需数据的新列表。
private List<ProductDetail> removeDuplicateProducts(List<ProductDetail> productDetails) {

    List<ProductDetail> returnList = new ArrayList<ProductDetail>();
    boolean exists = false;

    for (ProductDetail eachProductDetail : productDetails) {
        exists = false;
        for (ProductDetail eachInnerProductDetail : returnList) {
            // Your match condition
            if (eachProductDetail.getProductId().equals(eachInnerProductDetail.getProductId())
              && eachProductDetail.getPriceTagId().equals(eachInnerProductDetail.getPriceTagId()) ) {
                exists = true;
                eachInnerProductDetail.setQuantity(eachProductDetail.getQuantity() + eachInnerProductDetail.getQuantity());
                break; 
            }
        }

        // add to output list if not exists
        if (!exists){
            returnList.add(eachProductDetail);
        }

    }
    return returnList;
}

通过这种方式,您可以拥有原始列表和新列表。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接