该如何打印“ hello world”?


163

我发现了这种奇怪之处:

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

输出:

hello world

这是如何运作的?


14
我的意思是您可以自己解决这个问题。
Sotirios Delimanolis 2013年

30
是。我承认...我是在钓鱼帽:)
波西米亚

6
我想我已经看到了这个问题之前..在这里问
Zavior

6
@Oli应该为此戴上帽子。
Sotirios Delimanolis 2013年

12
诸如此类的问题并不能改善数据库,而只能以clickbait的形式存在,这是将来取消Hat游戏的必然途径。请不要为之而死。
Blazemonger 2013年

Answers:


256

该数字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个符号(其中是空格)。

算法说明

>>= 5从组到组中的for循环跳跃,则5位组被分离安定与掩模的数目31₁₀ = 11111₂在句子l & 31

现在,代码将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使用9696₁₀ = 1100000₂)进行5位编码,并且应该足以进行映射,但是这对于空间(无效空间!)不起作用

现在我们知道必须特别注意与其他字符同时处理空间。为此,代码使用OR 64 64₁₀ = 1000000₂l & 31 | 64)将提取的5位组的第7位(而不是第6位)打开。

到目前为止,5位组的形式为:(10xxxxx₂空格为1011111₂ = 95₁₀)。如果我们可以将空间映射为不0影响其他值,则可以打开第6位,仅此而已。这是mod 95要发挥作用的部分,空格是1011111₂ = 95₁₀,使用mod操作,(l & 31 | 64) % 95)只有空格返回到0,然后,代码通过添加32₁₀ = 100000₂ 到先前的结果,((l & 31 | 64) % 95) + 32)将5位值转换为有效的ascii 来打开第6位。字符

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);
    }
}    

11
这个答案不容置疑。相反,它会为您提供帮助。

7
答案比问题还要难:D
Yazan 2014年

1
解释要干净得多:)
Prashant'1

40

为上述答案增加一些价值。遵循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

26

有趣!

可见的标准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作用。
Ted Hopp

1
@Amir:实际上是95,因为您需要获得一个空格字符。
蜜蜂

17

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

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

16

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

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

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

为了克服这个问题,您使用了一些算法。((x+64)%95)+32几乎与x + 96(在这种情况下,请注意按位OR等于加法)相同,但是当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));

18
您应该解释自己在做什么,而不是发布另一个谜语
Aleksandr Dubinsky 2013年

1
我建议您花些力气找到一个网站(也许有一些Beta StackExchange?),欢迎在此提供有趣的谜语。堆栈溢出是一个严格执行重点的问答网站。
Marko Topolnik 2014年

1
@MarkoTopolnik我讨厌生活在一个严格执行所有规则或焦点的世界,绝不允许任何例外。更不用说SO上有无数这样的例外。
גלעדברקן

1
我也愿意,但是SO在如此大的范围内是如此。当然,即使在这里也有例外,但是不受欢迎
Marko Topolnik,2014年

1
另外15个人分享了亚历山大的观点。正如您在下面的评论中所指出的那样,您正确地指出了该问题本身不适合SO。
Marko Topolnik

3

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

我主要与一起工作Oracle database,因此我将使用一些Oracle知识来解释和解释:-)

让我们将数字4946144450195624转换为binary。为此,我使用了一个function称为dec2bin 的小符号,即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个字符(包括空格),因此我们需要在分组后仅剩下3位的最后一组中添加2位。

所以,现在我们有:-

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>

那么,我们从输出中得到什么呢?

德罗·奥尔利

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


1

我发现将代码翻译成PHP时,代码稍微容易理解,如下所示:

<?php

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

查看实时代码


0

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

使其上限:3


1
考虑添加一些有关您在做什么以及原因的解释。
fedorqui'SO停止伤害
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.