【问题标题】:check positive or negative without using conditional statements in java在java中不使用条件语句检查肯定或否定
【发布时间】:2014-06-29 05:29:27
【问题描述】:

上周有人问我一个面试问题:

我需要一个函数来打印出一个数字是正数还是负数,而不使用if elsewhileforswitcha? b:c等条件语句。我该怎么做。

我告诉面试官这是不可能的,因为这个问题本质上是“有条件的”。他告诉我这是可能的,但没有告诉我怎么做。我做了很多搜索,但没有好的答案。

【问题讨论】:

  • 正确答案-“没关系,因为这个问题很愚蠢。任何在生产代码中使用技巧而不是使用 if 语句的人都应该被解雇。”
  • 面试中问这些问题有什么意义?我怀疑雇主会找到最好的人选。诚然,思维很牵强,但解决方案的可读性较差。
  • 我希望这是一个接近比特或硬件的工作。
  • @GabeSechan 如果这是一个好主意,取决于代码的性质。这样的技巧在高性能代码中很有用。在密码学代码中,它们对于避免侧信道攻击至关重要。许多现代加密库要求不得有任何依赖于秘密数据的分支。所以我强烈不同意你以这种笼统的形式发表的言论。
  • a ? b : c 不是条件语句,而是条件表达式。事实上,作为一个表达式,而不是一个语句,首先是条件运算符存在的全部理由!因此,问题是错误的 ;-) (嘿,如果你想和你的受访者开玩笑,那就把你的问题弄对吧!)

标签: java


【解决方案1】:

一种可能的解决方案:

String[] responses = {"Positive", "Negative"};
System.out.println(responses[(i >> 31) & 1]);

这也将零计为正数。

因为 Java 中的整数需要存储在 two's complement 中(或者表现得好像它们一样),所以任何负数的最高位是 1,而任何其他数字的最高位是 0。(i >> 31) 复制最高位到其他位(因此负数变为11111111 11111111 11111111 11111111,正/零数变为00000000 00000000 00000000 00000000)。 & 1 将除最低位之外的所有位都设置为 0。(i >> 31) & 1 组合有效地仅读取 i 的最高位。

【讨论】:

  • 是的。但我的解释可能令人困惑。
  • 如果我们要计算跳转表,您可以说switch (i / i) { case -1: ...; break; case 1: ...; break; },并希望编译器同意您将其设为跳转表。
  • 该问题并未仅指定整数。在执行上述操作之前,您可以通过将 i/abs(i) 转换为 int32 来解决此问题。
  • @ebarr 你可以使用Double.doubleToLongBitsFloat.floatToIntBits 然后同样的事情——浮点数和双打也使用最高位作为符号。
  • 右移 31 位将“符号位”移动到最低位; 'anding' 与 1 将产生 1 或 0。现在您有了字符串数组的索引。我认为 >>> 会在不扩展符号位的情况下对其进行移动,并消除对 and 的需要,因此另一个答案是使用 responses[i >>> 31)]
【解决方案2】:

稍微详细说明一下immibis的回答:

int index(int i) {
    return 1 + (i>>31) - (-i>>31);
}

String[] text = {"negative", "zero", "positive"};

private String text(int i) {
    return text[index(i)];
}

有符号移位i>>31 将每个负数转换为-1,将其他每个负数转换为0。计算-i>>31 允许区分正数和非正数。现在看看计算出来的index

positive: 1 +    0 - (-1) = 2
zero:     1 +    0 -    0 = 1
negative: 1 + (-1) -    0 = 0

