首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在Java中多次获取集合的第n个元素

在Java中多次获取集合的第n个元素
EN

Stack Overflow用户
提问于 2017-01-12 14:25:22
回答 2查看 1.7K关注 0票数 2

有什么有效的方法在Java中获取集合的第n元素?我知道两种方法:-遍历它,直到到达所需的元素--通过将它转换为ArrayList并从该ArrayList获取元素,问题是是否还有其他方法可以快速获得它的第n个元素。我主要需要一个类似于TreeSets的特性。

编辑:例如,如果我想从10000个元素长的树状图或树集中选择1000个随机元素,非常频繁(即每2-3秒),然后一直将其克隆到数组列表中,效率很低,迭代这么多元素也是没有效率的。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2017-01-13 15:12:14

如果您确定需要从集合中随机位置的n个元素(有点像统计抽样),那么您可能需要考虑在迭代集合时,只迭代一次,并以期望的概率来获取样本。这种方法效率更高,因为您将只迭代一次集合。

下面的程序演示了这个想法:

代码语言:javascript
复制
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

public class SamplingFromSet {

    public static void main(String[] args) {
        Set<String> population = new TreeSet<>();

        /*
         * Populate the set
         */
        final int popSize = 17;
        for (int i=0; i<popSize; i++) {
            population.add(getRandomString());
        }

        List<String> sample 
            = sampleFromPopulation(population, 3 /*sampleSize */);

        System.out.println("population is");
        System.out.println(population.toString());
        System.out.println("sample is");
        System.out.println(sample.toString());

    }


    /**
     * Pick some samples for a population
     * @param population
     * @param sampleSize - number of samples
     * @return
     */
    private static <T> 
    List<T> sampleFromPopulation(Set<T> population
                                    , int sampleSize) {
        float sampleProb = ((float) sampleSize) / population.size();
        List<T> sample = new ArrayList<>();
        Iterator<T> iter = population.iterator();
        while (iter.hasNext()) {
            T element = iter.next();
            if (random.nextFloat()<sampleProb) {
                /*
                 * Lucky Draw!
                 */
                sample.add(element);
            }
        }
        return sample;
    }


    private static Random random = new Random();    

    private static String getRandomString() {
        return String.valueOf(random.nextInt());
    }
}

此程序的输出:

代码语言:javascript
复制
population is
[-1488564139, -1510380623, -1980218182, -354029751, -564386445, -57285541, -753388655, -775519772, 1538266464, 2006248253, 287039585, 386398836, 435619764, 48109172, 580324150, 64275438, 860615531]
sample is
[-57285541, -753388655, 386398836]

更新

然而,上面的程序有一个警告--由于在集合中取样本是通过概率完成的,返回的sample可能会根据您的运气情况,得到比指定的更少或更多的样本。但是,这个问题可以通过稍微修改一下过程来解决,这使用了一个稍微不同的方法签名:

代码语言:javascript
复制
/**
 * Pick some samples from a population
 * @param population
 * @param sampleSize - number of samples
 * @param exactSize - a boolean to control whether or not
 *   the returned sample list must be of the exact size as
 *   specified.
 * @return
 */
private static <T> 
List<T> sampleFromPopulation(Set<T> population
                                , int sampleSize
                                , boolean exactSize);

防止过抽样

在通过总体的一次迭代中,我们稍微超过了样本,然后在结束时,如果我们有太多的样本,我们会删除一些样本。

防止过采样

还要注意的是,即使是过采样,也有一个非零的概率,在一次迭代结束时,我们得到的样本仍然比期望的少。如果发生这种情况(不可能),我们将像第二次尝试一样递归地调用相同的方法。(这种递归有接近一个终止的概率,因为它与方法的多次递归调用不同,我们始终得到欠采样。)

以下代码实现了新的sampleFromPopulation()方法:

代码语言:javascript
复制
private static <T> 
List<T> sampleFromPopulation(Set<T> population
                                , int sampleSize
                                , boolean exactSize) {
    int popSize = population.size();
    double sampleProb = ((double) sampleSize) / popSize;

    final double    OVER_SAMPLING_MULIT = 1.2;
    if (exactSize) {
        /*
         * Oversampling to enhance of chance of getting enough
         * samples (if we then have too many, we will drop them 
         * later)
         */
        sampleProb = sampleProb * OVER_SAMPLING_MULIT;
    }
    List<T> sample = new LinkedList<>(); // linked list for fast removal
    Iterator<T> iter = population.iterator();
    while (iter.hasNext()) {
        T element = iter.next();
        if (random.nextFloat()<sampleProb) {
            /*
             * Lucky Draw!
             */
            sample.add(element);
        }
    }
    int samplesTooMany = sample.size() - sampleSize;
    if (!exactSize || samplesTooMany==0) {
        return sample;
    } else if (samplesTooMany>0) {
        Set<Integer> indexesToRemoveAsSet = new HashSet<>();
        for (int i=0; i<samplesTooMany; ) {
            int candidate = random.nextInt(sample.size());
            if (indexesToRemoveAsSet.add(candidate)) {
                /*
                 * add() returns true if candidate was not 
                 * previously in the set
                 */
                i++; // proceed to draw next index
            }
        }
        List<Integer> indexesToRemoveAsList 
            = new ArrayList<>(indexesToRemoveAsSet);
        Collections.sort(indexesToRemoveAsList
                , (i1, i2) -> i2.intValue() - i1.intValue()); // desc order  
        /*
         * Now we drop from the tail of the list
         */
        for (Integer index : indexesToRemoveAsList) {
            sample.remove((int) index); // remove by index (not by element)
        }
        return sample;
    } else { 
        /*
         * we were unluckly that we oversampling we still
         * get less samples than specified, so here we call
         * this very same method again recursively
         */
        return sampleFromPopulation(population, sampleSize, exactSize);
    }
}
票数 1
EN

Stack Overflow用户

发布于 2017-01-12 15:24:42

如果您的要求是从一个相当大的集合中选择随机元素,那么您应该问问自己,一个集合是否最适合这样做。

如果您想使用内置集,您将面临几个挑战。

TreeSet

TreeSet是一个有序集合,因此允许您访问第n个元素。但是,您必须迭代来定位n,因为没有像ArrayList那样允许随机访问的数组。顾名思义,TreeSet中的节点形成树,节点最有可能存储在内存中的任何地方。因此,要获得第n个元素,就必须从第一个节点开始,从一个节点跳到另一个节点,直到到达n位置--这类似于在LinkedList中这样做。

如果您只想选择一个随机元素,那么有几个选项:

  • 如果集合不改变(或者不经常),您可以创建一个匹配的数组或ArrayList,并使用随机访问。
  • 在集合上迭代一次随机次数。
  • 生成一个随机密钥并查找下一个较高/较低的元素,例如使用tailSet(randomKey)并获取该尾集的第一个元素。当然,您必须处理元素范围之外的随机密钥。这样,查找基本上就是二进制搜索。

HashSet

HashSets基本上包括两件事:一个桶数组和一个冲突的链表或树,也就是说,如果两个元素被映射到同一个桶中。然后,可以通过访问一个随机桶(这将是随机访问)来获得一个随机元素,然后对该桶中的元素进行随机次数的迭代。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/41615612

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档