Función de código de máquina x86 de 32 bits, 21 bytes
Función de código de máquina x86-64, 22 bytes
El ahorro de 1B en modo de 32 bits requiere el uso de separator = filler-1, por ejemplo, fill=0
y sep=/
. La versión de 22 bytes puede usar una elección arbitraria de separador y relleno.
Esta es la versión de 21 bytes, con input-separator = \n
(0xa), output-filler = 0
, output-separator = /
= filler-1. Estas constantes se pueden cambiar fácilmente.
; see the source for more comments
; RDI points to the output buffer, RSI points to the src string
; EDX holds the base
; This is the 32-bit version.
; The 64-bit version is the same, but the DEC is one byte longer (or we can just mov al,output_separator)
08048080 <str_exp>:
8048080: 6a 01 push 0x1
8048082: 59 pop ecx ; ecx = 1 = base**0
8048083: ac lods al,BYTE PTR ds:[esi] ; skip the first char so we don't do too many multiplies
; read an input row and accumulate base**n as we go.
08048084 <str_exp.read_bar>:
8048084: 0f af ca imul ecx,edx ; accumulate the exponential
8048087: ac lods al,BYTE PTR ds:[esi]
8048088: 3c 0a cmp al,0xa ; input_separator = newline
804808a: 77 f8 ja 8048084 <str_exp.read_bar>
; AL = separator or terminator
; flags = below (CF=1) or equal (ZF=1). Equal also implies CF=0 in this case.
; store the output row
804808c: b0 30 mov al,0x30 ; output_filler
804808e: f3 aa rep stos BYTE PTR es:[edi],al ; ecx bytes of filler
8048090: 48 dec eax ; mov al,output_separator
8048091: aa stos BYTE PTR es:[edi],al ;append delim
; CF still set from the inner loop, even after DEC clobbers the other flags
8048092: 73 ec jnc 8048080 <str_exp> ; new row if this is a separator, not terminator
8048094: c3 ret
08048095 <end_of_function>
; 0x95 - 0x80 = 0x15 = 21 bytes
La versión de 64 bits es 1 byte más larga, utilizando un DEC de 2 bytes o un mov al, output_separator
. Aparte de eso, el código de máquina es el mismo para ambas versiones, pero algunos nombres de registro cambian (por ejemplo, en rcx
lugar de ecx
en el pop
).
Ejemplo de salida al ejecutar el programa de prueba (base 3):
$ ./string-exponential $'.\n..\n...\n....' $(seq 3);echo
000/000000000/000000000000000000000000000/000000000000000000000000000000000000000000000000000000000000000000000000000000000/
algoritmo :
Pase sobre la entrada, haciendo exp *= base
para cada char de relleno. En los delimitadores y el byte cero de terminación, agregue exp
bytes de relleno y luego un separador a la cadena de salida y restablezca a exp=1
. Es muy conveniente garantizar que la entrada no termine con una nueva línea y un terminador.
En la entrada, cualquier valor de byte por encima del separador (comparación sin signo) se trata como relleno, y cualquier valor de byte por debajo del separador se trata como un marcador de fin de cadena. (La comprobación explícita de un byte cero requeriría una test al,al
ramificación adicional frente a la ramificación en las marcas establecidas por el bucle interno).
Las reglas solo permiten un separador final cuando se trata de una nueva línea final. Mi implementación siempre agrega el separador. Para obtener el ahorro de 1B en modo de 32 bits, esa regla requiere separador = 0xa ( '\n'
ASCII LF = salto de línea), relleno = 0xb ( '\v'
ASCII VT = pestaña vertical). Eso no es muy amigable para los humanos, pero satisface la letra de la ley. (Puede realizar un hexdump o
tr $'\v' x
la salida para verificar que funciona, o cambiar la constante para que el separador de salida y el relleno sean imprimibles. También noté que las reglas parecen requerir que pueda aceptar entradas con el mismo relleno / sep que usa para la salida , pero no veo nada que ganar al romper esa regla).
Fuente NASM / YASM. Compile como código de 32 o 64 bits, usando las %if
cosas incluidas con el programa de prueba o simplemente cambie rcx a ecx.
input_separator equ 0xa ; `\n` in NASM syntax, but YASM doesn't do C-style escapes
output_filler equ '0' ; For strict rules-compliance, needs to be input_separator+1
output_separator equ output_filler-1 ; saves 1B in 32-bit vs. an arbitrary choice
;; Using output_filler+1 is also possible, but isn't compatible with using the same filler and separator for input and output.
global str_exp
str_exp: ; void str_exp(char *out /*rdi*/, const char *src /*rsi*/,
; unsigned base /*edx*/);
.new_row:
push 1
pop rcx ; ecx=1 = base**0
lodsb ; Skip the first char, since we multiply for the separator
.read_bar:
imul ecx, edx ; accumulate the exponential
lodsb
cmp al, input_separator
ja .read_bar ; anything > separator is treated as filler
; AL = separator or terminator
; flags = below (CF=1) or equal (ZF=1). Equal also implies CF=0, since x-x doesn't produce carry.
mov al, output_filler
rep stosb ; append ecx bytes of filler to the output string
%if output_separator == output_filler-1
dec eax ; saves 1B in the 32-bit version. Use dec even in 64-bit for easier testing
%else
mov al, output_separator
%endif
stosb ; append the delimiter
; CF is still set from the .read_bar loop, even if DEC clobbered the other flags
; JNC/JNB here is equivalent to JE on the original flags, because we can only be here if the char was below-or-equal the separator
jnc .new_row ; separator means more rows, else it's a terminator
; (f+s)+f+ full-match guarantees that the input doesn't end with separator + terminator
ret
La función sigue el x86-64 SystemV ABI, con firma
void str_exp(char *out /*rdi*/, const char *src /*rsi*/, unsigned base /*edx*/);
Solo informa al llamante de la longitud de la cadena de salida al dejarle un puntero de un paso al finalrdi
, por lo que podría considerar este el valor de retorno en un no -convención de llamadas estándar.
xchg eax,edi
Costaría 1 o 2 bytes ( ) devolver el puntero final en eax o rax. (Si usa el x32 ABI, se garantiza que los punteros sean de solo 32 bits; de lo contrario, tenemos que usarlos xchg rax,rdi
en caso de que la persona que llama pase un puntero a un búfer fuera de los 32 bits bajos). No incluí esto en la versión que estoy publicar porque hay soluciones que la persona que llama puede usar sin obtener el valor derdi
, por lo que podría llamarlo desde C sin envoltorio.
Ni siquiera terminamos en nulo la cadena de salida ni nada, por lo que solo termina en nueva línea. Se necesitarían 2 bytes para arreglar eso: xchg eax,ecx / stosb
(rcx es cero desde rep stosb
).
Las formas de averiguar la longitud de la cadena de salida son:
- rdi apunta al final de la cadena al regresar (para que la persona que llama pueda hacer len = end-start)
- la persona que llama puede saber cuántas filas había en la entrada y contar nuevas líneas
- la persona que llama puede usar un gran búfer a cero y
strlen()
luego.
No son bonitas ni eficientes (excepto por usar el valor de retorno RDI de una persona que llama), pero si lo desea, no llame a las funciones de golf asm desde C.: P
Limitaciones de tamaño / rango
El tamaño máximo de la cadena de salida solo está limitado por las limitaciones de espacio de direcciones de memoria virtual. (Principalmente, ese hardware x86-64 actual solo admite 48 bits significativos en direcciones virtuales, divididas por la mitad porque firman-extienden en lugar de cero-extienden. Vea el diagrama en la respuesta vinculada ).
Cada fila solo puede tener un máximo de 2 ** 32-1 bytes de relleno, ya que acumulo el exponencial en un registro de 32 bits.
La función funciona correctamente para bases de 0 a 2 ** 32 - 1. (Correcto para la base 0 es 0 ^ x = 0, es decir, solo líneas en blanco sin bytes de relleno. Correcto para la base 1 es 1 ^ x = 1, así que siempre 1 relleno por línea.)
También es increíblemente rápido en Intel IvyBridge y posterior, especialmente para grandes filas que se escriben en la memoria alineada. rep stosb
es una implementación óptima memset()
para grandes recuentos con punteros alineados en CPU con la función ERMSB . por ejemplo, 180 ** 4 es 0.97GB, y toma 0.27 segundos en mi i7-6700k Skylake (con ~ 256k fallas de página suaves) para escribir en / dev / null. (En Linux, el controlador de dispositivo para / dev / null no copia los datos en ninguna parte, simplemente regresa. Por lo tanto, todo el tiempo está en las rep stosb
fallas de página suaves que se activan al tocar la memoria por primera vez. Es desafortunadamente no se usan páginas enormes transparentes para la matriz en el BSS. Probablemente una madvise()
llamada al sistema lo aceleraría).
Programa de prueba :
Cree un binario estático y ejecútelo como ./string-exponential $'#\n##\n###' $(seq 2)
para la base 2. Para evitar implementar un atoi
, lo utiliza base = argc-2
. (Los límites de longitud de la línea de comando evitan probar bases ridículamente grandes).
Este contenedor funciona para cadenas de salida de hasta 1 GB. (Solo hace una sola llamada al sistema write () incluso para cadenas gigantescas, pero Linux lo admite incluso para escribir en tuberías). Para contar caracteres, canalice wc -c
o use strace ./foo ... > /dev/null
para ver el argumento de la llamada al sistema de escritura.
Esto aprovecha el valor de retorno RDI para calcular la longitud de la cadena como un argumento para write()
.
;;; Test program that calls it
;;; Assembles correctly for either x86-64 or i386, using the following %if stuff.
;;; This block of macro-stuff also lets us build the function itself as 32 or 64-bit with no source changes.
%ifidn __OUTPUT_FORMAT__, elf64
%define CPUMODE 64
%define STACKWIDTH 8 ; push / pop 8 bytes
%define PTRWIDTH 8
%elifidn __OUTPUT_FORMAT__, elfx32
%define CPUMODE 64
%define STACKWIDTH 8 ; push / pop 8 bytes
%define PTRWIDTH 4
%else
%define CPUMODE 32
%define STACKWIDTH 4 ; push / pop 4 bytes
%define PTRWIDTH 4
%define rcx ecx ; Use the 32-bit names everywhere, even in addressing modes and push/pop, for 32-bit code
%define rsi esi
%define rdi edi
%define rsp esp
%endif
global _start
_start:
mov rsi, [rsp+PTRWIDTH + PTRWIDTH*1] ; rsi = argv[1]
mov edx, [rsp] ; base = argc
sub edx, 2 ; base = argc-2 (so it's possible to test base=0 and base=1, and so ./foo $'xxx\nxx\nx' $(seq 2) has the actual base in the arg to seq)
mov edi, outbuf ; output buffer. static data is in the low 2G of address space, so 32-bit mov is fine. This part isn't golfed, though
call str_exp ; str_exp(outbuf, argv[1], argc-2)
; leaves RDI pointing to one-past-the-end of the string
mov esi, outbuf
mov edx, edi
sub edx, esi ; length = end - start
%if CPUMODE == 64 ; use the x86-64 ABI
mov edi, 1 ; fd=1 (stdout)
mov eax, 1 ; SYS_write (Linux x86-64 ABI, from /usr/include/asm/unistd_64.h)
syscall ; write(1, outbuf, length);
xor edi,edi
mov eax,231 ; exit_group(0)
syscall
%else ; Use the i386 32-bit ABI (with legacy int 0x80 instead of sysenter for convenience)
mov ebx, 1
mov eax, 4 ; SYS_write (Linux i386 ABI, from /usr/include/asm/unistd_32.h)
mov ecx, esi ; outbuf
; 3rd arg goes in edx for both ABIs, conveniently enough
int 0x80 ; write(1, outbuf, length)
xor ebx,ebx
mov eax, 1
int 0x80 ; 32-bit ABI _exit(0)
%endif
section .bss
align 2*1024*1024 ; hugepage alignment (32-bit uses 4M hugepages, but whatever)
outbuf: resb 1024*1024*1024 * 1
; 2GB of code+data is the limit for the default 64-bit code model.
; But with -m32, a 2GB bss doesn't get mapped, so we segfault. 1GB is plenty anyway.
Este fue un desafío divertido que se prestó muy bien a asm, especialmente a las operaciones de cadena x86 . Las reglas están bien diseñadas para evitar tener que manejar una nueva línea y luego un terminador al final de la cadena de entrada.
Un exponencial con multiplicación repetida es como multiplicar con suma repetida, y de todos modos necesitaba hacer un bucle para contar los caracteres en cada fila de entrada.
Pensé en usar un operando mul
o en imul
lugar del más largo imul r,r
, pero su uso implícito de EAX entraría en conflicto con LODSB.
También probé SCASB en lugar de cargar y comparar , pero necesitaba xchg esi,edi
antes y después del ciclo interno, porque SCASB y STOSB usan EDI. (Por lo tanto, la versión de 64 bits tiene que usar el x32 ABI para evitar truncar los punteros de 64 bits).
Evitar STOSB no es una opción; nada más es tan corto. Y la mitad del beneficio de usar SCASB es que AL = relleno después de abandonar el bucle interno, por lo que no necesitamos ninguna configuración para REP STOSB.
SCASB se compara en la otra dirección de lo que había estado haciendo, por lo que necesitaba revertir las comparaciones.
Mi mejor intento con xchg y scasb. Funciona, pero no es más corto. ( Código de 32 bits, usando el truco inc
/ dec
para cambiar el relleno al separador ).
; SCASB version, 24 bytes. Also experimenting with a different loop structure for the inner loop, but all these ideas are break-even at best
; Using separator = filler+1 instead of filler-1 was necessary to distinguish separator from terminator from just CF.
input_filler equ '.' ; bytes below this -> terminator. Bytes above this -> separator
output_filler equ input_filler ; implicit
output_separator equ input_filler+1 ; ('/') implicit
8048080: 89 d1 mov ecx,edx ; ecx=base**1
8048082: b0 2e mov al,0x2e ; input_filler= .
8048084: 87 fe xchg esi,edi
8048086: ae scas al,BYTE PTR es:[edi]
08048087 <str_exp.read_bar>:
8048087: ae scas al,BYTE PTR es:[edi]
8048088: 75 05 jne 804808f <str_exp.bar_end>
804808a: 0f af ca imul ecx,edx ; exit the loop before multiplying for non-filler
804808d: eb f8 jmp 8048087 <str_exp.read_bar> ; The other loop structure (ending with the conditional) would work with SCASB, too. Just showing this for variety.
0804808f <str_exp.bar_end>:
; flags = below if CF=1 (filler<separator), above if CF=0 (filler<terminator)
; (CF=0 is the AE condition, but we can't be here on equal)
; So CF is enough info to distinguish separator from terminator if we clobber ZF with INC
; AL = input_filler = output_filler
804808f: 87 fe xchg esi,edi
8048091: f3 aa rep stos BYTE PTR es:[edi],al
8048093: 40 inc eax ; output_separator
8048094: aa stos BYTE PTR es:[edi],al
8048095: 72 e9 jc 8048080 <str_exp> ; CF is still set from the inner loop
8048097: c3 ret
Para una entrada de ../.../.
, produce ..../......../../
. No me voy a molestar en mostrar un hexdump de la versión con separator = newline.
"" <> "#"~Table~#
es 3 bytes más corto que"#"~StringRepeat~#
, probablemente golfable aún más.Japt , 7 bytes
Toma el gráfico como una matriz de cadenas con
"
el relleno y la base como un entero.Pruébalo en línea
Agregue
}R
al final para tomar el gráfico como una cadena separada de nueva línea. ( Pruébalo )Explicación
fuente
MATL ,
1411 bytesDelimitador es espacio. Relleno es cualquier personaje que no sea el espacio.
Pruébalo en línea!
Explicación
fuente
Haskell ,
3733 bytes4 bytes recortados gracias a sudee
Descripción:
Decepcionantemente, esto es
2 bytesmucho más corto que la versión de punto libre más difícil de leer:fuente
replicate(b^length x)'#'
con'#'<$[1..b^length x]
.ReRegex , 105 bytes
Pruébalo en línea!
ReRegex es como el primo feo de Retina que da todo el esfuerzo a las expresiones regulares, en lugar de tener sus propios operadores sofisticados.
Por supuesto, también tiene
#import
y#input
para guardar tanto la entrada de codificación como la reescritura de las mismas expresiones una y otra vez.Explicado.
Toma entrada en forma de:
en STDIN, y da salida como
En primer lugar, el programa importa la Biblioteca de Matemáticas , que por supuesto está completamente escrita en ReRegex. La mayor parte de esto es entonces tres expresiones regulares.
El primero coincide con nuestra base de entrada y busca una línea de unario después. luego, reemplaza esa línea con
;$1^d<$4>
, que es la base, al poder del (en decimal) unario. La biblioteca matemática maneja la conversión de base y el exponente. UNA ; se coloca al inicio para identificarlo luego como terminado.El segundo, coincide con la base, luego muchas líneas de;, antes de terminar. Si esto coincide con todo, se corta la base. dejando uf con solo las respuestas y
;
s.El último, coincide solo unario al inicio, opcionalmente, luego una
;
respuesta. Luego transforma esa respuesta en unario nuevamente, sin el;
.Debido a que la salida no coincide con la primera expresión regular, no se repite infinitamente, por lo que nuestra solución se genera.
fuente
Python 2 ,
5236 bytesLa entrada y la salida se toman como matrices de cadenas.
#
es el rellenoPruébalo en línea!
fuente
Röda , 19 bytes
Pruébalo en línea!
Toma una matriz como entrada y devuelve una secuencia de valores como salida.
Explicación
fuente
Haskell , 32 bytes
Pruébalo en línea! Ejemplo de uso:
f 3 ["##","#","###","#"]
devoluciones["#########","###","###########################","###"]
.Úselo
mapM putStrLn $ f 3 ["##","#","###","#"]
para obtener un resultado visualmente más agradable:fuente
sum[sum[]^sum[],sum[]^sum[]]
.05AB1E , 9 bytes
Las barras están separadas por espacios, el carácter de salida es el mismo que el carácter de entrada.
Pruébalo en línea!
fuente
PHP, 69 bytes
Pruébalo en línea!
fuente
[str_pad]."\n"
lugar de"\n".[str_pad]
arreglar esto (+1 byte). Además, puede suponer cuál es el relleno, por lo que podría guardar dos bytes$l[0]
cambiándolo a"#"
.Jalea , 7 bytes
Un enlace monádico que toma y devuelve listas de las barras (listas de caracteres, cadenas AKA), el carácter de relleno es flexible.
Pruébalo en línea! (el pie de página embellece la lista resultante uniendo sus elementos con nuevas líneas).
¿Cómo?
Alternativa de 7 bytes:
ṁ"L€*@¥
- obtenga la longitud de cada barra (L€
), aumentebase
a esa potencia (*@
), luego comprima ("
) la lista y aplique la diada de molde (ṁ
) entre las dos.fuente
Ruby , 29 bytes
Pruébalo en línea!
Sí, descubrí la semana pasada que
?#
produce una cadena de un solo carácter. No tengo idea de por qué existe esta función, pero estoy seguro de que lo hace.fuente
?X
operador, dondeX
hay algún carácter, es el operador "obtener la representación predeterminada de este carácter". En Ruby <1.9, devolvería el punto de código Unicode del carácter, porque así es como se definieron los caracteres, pero ahora devuelve una cadena que contiene el carácter. Es parte de un cambio general hacia un manejo Unicode más consistente en Ruby.?X
se usa? Muchas convenciones más extravagantes de Ruby, como la gran cantidad de$
variables, existen debido a la familiaridad con Perl.JavaScript (ES8), 39 bytes
Toma la base como un número entero y el gráfico como una matriz de cadenas con cualquier carácter como relleno, utilizando la sintaxis de curry.
Intentalo
Alternativa, 49 bytes
Esta versión toma el gráfico como una cadena separada de nueva línea, nuevamente con cualquier carácter como relleno.
fuente
m
bandera en la expresión regular, por defecto.
no coincide con las nuevas líneas.Mathematica, 86 bytes
entrada
fuente
Octava, 42 bytes
* La entrada / salida de la cadena no coincide completamente con la expresión regular, pero es posible entender qué barra es cuál.
Una función toma como base de entrada
b
y una matriz 2D de caracteres ques
contiene"!"
y la salida también es una matriz de caracteres.Pruébalo en línea!
Explicación:
fuente
CJam, 20 bytes
Formato de entrada
Se requiere entrada en el siguiente formato:
fuente
Carbón , 11 bytes
Pruébalo en línea! El enlace es a la versión detallada del código. I / O es como una lista de cadenas de
-
caracteres (tenga en cuenta que necesita una línea vacía para terminar la lista).fuente
V , 27 bytes
La idea básica es que agreguemos un
'
a cada fila (n ^ 0), y luego para cada uno#
reemplazamos el'
s en la línea con[input] * '
. Al final cambié todo el'
de#
nuevoPruébalo en línea!
fuente
R , 35 bytes
Una función anónima que toma las cadenas como una lista y la base, y devuelve una lista de cadenas.
Pruébalo en línea!
fuente
05AB1E , 10 bytes
El carácter del archivador es
1
y el delimitador es una nueva línea.Pruébalo en línea!
fuente
Retina , 62 bytes
Pruébalo en línea! Después de todo, un gráfico de barras es solo una lista de números unarios. Toma la entrada como el gráfico (usando
#
s) seguido de la base en decimal (para evitar confusiones). Explicación: El primer reemplazo de prefijos 1 y la base de cada línea del gráfico. El segundo reemplazo luego multiplica el primer número en cada línea por el segundo, siempre que el tercer número sea distinto de cero. El tercer reemplazo luego disminuye el tercer número en cada línea. Estos dos reemplazos se repiten hasta que el tercer número se convierte en cero. El último reemplazo elimina la base en todas partes, dejando el resultado deseado.fuente
Convexo , 9 bytes
Pruébalo en línea!
fuente
Alice , 23 bytes
Pruébalo en línea!
No solo no soy una persona que deja de fumar, sino que estoy tan comprometido a hacer el punto correctamente que uso
!
como relleno. Eso sin duda llamará la atención del lector.Explicación
Los espejos se retienen en esta explicación para que quede más claro cuando el programa cambia entre los modos cardinal y ordinal.
fuente
Perl 6 , 26 bytes
La lista de cadenas de entrada está en el primer parámetro,
@^a
. El segundo parámetro$^b
es la base. Se devuelve una lista de cadenas de salida.fuente