【问题标题】:How does this print "hello world"?这如何打印“hello world”?
【发布时间】:2014-01-10 06:55:08
【问题描述】:

我发现了这个奇怪的地方:

for (long l = 4946144450195624l; l > 0; l >>= 5)
    System.out.print((char) (((l & 31 | 64) % 95) + 32));

输出:

hello world

这是如何工作的?

【问题讨论】:

  • 我的意思是你可以自己解决这个问题。
  • 是的。我承认......我在钓鱼:)
  • 我想我以前在这里看到过这个问题..
  • @Oli 应该有一顶帽子。
  • 像这样的问题,不会改进数据库,只是作为点击诱饵存在,是将来取消帽子游戏的可靠方法。请不要通过嫖娼毁掉游戏。

标签: java string bit-shift


【解决方案1】:

数字4946144450195624适合64位,其二进制表示为:

 10001100100100111110111111110111101100011000010101000

程序为每 5 位组解码一个字符,从右到左

 00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
   d  |  l  |  r  |  o  |  w  |     |  o  |  l  |  l  |  e  |  h

5 位编码

对于 5 位,可以表示 2⁵ = 32 个字符。英文字母表包含 26 个字母,这为 32 - 26 = 6 个符号留下了空间 除了字母。使用这种编码方案,您可以拥有所有 26 个(一种情况)英文字母和 6 个符号(它们之间的空格)。

算法说明

for 循环中的>>= 5 从一个组跳到另一个组,然后将 5 位组与句子 l & 31 中的掩码 31₁₀ = 11111₂ 进行“与”运算

现在代码将 5 位值映射到其对应的 7 位 ascii 字符。这是棘手的部分,检查小写的二进制表示 下表中的字母:

  ascii   |     ascii     |    ascii     |    algorithm
character | decimal value | binary value | 5-bit codification 
--------------------------------------------------------------
  space   |       32      |   0100000    |      11111
    a     |       97      |   1100001    |      00001
    b     |       98      |   1100010    |      00010
    c     |       99      |   1100011    |      00011
    d     |      100      |   1100100    |      00100
    e     |      101      |   1100101    |      00101
    f     |      102      |   1100110    |      00110
    g     |      103      |   1100111    |      00111
    h     |      104      |   1101000    |      01000
    i     |      105      |   1101001    |      01001
    j     |      106      |   1101010    |      01010
    k     |      107      |   1101011    |      01011
    l     |      108      |   1101100    |      01100
    m     |      109      |   1101101    |      01101
    n     |      110      |   1101110    |      01110
    o     |      111      |   1101111    |      01111
    p     |      112      |   1110000    |      10000
    q     |      113      |   1110001    |      10001
    r     |      114      |   1110010    |      10010
    s     |      115      |   1110011    |      10011
    t     |      116      |   1110100    |      10100
    u     |      117      |   1110101    |      10101
    v     |      118      |   1110110    |      10110
    w     |      119      |   1110111    |      10111
    x     |      120      |   1111000    |      11000
    y     |      121      |   1111001    |      11001
    z     |      122      |   1111010    |      11010

在这里你可以看到我们要映射的 ascii 字符从第 7 位和第 6 位集合 (11xxxxx₂) 开始(除了空格,它只有第 6 位),你可以OR 5-少量 使用96 (96₁₀ = 1100000₂) 进行编码,这应该足以进行映射,但这不适用于空间(该死的空间!)

现在我们知道必须特别注意在处理其他字符的同时处理空间。为了实现这一点,代码打开第 7 位(但不是第 6 位) 提取的具有 OR 64 64₁₀ = 1000000₂ (l & 31 | 64) 的 5 位组。

到目前为止,5 位组的格式为:10xxxxx₂(空格为1011111₂ = 95₁₀)。 如果我们可以将空间映射到0 而不影响其他值,那么我们可以打开第 6 位,这应该就是全部了。 这是mod 95 部分的作用,空格是1011111₂ = 95₁₀,使用mod 操作(l & 31 | 64) % 95) 只有空间回到0,在此之后,代码通过添加32₁₀ = 100000₂ 打开第 6 位 对于之前的结果,((l & 31 | 64) % 95) + 32) 将 5 位值转换为有效的 ascii 字符

