【问题标题】:Java large for performanceJava 性能大
【发布时间】:2019-04-18 12:13:54
【问题描述】:

我必须找到第 100 位数字(不包括数字 3、4、7),但这需要很长时间,大约 9 秒才能找出如何提高性能。

import java.sql.Date;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;

public class FirstChallange {

    private static int removedNumbers;
    private static int numberQuantity;
    private static int lastNumberFound; 
    private static int cont;
    private static int pos3;
    private static int pos4;
    private static int pos7;    

    public static void main(String[] args) 
    { 

        long inicio = System.currentTimeMillis();  

        removedNumbers = 0;
        numberQuantity = 10000000;


        for (cont = 1; removedNumbers <= numberQuantity; cont++) {      
            String str = new String(); 
            str = String.valueOf(cont);     

            pos3 = str.indexOf("3");
            pos4 = str.indexOf("4");
            pos7 = str.indexOf("7");

            if((pos3 == -1) && (pos4 == -1) && (pos7 == -1)) {
                removedNumbers++; 
                if(removedNumbers == numberQuantity){ // can not find numbers (3, 4, 7)
                    lastNumberFound = cont; 
                }
            } 
        }       

        DecimalFormat dfmt = new DecimalFormat("0");

        System.out.println(dfmt.format(lastNumberFound)); 

        long fim  = System.currentTimeMillis();   
        System.out.println(new SimpleDateFormat("ss.SSS").format(new Date(fim - inicio)));  


    }
}

将数字转换为字符串并使用 indexOf 最佳模式删除它们?还是有什么比 indexOf 更好的,比如 RabinKarp?

预期结果:180999565 在 5/4 秒内

【问题讨论】:

  • 如果您的代码有效并且需要优化,您应该将其发布到codereview.stackexchange.com
  • 您应该以完全不同的方式思考问题。在每十年的自然数中,有 7 个是可以的:如果你必须说出第 20 个,应该是 29。你能算出 100 个自然数、1000 个、100 万个自然数的数字吗?第 40 个 OK 数字 not 等细节为 58(但 88)。一旦编程,2019 年的计算设备应该需要几毫秒,如果那样的话。
  • (Errm.我必须找到第千个数字numberQuantity = 10000000?!)

标签: java algorithm performance numbers


【解决方案1】:

你应该换个角度思考问题。

"生成所有 numberQuantity 以下不包含 3、4、7 的数字"

或者换句话说: "创建仅包含 0,1,2,5,6,8,9 作为数字的数字"。

这是 7 个不同的数字。

因此,一种方法可以是从 1 开始递增计数器并将其转换为以 7 为基数的表示,您可以像这样映射每个数字:

0->0
1->1
2->2
3->5
4->6
5->8
6->9

【讨论】:

  • 这将如何工作,计数器是一,我们将它映射到二?所以现在序列中缺少一个?我了解您的目标,但不了解您的映射..
  • (不需要的最小数字是 3,而不是 1。)
  • 因建议找到第n个数字而被否决意味着生成numberQuantity以下的所有数字
  • 我基于您的基本转换想法得到了 O(log(n)) 的答案。请注意,正如@greybeard 所观察到的,执行直接基数转换比在到达正确的数字之前生成所有n 数字要好得多。迭代是n 倍差。
【解决方案2】:

需要搜索吗?在上面的 cmets 上工作,有 7 位,有一种更有效的直接方法,即生成基数 7 位,然后根据排除方案重新映射它们。

请参阅下面的完整程序文本,它实现了三种策略: 更快的“搜索”,它使用字节数组而不是字符串。这与原始算法大致相同,但应该更快。使用 java 格式进行 base 7 转换,以及直接 base 7 转换,这很可能最能满足原始问题的要求。

这里只是“直接”实现。此实现将目标数字生成为基数 7 数字,然后根据方案重新映射数字以排除数字 3、4 和 7:

public static int findNumberDirect(int index) {
    int base = 7;

    byte[] digits = new byte[10];  // Storage for the base 7 digits.

    // Convert to base 7 by generating digits for each power of 7.

    for ( int nextDigit = 0; index > 0; nextDigit++ ) {
        int nextRem = index % base;
        index = index / base;

        digits[nextDigit] = (byte) nextRem;
    }

    // Remap the digits from base 7 to base 10 with exclusions.
    // This could be done in the prior loop.  I've kept this as
    // a separate step for clarity.

    int number = 0;

    int numDigits = digits.length;
    for ( int nextDigit = 0; nextDigit < numDigits; nextDigit++ ) {
        number *= 10;
        number += DIGIT_MAPPING[ digits[ numDigits - nextDigit - 1 ] ];
    }

    return number;
}

以下是程序输出的时间:

Index [        1 ] Search [        1 ] [   2100 (ns) ] Convert [        1 ] [   4500 (ns) ] Direct [        1 ] [   1300 (ns) ] 
Index [        4 ] Search [        6 ] [   1400 (ns) ] Convert [        6 ] [   1900 (ns) ] Direct [        6 ] [   1000 (ns) ] 
Index [       10 ] Search [       15 ] [   1900 (ns) ] Convert [       15 ] [   2000 (ns) ] Direct [     15 ] [   1000 (ns) ] 
Index [      100 ] Search [      202 ] [  26300 (ns) ] Convert [      202 ] [   2100 (ns) ] Direct [      202 ] [   1000 (ns) ] 
Index [     1000 ] Search [     2929 ] [  98300 (ns) ] Convert [     2929 ] [   2100 (ns) ] Direct [     2929 ] [   1000 (ns) ] 
Index [    10000 ] Search [    61106 ] [ 694300 (ns) ] Convert [    61106 ] [   2100 (ns) ] Direct [    61106 ] [    900 (ns) ]

这是完整的程序文本:

package my.tests;

public class NumberCounter {

    // Find the thousandth number (excluding numbers 3, 4, 7).

    public static final String USAGE_TEXT = "Usage: " + NumberCounter.class.getName() + " index*";

    public static final int MAX_INDEX = 10000000;

    public static void main(String[] args) {
        for ( int argNo = 0; argNo < args.length; argNo++ ) {
            String indexText = args[argNo];
            int index = Integer.parseInt(indexText);
            if ( index <= 0 ) {
                System.out.println("Error: Index [ " + indexText + " ] is less than 1.");
                return;
            } else if ( index > MAX_INDEX ) {
                System.out.println("Error: Index [ " + indexText + " ] is greater than " + Integer.toString(MAX_INDEX) + ".");
                return;
            }

            long searchStart = System.nanoTime();
            int searchNumber = findNumberSearch(index);
            long searchEnd = System.nanoTime();

            long convertStart = System.nanoTime();
            int convertNumber = findNumberConvert(index);
            long convertEnd = System.nanoTime();

            long directStart = System.nanoTime();
            int directNumber = findNumberDirect(index);
            long directEnd = System.nanoTime();

            System.out.println("Index [ " + formatAmount((long) index) + " ]" +
                               " Search [ " + formatAmount(searchNumber) + " ] [ " + formatDuration(searchEnd, searchStart) + " ]" +
                               " Convert [ " + formatAmount(convertNumber) + " ] [ " + formatDuration(convertEnd, convertStart) + " ]" +
                               " Direct [ " + formatAmount(directNumber) + " ] [ " + formatDuration(directEnd, directStart) + " ]");                               
        }
    }

    public static String formatDuration(long end, long start) {
        return String.format("%6d (ns)", Long.valueOf(end - start));
    }

    public static String formatAmount(long amount) {
        return String.format("%8d", Long.valueOf(amount));
    }

    private final static byte[] DIGIT_MAPPING = { 0, 1, 2, 5, 6, 8, 9 };

    public static int findNumberSearch(int index) {
        byte[] digits = new byte[10];
        int numDigits = digits.length;

        for ( int nextNumber = 0; nextNumber < index; nextNumber++ ) {
            for ( int nextDigit = 0; nextDigit < numDigits; nextDigit++ ) {
                int digitOffset = numDigits - nextDigit - 1;
                byte digit = digits[digitOffset];
                if ( digit == 6 ) {
                    digit = 0;
                } else {
                    digit++;
                }
                digits[digitOffset] = digit;
                if ( digit != 0 ) {
                    break;
                }
            }
        }

        int number = 0;

        for ( int nextDigit = 0; nextDigit < numDigits; nextDigit++ ) {
            number *= 10;
            number += DIGIT_MAPPING[ digits[nextDigit] ];
        }

        return number;
    }

    public static int findNumberConvert(int index) {
        String numberText = Integer.toString(index, 7);

        int number = 0;

        int numDigits = numberText.length();
        for ( int nextDigit = 0; nextDigit < numDigits; nextDigit++ ) {
            number *= 10;
            number += DIGIT_MAPPING[ numberText.charAt(nextDigit) - '0' ];
        }

        return number;
    }

