¿Cómo se imprime "hola mundo"?

163

Descubrí esta rareza:

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

Salida:

hello world

¿Como funciona esto?

Bohemio
fuente
14
Quiero decir que puedes resolver esto tú mismo.
Sotirios Delimanolis
30
Si. Lo admito ... estoy pescando sombreros :)
Bohemio
66
Creo que he visto esta pregunta aquí antes ..
Zavior
66
@Oli Debería haber un sombrero para eso.
Sotirios Delimanolis
12
Preguntas como esta, que no mejoran la base de datos pero existen únicamente como clickbait, son una forma segura de cancelar el juego Hat en el futuro. Por favor, no arruines el juego golpeándolo.
Blazemonger

Respuestas:

256

El número se 4946144450195624ajusta a 64 bits, su representación binaria es:

 10001100100100111110111111110111101100011000010101000

El programa decodifica un carácter para cada grupo de 5 bits, de derecha a izquierda

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

Codificación de 5 bits

Para 5 bits, es posible representar 2⁵ = 32 caracteres. El alfabeto inglés contiene 26 letras, esto deja espacio para 32 - 26 = 6 símbolos aparte de las letras. Con este esquema de codificación, puede tener las 26 letras inglesas (un caso) y 6 símbolos (siendo el espacio entre ellos).

Descripción del algoritmo

En >>= 5el bucle for salta de un grupo a otro, luego el grupo de 5 bits se aísla ANDing el número con la máscara 31₁₀ = 11111₂en la oraciónl & 31

Ahora el código asigna el valor de 5 bits a su correspondiente carácter ascii de 7 bits. Esta es la parte difícil, verifique las representaciones binarias para las letras minúsculas del alfabeto en la siguiente tabla:

  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

Aquí puede ver que los caracteres ascii que queremos mapear comienzan con el conjunto de 7 y 6 bits ( 11xxxxx₂) (excepto el espacio, que solo tiene el 6to bit activado ), puede ORcodificar con 5 bits con 96(96₁₀ = 1100000₂ ) y eso debería ser suficiente para hacer el mapeo, pero eso no funcionaría para el espacio (¡maldito espacio!)

Ahora sabemos que se debe tener especial cuidado al procesar el espacio al mismo tiempo que los otros personajes. Para lograr esto, el código activa el séptimo bit (pero no el sexto) en el grupo extraído de 5 bits con un OR 6464₁₀ = 1000000₂ ( l & 31 | 64).

Hasta ahora, el grupo de 5 bits tiene la forma: 10xxxxx₂(el espacio sería 1011111₂ = 95₁₀). Si podemos asignar espacio a 0otros valores que no afectan, entonces podemos activar el sexto bit y eso debería ser todo. Esto es lo que mod 95viene a jugar la parte, el espacio es 1011111₂ = 95₁₀, usando la operación de modulación (l & 31 | 64) % 95)solo vuelve el espacio 0, y después de esto, el código activa el sexto bit al agregarlo 32₁₀ = 100000₂ al resultado anterior,((l & 31 | 64) % 95) + 32) transformando el valor de 5 bits en un ascii válido personaje

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

El siguiente código realiza el proceso inverso, dada una cadena en minúscula (máximo 12 caracteres), devuelve el valor de 64 bits que podría usarse con el código del OP:

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);
    }
}    
higuaro
fuente
11
Esta respuesta no deja misterio. Más bien, piensa por ti.
77
la respuesta es aún más difícil que la pregunta: D
Yazan
1
La explicación es mucho más limpia :)
Prashant
40

Agregar algo de valor a las respuestas anteriores. El siguiente script maravilloso imprime valores intermedios.

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

Aquí está

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
Jayan
fuente
26

¡Interesante!

Los caracteres ASCII estándar que están visibles están en el rango de 32 a 127.

Es por eso que ves 32 y 95 (127 - 32) allí.

De hecho, cada carácter se asigna a 5 bits aquí (puede encontrar la combinación de 5 bits para cada carácter), y luego todos los bits se concatenan para formar un gran número.

Los largos positivos son números de 63 bits, lo suficientemente grandes como para contener una forma cifrada de 12 caracteres. Por lo tanto, es lo suficientemente grande como para contenerlo Hello word, pero para textos más grandes usará números más grandes, o incluso un BigInteger.


En una aplicación, queríamos transferir caracteres visibles en inglés, caracteres persas y símbolos por SMS. Como puede ver, hay 32 (number of Persian chars) + 95 (number of English characters and standard visible symbols) = 127valores posibles, que se pueden representar con 7 bits.

Convertimos cada carácter UTF-8 (16 bits) a 7 bits, y ganamos más del 56% de relación de compresión. Entonces podríamos enviar mensajes de texto con el doble de longitud en la misma cantidad de SMS. (De alguna manera sucedió lo mismo aquí).

