有什么有效的方法在Java中获取集合的第n元素?我知道两种方法:-遍历它,直到到达所需的元素--通过将它转换为ArrayList并从该ArrayList获取元素,问题是是否还有其他方法可以快速获得它的第n个元素。我主要需要一个类似于TreeSets的特性。
编辑:例如,如果我想从10000个元素长的树状图或树集中选择1000个随机元素,非常频繁(即每2-3秒),然后一直将其克隆到数组列表中,效率很低,迭代这么多元素也是没有效率的。
发布于 2017-01-13 15:12:14
如果您确定需要从集合中随机位置的n个元素(有点像统计抽样),那么您可能需要考虑在迭代集合时,只迭代一次,并以期望的概率来获取样本。这种方法效率更高,因为您将只迭代一次集合。
下面的程序演示了这个想法:
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());
}
}此程序的输出:
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可能会根据您的运气情况,得到比指定的更少或更多的样本。但是,这个问题可以通过稍微修改一下过程来解决,这使用了一个稍微不同的方法签名:
/**
* 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()方法:
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);
}
}发布于 2017-01-12 15:24:42
如果您的要求是从一个相当大的集合中选择随机元素,那么您应该问问自己,一个集合是否最适合这样做。
如果您想使用内置集,您将面临几个挑战。
TreeSet
TreeSet是一个有序集合,因此允许您访问第n个元素。但是,您必须迭代来定位n,因为没有像ArrayList那样允许随机访问的数组。顾名思义,TreeSet中的节点形成树,节点最有可能存储在内存中的任何地方。因此,要获得第n个元素,就必须从第一个节点开始,从一个节点跳到另一个节点,直到到达n位置--这类似于在LinkedList中这样做。
如果您只想选择一个随机元素,那么有几个选项:
tailSet(randomKey)并获取该尾集的第一个元素。当然,您必须处理元素范围之外的随机密钥。这样,查找基本上就是二进制搜索。HashSet
HashSets基本上包括两件事:一个桶数组和一个冲突的链表或树,也就是说,如果两个元素被映射到同一个桶中。然后,可以通过访问一个随机桶(这将是随机访问)来获得一个随机元素,然后对该桶中的元素进行随机次数的迭代。
https://stackoverflow.com/questions/41615612
复制相似问题