Java Project Euler 35:哈希集给出了不正确的结果

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)的调用,它获取所有等于或小于一百万的素数。我将其存储在另一个集中,因为它是通过

我为以下内容编写了一个Java程序:

这个数字197被称为循环素数,因为 数字197971和719本身就是素数

100以下有13个这样的素数:2,3,5,7,11,13,17,31, 37、71、73、79和97

一百万以下有多少个循环素数

我的代码可以很好地编译和运行,但是,它会根据我使用的数据结构给出不同的结果

算法的工作原理如下:

  • 获取预先计算的素数。这是对
    MathUtils.getPrimes(1000000)
    的调用,它获取所有等于或小于一百万的素数。我将其存储在另一个
    集中
    ,因为它是通过返回子集来实现的,除非我将素数复制到它们自己的数据结构中,否则性能会非常糟糕

  • 当素数集不为空时,获取下一个素数

  • 求出那个素数的所有旋转。例如1979719。这些旋转本身不需要素数,因为我需要验证它们

  • 如果素数集包含所有旋转数,则将旋转数添加到运行总数中

  • 移除素数集合中的所有旋转(如果存在)

  • 我注意到这个代码有两个奇怪的地方。如果我使用
    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