Amir Pashazadeh
fuente
Están sucediendo muchas más cosas en el código de OP. Por ejemplo, esto realmente no explica lo que | 64está haciendo.
Ted Hopp
1
@Amir: en realidad 95 está ahí porque necesitas obtener un carácter de espacio.
Abeja
17

Obtendrá un resultado que charrepresenta los valores inferiores

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

Ha codificado caracteres como valores de 5 bits y ha empaquetado 11 de ellos en una longitud de 64 bits.

(packedValues >> 5*i) & 31 es el i-ésimo valor codificado con un rango de 0-31.

La parte difícil, como dices, es codificar el espacio. Las letras inglesas en minúsculas ocupan el rango contiguo 97-122 en Unicode (y ascii, y la mayoría de las otras codificaciones), pero el espacio es 32.

Para superar esto, usaste algo de aritmética. ((x+64)%95)+32es casi lo mismo que x + 96(tenga en cuenta cómo OR a nivel de bits es equivalente a la suma, en este caso), pero cuando x = 31, obtenemos 32.

Aleksandr Dubinsky
fuente
6

Imprime "hola mundo" por una razón similar que esto hace:

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

pero por una razón algo diferente a esto:

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));
גלעד ברקן
fuente
18
Deberías explicar lo que estás haciendo, en lugar de publicar otro acertijo
Aleksandr Dubinsky
1
Le sugiero que invierta un poco de esfuerzo en encontrar un sitio (¿tal vez un poco de Beta StackExchange?) Donde sea divertido contribuir con acertijos divertidos. Stack Overflow es un sitio de preguntas y respuestas con un enfoque estrictamente obligatorio.
Marko Topolnik el
1
@MarkoTopolnik Odiaría vivir en un mundo donde todas las reglas o el enfoque se aplicaran tan estrictamente como para nunca permitir excepciones. Sin mencionar que hay innumerables excepciones en SO.
עדלעד ברקן
1
Yo también lo haría, pero SO es un mundo tan excepcional. Claro que hay excepciones incluso aquí, pero no son bienvenidas .
Marko Topolnik
1
Otros 15 compartieron el sentimiento de Alexandr. Y tiene razón al señalar que la pregunta en sí misma es inapropiada para SO, como se comenta a continuación.
Marko Topolnik
3

Sin una Oracleetiqueta, era difícil ver esta pregunta. La generosidad activa me trajo aquí. Ojalá la pregunta tuviera otras etiquetas tecnológicas relevantes también :-(

Principalmente trabajo con ellos Oracle database, así que usaría algunos Oracleconocimientos para interpretar y explicar :-)

Vamos a convertir el número 4946144450195624a binary. Para eso utilizo un pequeño functionllamado dec2bin, es decir, decimal a binario .

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>

Usemos la función para obtener el valor binario:

SQL> SELECT dec2bin(4946144450195624) FROM dual;

DEC2BIN(4946144450195624)
--------------------------------------------------------------------------------
10001100100100111110111111110111101100011000010101000

SQL>

Ahora el truco es la 5-bitconversión. Comience a agrupar de derecha a izquierda con 5 dígitos en cada grupo. Obtenemos :-

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

Finalmente nos quedarían con solo 3 dígitos en el final a la derecha. Porque, tuvimos un total de 53 dígitos en la conversión binaria.

SQL> SELECT LENGTH(dec2bin(4946144450195624)) FROM dual;

LENGTH(DEC2BIN(4946144450195624))
---------------------------------
                               53

SQL>

hello worldel total tiene 11 caracteres (incluido el espacio), por lo que debemos agregar 2 bits al último grupo donde nos quedamos con solo 3 bits después de la agrupación.

Entonces, ahora tenemos: -

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

Ahora, necesitamos convertirlo a un valor ascii de 7 bits. Para los personajes es fácil, solo necesitamos establecer el 6to y 7mo bit. Agregue 11a cada grupo de 5 bits arriba a la izquierda.

Eso da :-

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

Interpretemos los valores binarios que usaré 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>

Veamos cada valor binario:

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>

Veamos qué personajes son: -

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>

Entonces, ¿qué obtenemos en la salida?

dlrow ⌂ olleh

Eso es hello⌂world a la inversa. El único problema es el espacio . Y la razón está bien explicada por @higuaro en su respuesta. Sinceramente, no pude interpretar el problema espacial por mí mismo en el primer intento, hasta que vi la explicación dada en su respuesta.

Lalit Kumar B
fuente
1

Encontré el código un poco más fácil de entender cuando se traduce a PHP, de la siguiente manera:

<?php

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

Ver código en vivo

slevy1
fuente
0

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

Para hacerlo mayúsculas: 3


fuente
1
considere agregar alguna explicación sobre lo que está haciendo y por qué.
fedorqui 'SO deja de dañar'