isolates 5 bits --+          +---- takes 'space' (and only 'space') back to 0
                  |          |
                  v          v
               (l & 31 | 64) % 95) + 32
                       ^           ^ 
       turns the       |           |
      7th bit on ------+           +--- turns the 6th bit on

以下代码执行逆过程,给定一个小写字符串(最多 12 个字符),返回可与 OP 代码一起使用的 64 位长值:

public class D {
    public static void main(String... args) {
        String v = "hello test";
        int len = Math.min(12, v.length());
        long res = 0L;
        for (int i = 0; i < len; i++) {
            long c = (long) v.charAt(i) & 31;
            res |= ((((31 - c) / 31) * 31) | c) << 5 * i;
        }
        System.out.println(res);
    }
}    

【讨论】:

  • 这个答案并不神秘。相反,它会帮你思考。
  • 答案比问题更难:D
  • 解释清楚多了:)
【解决方案2】:

为上述答案增加一些价值。以下 groovy 脚本打印中间值。

String getBits(long l) {
return Long.toBinaryString(l).padLeft(8,'0');
}

for (long l = 4946144450195624l; l > 0; l >>= 5){
    println ''
    print String.valueOf(l).toString().padLeft(16,'0')
    print '|'+ getBits((l & 31 ))
    print '|'+ getBits(((l & 31 | 64)))
    print '|'+ getBits(((l & 31 | 64)  % 95))
    print '|'+ getBits(((l & 31 | 64)  % 95 + 32))

    print '|';
    System.out.print((char) (((l & 31 | 64) % 95) + 32));
}

在这里

4946144450195624|00001000|01001000|01001000|01101000|h
0154567014068613|00000101|01000101|01000101|01100101|e
0004830219189644|00001100|01001100|01001100|01101100|l
0000150944349676|00001100|01001100|01001100|01101100|l
0000004717010927|00001111|01001111|01001111|01101111|o
0000000147406591|00011111|01011111|00000000|00100000| 
0000000004606455|00010111|01010111|01010111|01110111|w
0000000000143951|00001111|01001111|01001111|01101111|o
0000000000004498|00010010|01010010|01010010|01110010|r
0000000000000140|00001100|01001100|01001100|01101100|l
0000000000000004|00000100|01000100|01000100|01100100|d