【讨论】:

    【解决方案3】:

    这里有一个变体,说明零既不是正也不是负:

        int x = (int)Math.sqrt(Math.pow(n, 2));
        try {
            x = n / x;
        }
        catch (ArithmeticException e) {
            x = 0;
        }
    
        String[] result = {"negative", "zero", "positive"};
        System.out.println(result[x + 1]);
    

    【讨论】:

    • 冷静的开箱即用答案。
    • 这认为整数 > 46340 或
    • @dave_thompson_085 好点。我应该使用 Math.pow。答案已相应编辑。
    • 你也可以使用Math.signum
    【解决方案4】:

    滥用数组不能有负大小这一事实的超级简单解决方案:

    void printPositive(int i) {
        try { new int[i]; System.out.println("positive"); } 
        catch( NegativeArraySizeException e) { System.out.println("negative"); }
    }
    

    好的,如果i 是肯定的,这个答案可能会分配一个巨大的数组,并且VM 在评估new int[i] 时可能会在其内部使用条件语句,但至少它会向面试官展示某种创造力。此外,它可能会向面试官展示你可以跳出框框思考(因为他可能预计你会像大多数其他答案一样使用一些魔法)并做一些完全不同的事情。

    【讨论】:

    • 对于接近 Integer.MAX_VALUE 的数字,它会总是失败,因为 maximum array size 有点小(JVM 需要一些地方来放置标头)。还要注意,避免分支有一个很好的理由:分支很慢(而且这个问题的大多数答案都更慢)。
    • @maaartinus:对,我们也应该抓住OutOfMemoryError。当然,这个解决方案对于真正的代码来说是没有用的。分支默认不慢;只有无法很好预测的分支才会很慢。因此,如果您的函数的输入在 95% 的情况下都是正数,那么该分支实际上对于正数输入是空闲的。
    • 关于分支,我know。还有(in)famous question
    【解决方案5】:

    Old answer。我做出这个新答案的原因是因为我使用了布尔的compareTo 方法,该方法使用三元运算符将布尔表达式转换为二进制。

    这是我的新答案,更难读。

    public static String positiveOrNegative(int n) {
    
        ArrayList<String> responses = new ArrayList<String>();
        // first element should be "Zero", so if n is 0, the response is "Zero"
        responses.add("Zero");
    
        // this populates the ArrayList with elements "Positive" for n elements
        // so that if n is positive, n will be an index in the ArrayList
        // and the return will be "Positive"
        // but still if n is negative, it will never be an index in the ArrayList
        for (int i = 0; i < n; i++) {
            responses.add("Positive");
        }
    
        String response = "";
        try {
            // try to get a response from the ArrayList
            response = responses.get(n);
        } catch (Exception e) {
             // index is out of bounds, so it must have been negative
            response = "Negative";
        }
    
        return response;
    }
    
    public static void main(String[] args) {
        System.out.println(positiveOrNegative(4)); // Positive
        System.out.println(positiveOrNegative(1)); // Positive
        System.out.println(positiveOrNegative(0)); // Zero
        System.out.println(positiveOrNegative(-1)); // Negative
        System.out.println(positiveOrNegative(-4)); // Negative
    }
    

    【讨论】:

    • -1 因为布尔对compareTo 的内部实现是:return (x == y) ? 0 : (x ? 1 : -1);,根据要求,这是不允许的。
    • @bbalchev 我知道这一点,但我只是希望它是规则的一个例外,因为我们从未在函数中看到它。但是,该代码仍在使用中,所以您是对的。我现在编辑了答案,以消除所有条件语句的使用。这很丑陋,但至少它是正确的。如果你现在觉得这个答案合适,你可以取消你的反对票。
    • for 循环也是不允许的。
    • @mikeaworski 可能是因为 for 循环包含条件语句。
    • 理论上是正确的,但除此之外它是如此糟糕,它开始变得有趣了。尝试使用0x7FFFFFFF(32 位)。以防万一它在没有 OOM 崩溃或准挂起的情况下工作,请将其扩展到 64 位并重试。
    【解决方案6】:

    另一种可能的解决方案:

    boolean isPositive(int n) {
      return n > ((n + 1) % n);
    }
    

    但它不适用于0

    ---- 编辑 -----

    新算法:

    String isPositive(int n) {
      String[] results = {"-", "", "+"};
      return results[1+(1+((n+1)%n)*((n-1)%n))/n];
    }
    

    它仍然不适用于0

    【讨论】:

    • 你仍然需要像if (isPositive) print "positive"这样的代码
    • 这并不是问题所问的。你也可以这样做:return n &gt; 0;
    • 糟糕,我想我误解了这个问题。
    【解决方案7】:

    伙计们,这并不难,不需要移位或做奇怪的调用,只需使用 Math 类中的 signum 方法! ;p

    http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html#signum%28float%29
    

    【讨论】:

    • 如果我是面试官,我会雇用这个人。没有必要重新发明轮子。
    猜你喜欢
    • 2022-01-26
    • 1970-01-01
    • 1970-01-01
    • 2017-12-08
    • 2014-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-19
    相关资源
    最近更新 更多