【问题标题】:Making a generic function work with multiple non-overlapping types使泛型函数与多个非重叠类型一起工作
【发布时间】:2015-08-27 19:40:00
【问题描述】:

我正在尝试编写一个函数,该函数从集合中随机抽取元素并将它们添加到新集合中。所以如果你想从 {1,2,3,4,5} 中绘制 3 个元素,你可以得到 {5,3,4}。我想出了这个通用函数:

/**
 * Take a random sample without repeats of the specified size from a
 * collection. Note that if the collection you're sampling from contains
 * repeated elements, then the sample could also contain repeated elements.
 * Use a Set as an argument to avoid this.
 *
 * @param <T> The type of objects in the Collection
 * @param <E> The type of the collection
 * @param collection The collection
 * @param size The sample size of elements which you wish to extract from
 * the collection
 * @param factory A factory method for the collection E. Call with
 * "new::ArrayList" or something similar.
 * @return A random sample of the collection consisting of 'size' elements
 * without repeats (unless the original collection contained repeats).
 * @throws IllegalArgumentException if size is larger than the collection.size().
 */
public static <T, E extends Collection<T>> E drawRandomlyWithoutReplacement(List<T> collection, int size, Supplier<E> factory) {
    if (size > collection.size()) {
        throw new IllegalArgumentException("The sample size cannot be greater than the size of the collection.");
    }

    E list = factory.get();
    for (int i = 0; i < size; i++) {
        int r = MathUtils.randomInt(0, collection.size() - 1);
        list.add(collection.remove(r));
    }
    return list;
}

不幸的是,Collection 接口没有一个函数,如果你删除它,它会从集合中返回一个元素,但是 List 和 Vector(以及其他)确实有它。有没有办法让这个函数适用于列表和向量,而不必重载 3 次?我尝试将第一个参数设为 C 类型,其中 C extends List&lt;T&gt; | Vector&lt;T&gt; 但不幸的是这不起作用。

【问题讨论】:

  • Set是哪个?
  • 您的 JavaDoc 说:“使用 Set 作为参数来避免这种 [重复]”。所以没有必要创建不同的方法还是我错了?
  • @DraganBozanovic,你说得对,Set 没有这个方法。
  • A Vector is-a List ...!?
  • 请解释为什么将E extends Collection 更改为E extends List 应该不能解决您的问题

标签: java generics


【解决方案1】:

Collection 接口不能删除,但它的 Iterator

public static <T, E extends Collection<T>> E drawRandomlyWithoutReplacement(Collection<T> collection, int size,
    Supplier<E> factory) {
  final int colSize = collection.size();
  if (size > colSize) {
    throw new IllegalArgumentException("The sample size cannot be greater than the size of the collection.");
  } else if(size == 0) {
    return factory.get();
  }

  Random rand = new Random();
  Set<Integer> sampleIndices = new TreeSet<>();
  while (sampleIndices.size() < size) {
    sampleIndices.add(rand.nextInt(colSize));
  }

  E result = factory.get();

  Iterator<T> collectionIterator = collection.iterator();
  Iterator<Integer> indexIterator = sampleIndices.iterator();
  int sampleIndex = indexIterator.next();
  for (int i = 0; i < colSize; i++) {
    T sample = collectionIterator.next();
    if (i == sampleIndex) {
      result.add(sample);
      collectionIterator.remove();
      if (indexIterator.hasNext()) {
        sampleIndex = indexIterator.next();
      } else {
        break;
      }
    }
  }
  return result;
}

【讨论】:

    【解决方案2】:

    collection 是任何集合 (Collection&lt;T&gt; collection)。

    E list = factory.get();
    List<T> colAsList = new ArrayList<>(collection);
    for (int i = 0; i < size; i++) {
        int r = MathUtils.randomInt(0, colAsList.size() - 1);
        list.add(colAsList.remove(r));
    }
    collection.removeAll(list);
    return list;
    

    【讨论】:

      【解决方案3】:

      List、ArrayList、Vector 是子类,它定义了

      E remove(int index) //移除并返回

      http://docs.oracle.com/javase/7/docs/api/java/util/List.html

      感谢 Marco 指出,我最初建议使用 AbstractList

      【讨论】:

      【解决方案4】:

      对于未知大小的集合或元素流(在您的情况下为迭代器),有一系列算法具有您需要的确切用途,称为 reservoir sampling

      水库抽样是一系列随机算法,用于从包含 n 个项目的列表 S 中随机选择 k 个项目的样本,其中 n 是一个非常大或未知的数字。

      以下算法在线性时间内运行,并保证以所需的概率选择数字。

      public static <T> List<T> reservoirSample(Iterable<T> items, int m){
          ArrayList<T> res = new ArrayList<T>(m);
          int count = 0;
          for(T item : items){
              count++;
              if (count <= m)
                  res.add(item);
              else{
                  int r = rnd.nextInt(count);
                  if (r < m)
                      res.set(r, item);
              }
          }
          return res;
      }
      

      此示例取自this blogpost,您可以在此处进一步了解该主题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多