如何设计集合交集、差集、补集

本文探讨了如何在Java中实现集合的交集、差集和补集操作。从自定义实现开始,逐步介绍了JDK和Guava库提供的解决方案。通过分析源码,展示了Guava如何巧妙利用迭代器提高效率。

如何设计集合交集、差集、补集

集合的交集可能你会说JDK已经提供了交集的功能,但是差集、补集并没有提供实现。于是,首先让我们动手重复造轮子,先实现一个交集功能,你会怎么实现呢?

我设计一个函数,需要两个入参,然后求他们交集,并返回。我的实现如下。

public static <T> Set<T> intersection(Collection<T> from, Collection<T> to) {
      Objects.requireNonNull(from);
      Objects.requireNonNull(to);

      Iterator<T> iterator = from.iterator();
      Set<T> result = new HashSet<>();
      while (iterator.hasNext()) {
          T var = iterator.next();
          if (to.contains(var)) {
              result.add(var);
          }
      }
      return result;
 }

当然,这里肯定还有不足的地方,比如性能,内存等等。

JDK是如何设计的呢?

既然JDK已经我们提供这个功能,看看官方是怎么实现的,有什么值得借鉴的地方呢?
JDK在 AbstractCollection 提供了实现,主要是借助迭代器来实现的。

public boolean retainAll(Collection<?> c) {
      Objects.requireNonNull(c);
      boolean modified = false;
      Iterator<E> it = iterator();
      while (it.hasNext()) {
          if (!c.contains(it.next())) {
              it.remove();
              modified = true;
          }
      }
      return modified;
  }

但是这里有一个比较尬尴的地方是,使用 Arrays.asList() 返回的list调用 retainAll 就会抛出异常。原因是Arrays.asList 只是提供一种视图的list,不支持修改操作。

那还要其他更好设计吗?

当然有啊,guava库提供Sets的工具类,它提供集合交集、差集、补集等等功能。在了解guava是如何设计之前,先了解怎么使用它。

 Set<Integer> var1 = Sets.newHashSet(1,3,4,5,8);
 Set<Integer> var2 = Sets.newHashSet(3,4,6,9,12);
 Sets.SetView<Integer> var3 = Sets.intersection(var1, var2);
 System.out.println(var3);  
 // [3,4] 交集

System.out.println(Sets.difference(var1, var2));
// [8,1,5] var1的差集

System.out.println(Sets.symmetricDifference(var1, var2));
// [8, 1, 5, 9, 12, 6] 补集

那么guava是如何设计的呢?
迭代器。首先guava的自定义的迭代器 AbstractIterator

public abstract class AbstractIterator<T> extends UnmodifiableIterator<T> {
   private State state = State.NOT_READY;
   private @Nullable T next;

   protected abstract T computeNext();  // 钩子函数

   /**
   * 实现接口Iterator的hasNext方法,状态state来判断是否还有下个元素
   */
   @Override
   public final boolean hasNext() {
    checkState(state != State.FAILED);
    switch (state) {
      case DONE:
        return false;
      case READY:
        return true;
      default:
    }
    return tryToComputeNext();
  }

  /**
  * tryToComputeNext方法用来计算下一个元素的值,并把值绑定到域next上。
  * 
  */
  private boolean tryToComputeNext() {
    state = State.FAILED; // temporary pessimism
    next = computeNext();
    if (state != State.DONE) {
      state = State.READY;
      return true;
    }
    return false;
  }

   /**
   * 实现了接口Iterator的next方法,并返回域next的值
   */
   public final T next() {
    if (!hasNext()) {
      throw new NoSuchElementException();
    }
    state = State.NOT_READY;
    T result = next;
    next = null;
    return result;
  }
}

Sets.intersection在内部使用匿名类重写了 AbstractIterator 类的 computeNext 方法。

return new AbstractIterator<E>() {
    final Iterator<E> itr = set1.iterator();
    @Override
    protected E computeNext() {
      while (itr.hasNext()) {
        E e = itr.next();
        if (set2.contains(e)) {
          return e;
        }
      }
      return endOfData();
    }
};

方法调用时序图
在这里插入图片描述

再造轮子

现在我们需要动手借助guava设计,仿造这样的迭代器,对之前轮子进行改进。

参考guava的 AbstractIterator 迭代器,并简化一些状态的设计。

abstract static class MyIterator<T> implements Iterator<T> {
    private boolean endOf = false;
    private T next;
    
    protected abstract T computeNext();

    @Override
    public boolean hasNext() {
        next = computeNext();
        return !endOf ;
    }

    @Override
    public T next() {
        return next;
    }

    protected T endOfData(){
        endOf = true;
        return null;
    }
}

使用迭代改进第一个设计的版本。

public static <T> Set<T> intersection(Collection<T> from, Collection<T> to) {
    Objects.requireNonNull(from);
    Objects.requireNonNull(to);
    return new AbstractSet<T>() {
        @Override
        public Iterator<T> iterator() {
            return new MyIterator<T>() {
                final Iterator<T> itr = from.iterator();
                @Override
                protected T computeNext() {
                    while (itr.hasNext()) {
                        T var = itr.next();
                        if (to.contains(var)) {
                            return var;
                        }
                    }
                    return endOfData();
                }
            };
        }

        @Override
        public int size() {
            int cnt = 0;
            for (T t: from) {
                if (to.contains(t)) {
                    cnt++;
                }
            }
            return cnt;
        }
    };
}

guava的差集、补集都是类似的实现,如果你感兴趣的话,可以尝试把其他两个实现了。真的是长见识,迭代器还可以这样玩,优秀。

Python中,可以使用集合(set)来进行交集差集的操作。 1. 交集:两个集合中共同存在的元素构成的新集合。可以使用`&`算符或者`intersection()`方法来实现。 示例代码: ``` set1 = {1, 2, 3} set2 = {2, 3, 4} intersection_set = set1 & set2 # 或者使用 intersection() 方法 # intersection_set = set1.intersection(set2) print(intersection_set) # 输出: {2, 3} ``` 2. :两个集合中所有的元素构成的新集合。可以使用`|`运算符或者`union()`方法来实现。 示例代码: ```python set1 = {1, 2, 3} set2 = {2, 3, 4} union_set = set1 | set2 # 或者使用 union() 方法 # union_set = set1.union(set2) print(union_set) # 输出: {1, 2, 3, 4} ``` 3. 差集:第一个集合中存在,而第二个集合中不存在的元素构成的新集合。可以使用`-`运算符或者`difference()`方法来实现。 示例代码: ```python set1 = {1, 2, 3} set2 = {2, 3, 4} difference_set = set1 - set2 # 或者使用 difference() 方法 # difference_set = set1.difference(set2) print(difference_set) # 输出: {1} ``` 4. :在全中存在,但是不在指定集合中的元素构成的新集合。可以使用`^`运算符或者`symmetric_difference()`方法来实现。 示例代码: ```python set1 = {1, 2, 3} set2 = {2, 3, 4} complement_set = set1 ^ set2 # 或者使用 symmetric_difference() 方法 # complement_set = set1.symmetric_difference(set2) print(complement_set) # 输出: {1, 4} ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值