【讨论】:

    【解决方案3】:

    有趣!

    可见的标准 ASCII 字符范围为 32 到 127。

    这就是你看到 32 和 95 (127 - 32) 的原因。

    其实这里每个字符都映射为5位,(你可以找到每个字符的5位组合是什么),然后将所有位串联起来形成一个大数。

    正长整数是 63 位数字,大到足以容纳 12 个字符的加密形式。所以它足够容纳Hello word,但对于较大的文本,您应该使用更大的数字,甚至是 BigInteger。


    在一个应用程序中,我们希望通过 SMS 传输可见的英文字符、波斯文字符和符号。如您所见,有 32 (number of Persian chars) + 95 (number of English characters and standard visible symbols) = 127 可能的值,可以用 7 位表示。

    我们将每个 UTF-8(16 位)字符转换为 7 位,并获得超过 56% 的压缩率。因此,我们可以在相同数量的 SMS 中发送长度为两倍的文本。 (不知何故,这里发生了同样的事情)。

    【讨论】:

    • OP 的代码中还有很多内容。例如,这并不能真正解释 | 64 在做什么。
    • @Amir:实际上 95 在那里,因为你需要一个空格字符。
    【解决方案4】:

    您得到的结果恰好是以下值的char 表示

    104 -> h
    101 -> e
    108 -> l
    108 -> l
    111 -> o
    32  -> (space)
    119 -> w
    111 -> o
    114 -> r
    108 -> l
    100 -> d
    

    【讨论】:

      【解决方案5】:

      您已将字符编码为 5 位值,并将其中的 11 个打包成 64 位长。

      (packedValues &gt;&gt; 5*i) &amp; 31 是第 i 个编码值,范围为 0-31。

      正如您所说,最困难的部分是对空间进行编码。小写英文字母在 Unicode(和 ascii 以及大多数其他编码)中占据连续范围 97-122,但空格是 32。

      为了克服这个问题,您使用了一些算术。 ((x+64)%95)+32x + 96 几乎相同(注意在这种情况下按位或等于加法),但是当 x=31 时,我们得到 32

      【讨论】:

        【解决方案6】:

        它出于类似的原因打印“hello world”:

        for (int k=1587463874; k>0; k>>=3)
             System.out.print((char) (100 + Math.pow(2,2*(((k&7^1)-1)>>3 + 1) + (k&7&3)) + 10*((k&7)>>2) + (((k&7)-7)>>3) + 1 - ((-(k&7^5)>>3) + 1)*80));
        

        但出于与此不同的原因:

        for (int k=2011378; k>0; k>>=2)
            System.out.print((char) (110 + Math.pow(2,2*(((k^1)-1)>>21 + 1) + (k&3)) - ((k&8192)/8192 + 7.9*(-(k^1964)>>21) - .1*(-((k&35)^35)>>21) + .3*(-((k&120)^120)>>21) + (-((k|7)^7)>>21) + 9.1)*10));
        

        【讨论】:

        • 你应该解释你在做什么,而不是发布另一个谜语
        • 我建议您花一些精力寻找一个欢迎提供有趣谜语的网站(也许是一些 Beta StackExchange?)。 Stack Overflow 是一个严格执行重点的问答网站。
        • @MarkoTopolnik 我讨厌生活在一个所有规则或重点都被严格执行以致绝不允许任何例外的世界。更不用说 SO 上有无数这样的例外。
        • 我也愿意,但在很大程度上,SO 就是这样一个世界。当然,即使在这里也有例外,但他们不欢迎
        • 另外 15 位分享了 Alexandr 的观点。正如下面评论的那样,您指出问题本身不适合 SO,这是正确的。
        【解决方案7】:

        没有Oracle 标签,很难看到这个问题。积极的赏金把我带到了这里。我希望这个问题也有其他相关的技术标签:-(

        我主要和Oracle database一起工作,所以我会使用一些Oracle的知识来解释和解释:-)

        让我们将数字4946144450195624 转换为binary。为此,我使用了一个名为 dec2bin 的小型 function,即 decimal-to-binary

        SQL> CREATE OR REPLACE FUNCTION dec2bin (N in number) RETURN varchar2 IS
          2    binval varchar2(64);
          3    N2     number := N;
          4  BEGIN
          5    while ( N2 > 0 ) loop
          6       binval := mod(N2, 2) || binval;
          7       N2 := trunc( N2 / 2 );
          8    end loop;
          9    return binval;
         10  END dec2bin;
         11  /
        
        Function created.
        
        SQL> show errors
        No errors.
        SQL>
        

        让我们用函数来获取二进制值-

        SQL> SELECT dec2bin(4946144450195624) FROM dual;
        
        DEC2BIN(4946144450195624)
        --------------------------------------------------------------------------------
        10001100100100111110111111110111101100011000010101000
        
        SQL>
        

        现在要注意的是5-bit 转换。从右到左开始分组,每组 5 个数字。我们得到:-

        100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
        

        我们最终将只剩下 3 个数字,他在右边结束。因为,我们在二进制转换中总共有 53 位。

        SQL> SELECT LENGTH(dec2bin(4946144450195624)) FROM dual;
        
        LENGTH(DEC2BIN(4946144450195624))
        ---------------------------------
                                       53
        
        SQL>
        

        hello world 总共有 11 个字符(包括空格),所以我们需要在最后一个组中添加 2 位,分组后我们只剩下 3 位.

        所以,现在我们有了:-

        00100|01100|10010|01111|10111|11111|01111|01100|01100|00101|01000
        

        现在,我们需要将其转换为 7 位 ascii 值。对于字符很简单,我们只需要设置第 6 位和第 7 位。将11 添加到左侧上方的每个 5 位组中。

        这给出了:-

        1100100|1101100|1110010|1101111|1110111|1111111|1101111|1101100|1101100|1100101|1101000
        

        让我们解释一下二进制值,我将使用binary to decimal conversion function

        SQL> CREATE OR REPLACE FUNCTION bin2dec (binval in char) RETURN number IS
          2    i                 number;
          3    digits            number;
          4    result            number := 0;
          5    current_digit     char(1);
          6    current_digit_dec number;
          7  BEGIN
          8    digits := length(binval);
          9    for i in 1..digits loop
         10       current_digit := SUBSTR(binval, i, 1);
         11       current_digit_dec := to_number(current_digit);
         12       result := (result * 2) + current_digit_dec;
         13    end loop;
         14    return result;
         15  END bin2dec;
         16  /
        
        Function created.
        
        SQL> show errors;
        No errors.
        SQL>
        

        让我们看看每个二进制值 -

        SQL> set linesize 1000
        SQL>
        SQL> SELECT bin2dec('1100100') val,
          2    bin2dec('1101100') val,
          3    bin2dec('1110010') val,
          4    bin2dec('1101111') val,
          5    bin2dec('1110111') val,
          6    bin2dec('1111111') val,
          7    bin2dec('1101111') val,
          8    bin2dec('1101100') val,
          9    bin2dec('1101100') val,
         10    bin2dec('1100101') val,
         11    bin2dec('1101000') val
         12  FROM dual;
        
               VAL        VAL        VAL        VAL        VAL        VAL        VAL        VAL        VAL     VAL           VAL
        ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
               100        108        114        111        119        127        111        108        108     101           104
        
        SQL>
        

        让我们看看它们是什么字符:-

        SQL> SELECT chr(bin2dec('1100100')) character,
          2    chr(bin2dec('1101100')) character,
          3    chr(bin2dec('1110010')) character,
          4    chr(bin2dec('1101111')) character,
          5    chr(bin2dec('1110111')) character,
          6    chr(bin2dec('1111111')) character,
          7    chr(bin2dec('1101111')) character,
          8    chr(bin2dec('1101100')) character,
          9    chr(bin2dec('1101100')) character,
         10    chr(bin2dec('1100101')) character,
         11    chr(bin2dec('1101000')) character
         12  FROM dual;
        
        CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER CHARACTER
        --------- --------- --------- --------- --------- --------- --------- --------- --------- --------- ---------
        d         l         r         o         w         ⌂         o         l         l         e         h
        
        SQL>
        

        那么,我们在输出中得到了什么?

        d l r o w ⌂ o l l e h

        那是 hello⌂world 反过来。唯一的问题是空格。 @higuaro 在他的回答中很好地解释了原因。老实说,在我看到他的回答中给出的解释之前,我第一次尝试自己无法解释空间问题。

        【讨论】:

          【解决方案8】:

          我发现代码翻译成PHP后更容易理解,如下:

          <?php
          
          $result=0;
          $bignum = 4946144450195624;
          for (; $bignum > 0; $bignum >>= 5){
              $result = (( $bignum & 31 | 64) % 95) + 32;
              echo chr($result);
          }
          

          live code

          【讨论】:

            【解决方案9】:

            out.println((char) (((l & 31 | 64) % 95) + 32 / 1002439 * 1002439));

            让它大写:3

            【讨论】:

            • 考虑添加一些关于你在做什么以及为什么做的解释。
            猜你喜欢
            • 1970-01-01
            • 2018-03-11
            • 1970-01-01
            • 2015-05-04
            • 1970-01-01
            • 1970-01-01
            • 2020-03-24
            • 2015-12-04
            • 2018-10-31
            相关资源
            最近更新 更多