Java Project Euler 35:哈希集给出了不正确的结果
我为以下内容编写了一个Java程序: 这个数字197被称为循环素数,因为 数字197971和719本身就是素数 100以下有13个这样的素数:2,3,5,7,11,13,17,31, 37、71、73、79和97 一百万以下有多少个循环素数 我的代码可以很好地编译和运行,但是,它会根据我使用的数据结构给出不同的结果 算法的工作原理如下:Java Project Euler 35:哈希集给出了不正确的结果,java,algorithm,performance,Java,Algorithm,Performance,我为以下内容编写了一个Java程序: 这个数字197被称为循环素数,因为 数字197971和719本身就是素数 100以下有13个这样的素数:2,3,5,7,11,13,17,31, 37、71、73、79和97 一百万以下有多少个循环素数 我的代码可以很好地编译和运行,但是,它会根据我使用的数据结构给出不同的结果 算法的工作原理如下: 获取预先计算的素数。这是对MathUtils.getPrimes(1000000)的调用,它获取所有等于或小于一百万的素数。我将其存储在另一个集中,因为它是通过
MathUtils.getPrimes(1000000)
的调用,它获取所有等于或小于一百万的素数。我将其存储在另一个集中
,因为它是通过返回子集来实现的,除非我将素数复制到它们自己的数据结构中,否则性能会非常糟糕TreeSet
存储素数,则性能非常快,并产生正确的结果:
int current = start;
do {
int currMagnitude = 1;
for (int i = current; i > 9; i /= 10) {
currMagnitude *= 10;
}
if (currMagnitude == magnitude)
results.add(current);
current = ((current % 10) * magnitude) + (current / 10);
} while (current != start);
回答:55时间:76毫秒 如果我切换到
HashSet
,性能会差得多,结果也不正确
回答:50时间:2527毫秒 我将代码放在顶部,以在代码运行之前仔细检查这两个集合是否包含相同的值,而且它们总是这样
HashSet
与TreeSet
相比会产生不正确的结果?没有空值或其他奇怪的值,只有正的、不同的Integer
实例。这些集合开始时包含完全相同的数据。算法是一样的,因为它是完全相同的代码。由于实现和数据大小之间的顺序差异,几乎不可能在算法运行时比较它们的状态。如果我减少输入大小,这两种方法将产生相同的结果,最多可达100000个TreeSet
的执行速度比HashSet
快得多,而它必须执行所有那些不适用于HashSet
的删除和树旋转?查看支持HashSet
的HashMap
的代码,除了定位到特定bin的内容外,没有正在进行的内容大小调整或洗牌。此外,素数分布相当均匀。虽然没有简单的方法进行验证,但我希望不会出现最坏情况下的性能问题,即许多项目占用了表中的少量存储箱集
实现
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.NavigableSet;
import java.util.TreeSet;
public class Problem_0035 {
public static void main(String[] args) {
// Swap these two variable names to compare.
Collection<Integer> primes = new TreeSet<>(sieve(1000000));
Collection<Integer> primes2 = new HashSet<>(sieve(1000000));
if (!primes.containsAll(primes2) || !primes2.containsAll(primes)
|| (primes.size() != primes2.size())) {
System.out.println("Primes are not the same!");
}
final long start = System.currentTimeMillis();
int result = 0;
// Keep getting a prime and checking for its rotations. Remove the primes checked.
while (!primes.isEmpty()) {
Integer next = primes.iterator().next();
Collection<Integer> rotations = getRotations(next);
if (primes.containsAll(rotations)) {
result += rotations.size();
}
primes.removeAll(rotations);
}
System.out.println("Answer: " + result);
// 55
System.out.println("Time: " + (System.currentTimeMillis() - start) + "ms");
}
/** Enumerate all rotations of the given integer. */
private static Collection<Integer> getRotations(Integer argValue) {
Collection<Integer> results = new LinkedList<>();
final int start = argValue.intValue();
// Count the digits
int magnitude = 1;
for (int i = start; i > 9; i /= 10) {
magnitude *= 10;
}
int current = start;
do {
results.add(Integer.valueOf(current));
current = ((current % 10) * magnitude) + (current / 10);
} while (current != start);
return results;
}
/** Sieve of Eratosthenes. */
private static Collection<Integer> sieve(int argCeiling) {
NavigableSet<Integer> primes = new TreeSet<>();
for (int i = 2; i <= argCeiling; ++i) {
primes.add(Integer.valueOf(i));
}
for (Integer number = primes.first(); number != null; number = primes.higher(number)) {
int n = number.intValue();
for (int i = n * 2; i <= argCeiling; i += n) {
primes.remove(Integer.valueOf(i));
}
}
return primes;
}
//
// Filter the set through this method to remove the problematic primes.
// See answers for an explanation.
//
/**
* Any prime number with a zero or five anywhere in its number cannot have prime
* rotations, since no prime can end in five or zero. Filter those primes out.
*/
private static Collection<Integer> filterImpossiblePrimes(Collection<Integer> in) {
Collection<Integer> out = new TreeSet<>();
for (Integer prime : in) {
if (!willBeRotatedComposite(prime)) {
out.add(prime);
}
}
return out;
}
/** If the prime is guaranteed to be rotated to a composite, return true. */
private static boolean willBeRotatedComposite(Integer prime) {
int p = prime.intValue();
boolean result = false;
if (p > 10) {
while (p > 0) {
// Primes must end in 1, 3, 7, or 9. Filter out all evens and 5s.
if ((p % 5 == 0) || (p % 2 == 0)) {
result = true;
break;
}
p /= 10;
}
}
return result;
}
}
import java.util.Collection;
导入java.util.HashSet;
导入java.util.LinkedList;
导入java.util.NavigableSet;
导入java.util.TreeSet;
公共课问题{
公共静态void main(字符串[]args){
//交换这两个变量名以进行比较。
集合素数=新树集(筛(1000000));
集合素数2=新哈希集(筛(1000000));
如果(!primes.containsAll(primes2)| |!primes2.containsAll(primes)
||(primes.size()!=primes2.size()){
System.out.println(“素数不一样!”);
}
最终长启动=System.currentTimeMillis();
int结果=0;
//继续获取素数并检查其旋转。移除已检查的素数。
而(!primes.isEmpty()){
整数next=primes.iterator().next();
集合旋转=getRotations(下一步);
if(素数包含所有(旋转)){
结果+=旋转。大小();
}
素数。移除所有(旋转);
}
System.out.println(“回答:“+结果”);
// 55
System.out.println(“时间:”+(System.currentTimeMillis()-start)+“毫秒”);
}
/**枚举给定整数的所有旋转*/
私有静态集合getRotations(整型argValue){
收集结果=新建LinkedList();
final int start=argValue.intValue();
//数一数数字
整数震级=1;
对于(int i=start;i>9;i/=10){
震级*=10;
}
int电流=启动;
做{
结果.add(Integer.valueOf(current));
电流=((电流%10)*幅值)+(电流/10);
}while(当前!=启动);
返回结果;
}
/**埃拉托斯坦筛*/
专用静态收集筛(内部ARG天花板){
NavigableSet primes=新树集();
对于(int i=2;i 0){
//素数必须在1、3、7或9中结束。过滤掉所有的even和5s。
如果((p%5==0)| |(p%2==0)){
结果=真;
打破
}
p/=10;
}
}
返回结果;
}
}
一些摆弄表明,哈希集最昂贵的位是通过Integer next=primes.iterator().next()查找下一个要检查的素数代码>-在我的机器上,使用hashset的版本几乎只需要4秒,其中大约3.9秒用于迭代器相关的业务
HashSet
基于HashMap
,其迭代器必须逐步遍历所有bucket,直到找到一个非空bucket;根据我对HashMap
源代码的浏览,它在删除后不会自行调整大小,也就是说,一旦您将其调整到一定容量,如果不插入,则必须手动调整大小。这可能会产生这样的效果:一旦您删除了HashSet
中相当大的一部分元素,它的大部分bucket都是空的,因此查找第一个非空bucket就变得非常困难
Collection<Integer> primesCopy = new HashSet<>(primes);
for(Integer i in primesCopy) {
if(!primes.contains(i)) continue;
// rest of code as it was