简体   繁体   English

Java中带有查找表的程序阶乘

[英]Program Factorial in Java with a lookup table

In an effort to improve my programming I am trying out this problem in Java. 为了改进我的编程,我正在尝试使用Java解决此问题。 I have looked everywhere for an elegant solution and cant find it. 我到处寻找一种优雅的解决方案,却找不到。

Write a program which takes a single argument, computes the factorial and prints the value on the screen.Also the program must be interactive instead of terminating after each computation and that it uses a table of previous values to improve the performance. 编写一个仅包含一个参数,计算阶乘并在屏幕上显示值的程序,该程序还必须是交互式的,而不是在每次计算后都终止,并且该程序使用以前的值表来提高性能。

How do I make it so that in the method calls it checks if it has already computed the factorial for a value? 我如何使它在方法调用中检查是否已经计算出值的阶乘? Any other improvements to this code to make it more readable or efficient are also appreciated. 还希望对此代码进行任何其他改进以使其更具可读性或效率。

This is what I have written so far. 这是我到目前为止所写的。

public static void main(String[] args) {
    int input = 0;
    Scanner reader = new Scanner(System.in);

    HashMap cache = new HashMap();

    while(1 == 1){
        do{
            try {
                System.out.println("Enter the number you would like to factor");
                input = reader.nextInt();
            } catch (InputMismatchException e){
                reader.next();
            }
        } while(input <= 0);

        if(cache.get(input) != null){
            System.out.println("Found in cache");
            System.out.println(input + "! = " + cache.get(input));
        } 
        else {
            cache.put(input, factorialRecursively(input));
            System.out.println(input + "! = " + factorialRecursively(input));
        }

    }   

}       

public static int factorialIteratively(int a) throws InputMismatchException {
    int temp = 1;
    while(a > 0){
        temp = temp * a;
        a--;
    }
    return temp;
}

public static int factorialRecursively(int a) throws InputMismatchException{
    if (a == 1){
        return 1;
    }
    else {
        return a * factorialRecursively(a-1);
    }
}

OK, I'll give it a shot. 好吧,我会试一下。 Let me first give you the full code answer, and get into each change in details: 首先让我给您完整的代码答案,然后详细介绍每次更改:

public static void main(String[] args) {
    int input = 0;

    try (Scanner reader = new Scanner(System.in)) {

        Map<Integer, Integer> cache = new HashMap<Integer, Integer>();
        cache.put(1, 1);

        while (true) {
            do {
                try {
                    System.out.println("Enter the number you would like to factor");
                    input = reader.nextInt();
                } catch (InputMismatchException e){
                    reader.next();
                    input = 0;
                }
            } while(input <= 0);

            if(cache.get(input) != null) {
                System.out.println("Found in cache");
                System.out.println(input + "! = " + cache.get(input));
            } 
            else {
                int result = factorialRecursively(input, cache);
                System.out.println(input + "! = " + result);
            }
        }   
    }
}       

public static int factorialRecursively(int limit, Map<Integer, Integer> cache) {

    int cacheSize = cache.size();
    if (cacheSize < limit) {
        int nextFactorial = cacheSize + 1; 
        System.out.println("Calculating and caching " + nextFactorial + "!");
        cache.put(nextFactorial, cache.get(cacheSize) * nextFactorial); 
        return factorialRecursively(limit, cache);
    }
    else {
        return cache.get(limit);
    }
}

1) the direct answer to your question: "how to make sure the method that calculates the factorial, checks whether it has already computed the factorial for a value". 1)您问题的直接答案:“如何确保计算阶乘的方法,检查它是否已经为值计算了阶乘”。 So I stuck to the recursive approach, although you could also implement the iterative version using the same concept. 所以我坚持使用递归方法,尽管您也可以使用相同的概念来实现迭代版本。
Basically, the idea is to pass a reference of the results cache to the method itself (the Map<Integer, Integer> cache argument), so it becomes responsible for storing the resulting computed factorial at each multiplication step, and not only once the full factorial has been computed. 基本上,这个想法是将结果缓存的引用传递给方法本身( Map<Integer, Integer> cache参数),因此它负责在每个乘法步骤中存储结果计算的阶乘,而不仅是一次完整的阶乘已被计算。 To do so, I made the method compute the factorial from the bottom up, and not the other way round. 为此,我使该方法从下至上而不是从反方向开始计算阶乘。 We store the smallest factorial in the cache as starting point ( cache.put(1, 1); ). 我们将最小的阶乘存储在缓存中作为起点( cache.put(1, 1); )。 I added an output message which shows when a factorial is calculated and cached, and by testing the code you can see that one particular factorial only ever gets calculated and stored once. 我添加了一条输出消息,显示了何时计算和缓存阶乘,并且通过测试代码,您可以看到一个特定阶乘仅被计算和存储一次。