    public static int findNumberDirect(int index) {
        int base = 7;

        byte[] digits = new byte[10];

        for ( int nextDigit = 0; index > 0; nextDigit++ ) {
            int nextRem = index % base;
            index = index / base;

            digits[nextDigit] = (byte) nextRem;
        }

        int number = 0;

        int numDigits = digits.length;
        for ( int nextDigit = 0; nextDigit < numDigits; nextDigit++ ) {
            number *= 10;
            number += DIGIT_MAPPING[ digits[ numDigits - nextDigit - 1 ] ];
        }

        return number;
    }
}

【讨论】:

  • 我似乎已经重新实现了您的答案,然后再回来并完全阅读它。您的答案可以通过首先说明您如何回答问题以及您的答案有效的原因来改进;然后,稍后,提供基准。直接转换的见解更有趣(目前隐藏在代码的底部)
  • 是的,这有点简洁。我做了一些编辑。谢谢!
【解决方案3】:

按照 MrSmith 的 answer,您可以观察到,一旦您使用下面的查找表和函数翻译预期的输出 180999565...

                       // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
static int b10_to_b7[] = {0, 1, 2,-1,-1, 3, 4,-1, 5, 6}; // avoids 3, 4 and 7
static int b7_to_b10[] = {0, 1, 2, 5, 6, 8, 9,-1,-1,-1}; // undoes above permutation 

// extracts digits in base b, replacing them according to p
static long permuteDigits(long input, long base, int[] p) {
    long output = 0;
    long shift = 1;
    while (input > 0) {
       int digit = (int)(input % base);
       input /= base;
       output += p[digit] * shift;
       shift *= base;
    }
    return output;
}

// converts input digits in base ibase into obase
static long changeBase(long input, long ibase, long obase) {
    long output = 0;
    long shift = 1;
    while (input > 0) {
       int digit = (int)(input % ibase);
       input /= ibase;
       output += digit * shift;
       shift *= obase;
    }
    return output;
}


permuteDigits(180999565L, 10, b10_to_b7); // 150666343
changeBase(150666343L, 10, 7);            // 10000000  !!

因此,使用相同的函数,您可以通过反转操作直接找到您的结果:

changeBase(10000000L, 7, 10);             // 150666343
permuteDigits(150666343L, 10, b7_to_b10); // 180999565

而且这只需要O(log(n)) 操作,这比任何迭代方法都快得多。你原来的代码是O(n log(n))

请注意,您可以通过合并这两个函数来优化我的代码。但这只会让您获得 2 倍的速度提升:没有什么比避免迭代带来的 10M 提升更剧烈了。

【讨论】:

    【解决方案4】:

    每次执行 indexOf 时,都会在 String 中循环查看它是否包含字符。

    然后你正在做 3 个“indexOf”,所以在每一轮循环中,你在该部分的处理乘以 3。

    我们可以考虑将这 3 个检查放在一个循环中,从而提高性能,例如通过创建一个模拟“indexOf”但您通过 3 个验证的方法:

    import java.sql.Date;
    import java.text.DecimalFormat;
    import java.text.SimpleDateFormat;
    
    public class FirstChallange {
    
        private static int removedNumbers=0;
        private static int numberQuantity;
        private static int lastNumberFound; 
        private static int cont;
    
        private static char three = '3';
        private static char four = '4';
        private static char seven = '7';
    
        private static SimpleDateFormat sf = new SimpleDateFormat("ss.SSS");
        private static DecimalFormat dfmt = new DecimalFormat("0");
    
        public static void main(String[] args) 
        { 
    
            long inicio = System.currentTimeMillis();  
    
            numberQuantity = 10000000;
    
            String str; 
    
            for (cont = 1; removedNumbers <= numberQuantity; cont++) {      
    
                //Faster way to create String
                str = "" + cont;
    
                if(!contains(str.toCharArray())) {
                    removedNumbers++; 
                    if(removedNumbers == numberQuantity){ // can not find numbers (3, 4, 7)
                        lastNumberFound = cont; 
                    }
                } 
            }
    
            System.out.println(dfmt.format(lastNumberFound)); 
    
            long fim  = System.currentTimeMillis();
            System.out.println(sf.format(new Date(fim - inicio)));  
    
        }
    
        public static boolean contains(char[] chars) {
    
            for (int i = 0; i < chars.length; i++) {
                 if(chars[i] == three) {
                    return true;
                } else if (chars[i] == four) {
                    return true;
                } else if(chars[i] == seven) {
                    return true;
                }
            }
            return false;
        }
    
    }
    

    我在这里创建的 contains 方法检查是否存在我们不想要的字符,如果是,我们不再检查并返回 true。

    我将性能降低了 3 或 4 秒。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-07
      • 2010-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-27
      相关资源
      最近更新 更多