2) the use of Map<Integer, Integer> cache = new HashMap<Integer, Integer>(); 2)使用Map<Integer, Integer> cache = new HashMap<Integer, Integer>(); instead of HashMap cache = new HashMap(); 而不是HashMap cache = new HashMap(); .
a) Since Java 1.5, collections have been made generic (long story, outside the scope here), and when referenced should be parameterized. a)从Java 1.5开始,集合已经变得通用(长话短说,超出了此处的范围),并且在引用时应进行参数化。 So here we're saying, I want to store Integer values (computed factorials), that I can access with Integer keys (factorial source). 因此,这里我们要说的是,我想存储整数值(计算的阶乘),以便可以使用整数键(阶乘源)进行访问。
b) The reason the code compiles and allows us to use integer primitives and not Integer class objects, is because of a mechanism named auto-boxing which seamlessly wraps integer primitives into Integer objects, and vice-versa. b)代码之所以可以编译并允许我们使用整数基元而不是Integer类对象,是因为有一种名为自动装箱的机制,该机制将整数基元无缝地包装到Integer对象中,反之亦然。 Again, out of scope, but one should be aware of it. 同样,超出范围,但是应该意识到这一点。
c) The functionality we are after here is that of the Map interface, which HashMap happens to be an implementation of. c)我们这里要使用的功能是Map接口的功能, HashMap恰好是该接口的实现。 Unless the referencing code relies on HashMap specific methods (unlikely), it is good practice to declare your variables as the corresponding collection interface (like List for ArrayList etc.), not as the implementation class. 除非引用代码(不太可能)依赖于HashMap特定的方法,否则,将变量声明为相应的集合接口(如ArrayList List等)而不是实现类是一个好习惯。

3) InputMismatchException is only thrown by the Scanner class, and therefore does not need to be thrown by any code that does not call the Scanner nextInt() method. 3)InputMismatchException仅由Scanner类引发,因此不需要由任何未调用Scanner nextInt()方法的代码引发。

4) the use of true instead of 1 == 1 . 4)使用true代替1 == 1 Equivalent in practice, but using a boolean value is more readable. 在实践中等效,但是使用布尔值更具可读性。

5) Only calling the factorialRecursively method once. 5)仅调用一次factorialRecursively方法。 Your code calls the method twice, which is unnecessary. 您的代码两次调用该方法,这是不必要的。 If you're after efficiency and elegance, you should only call the method once and store its results in a variable. 如果您追求效率和优雅,则只应调用该方法一次并将其结果存储在变量中。

6) the use of try (Scanner reader = new Scanner(System.in)) { ... instead of Scanner reader = new Scanner(System.in); 6)使用try (Scanner reader = new Scanner(System.in)) { ...代替Scanner reader = new Scanner(System.in); . A Scanner object must always have its close() method called to clean up resources. Scanner对象必须始终调用其close()方法来清理资源。 In older Java versions this was achieved using try{}finally{} statements, but later versions allow this syntax (try-with-resources statement) if the class implements the AutoCloseable interface. 在较早的Java版本中,这是通过try {} finally {}语句实现的,但是,如果该类实现了AutoCloseable接口,则更高的版本允许使用这种语法(try-with-resources语句)。 This means the close() method is guaranteed to always get called. 这意味着可以确保始终调用close()方法。

7) Last but not least: the use of int and Integer , which I did not change. 7)最后但并非最不重要的一点:我没有改变过intInteger的使用。 As suggested in the comments by @indivisible, you should be aware of data type limitations . 如@indivisible在注释中所建议,您应该了解数据类型限制 An int can only hold numbers represented by 31 bits + sign (-2^31 -1 to 2^31), which limits this code to 16!, after which results become erroneous. 一个int只能保存由31位+符号(-2 ^ 31 -1到2 ^ 31)表示的数字,这会将代码限制为16 !,之后结果将是错误的。 If you wish to generate bigger numbers you should use long / Long . 如果希望生成更大的数字,则应使用long / Long Anyway, again out of scope of this question really. 无论如何,确实再次超出了这个问题的范围。

Have fun :) 玩得开心 :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM