Fonction de code machine x86 32 bits, 21 octets
Fonction de code machine x86-64, 22 octets
L'économie 1B en mode 32 bits nécessite l'utilisation de separator = filler-1, par exemple fill=0
et sep=/
. La version 22 octets peut utiliser un choix arbitraire de séparateur et de remplissage.
Il s'agit de la version à 21 octets, avec séparateur d'entrée = \n
(0xa), remplisseur de 0
sortie = , séparateur de sortie = /
= remplisseur-1. Ces constantes peuvent être facilement modifiées.
; 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 version 64 bits est plus longue de 1 octet, utilisant un DEC de 2 octets ou un mov al, output_separator
. En dehors de cela, le code machine est le même pour les deux versions, mais certains noms de registre changent (par exemple rcx
au lieu de ecx
dans pop
).
Exemple de résultat de l'exécution du programme de test (base 3):
$ ./string-exponential $'.\n..\n...\n....' $(seq 3);echo
000/000000000/000000000000000000000000000/000000000000000000000000000000000000000000000000000000000000000000000000000000000/
Algorithme :
Faites une boucle sur l'entrée, exp *= base
pour chaque caractère de remplissage. Sur les délimiteurs et l'octet zéro de fin, ajoutez des exp
octets de remplissage, puis un séparateur à la chaîne de sortie et réinitialisez-le exp=1
. Il est très pratique de garantir que l'entrée ne se termine pas à la fois par une nouvelle ligne et un terminateur.
En entrée, toute valeur d'octet au-dessus du séparateur (comparaison non signée) est traitée comme un remplissage et toute valeur d'octet au-dessous du séparateur est traitée comme un marqueur de fin de chaîne. (La vérification explicite d'un octet zéro prendrait un test al,al
branchement supplémentaire par rapport aux indicateurs définis par la boucle interne).
Les règles n'autorisent un séparateur de fin que lorsqu'il s'agit d'une nouvelle ligne de fin. Mon implémentation ajoute toujours le séparateur. Pour obtenir l'enregistrement 1B en mode 32 bits, cette règle nécessite un séparateur = 0xa ( '\n'
ASCII LF = saut de ligne), filler = 0xb ( '\v'
ASCII VT = tabulation verticale). Ce n'est pas très convivial pour l'homme, mais satisfait à la lettre de la loi. (Vous pouvez hexdump ou
tr $'\v' x
la sortie pour vérifier qu'elle fonctionne, ou changer la constante pour que le séparateur et le remplisseur de sortie soient imprimables. J'ai également remarqué que les règles semblent exiger qu'il puisse accepter l'entrée avec le même remplissage / sep qu'il utilise pour la sortie , mais je ne vois rien à gagner à enfreindre cette règle.).
Source NASM / YASM. Construisez en code 32 ou 64 bits, en utilisant les %if
éléments inclus avec le programme de test ou changez simplement rcx en 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 fonction suit l'ABI SystemV x86-64, avec signature.
void str_exp(char *out /*rdi*/, const char *src /*rsi*/, unsigned base /*edx*/);
Elle n'informe l'appelant que de la longueur de la chaîne de sortie en lui laissant un pointeur un-la-fin rdi
, vous pouvez donc considérer cela comme la valeur de retour dans un non - convention d'appel standard.
Cela coûterait 1 ou 2 octets ( xchg eax,edi
) pour retourner le pointeur de fin en eax ou rax. (Si vous utilisez l'ABI x32, les pointeurs sont garantis à seulement 32 bits, sinon nous devons utiliser xchg rax,rdi
au cas où l'appelant passe un pointeur vers un tampon en dehors des 32 bits bas.) Je n'ai pas inclus cela dans la version que je suis la publication car il existe des solutions de contournement que l'appelant peut utiliser sans obtenir la valeur rdi
, vous pouvez donc l'appeler à partir de C sans wrapper.
Nous ne terminons même pas null la chaîne de sortie ou quoi que ce soit, donc c'est seulement terminé par une nouvelle ligne. Il faudrait 2 octets pour corriger cela: xchg eax,ecx / stosb
(rcx est zéro à partir de rep stosb
.)
Les moyens de connaître la longueur de la chaîne de sortie sont les suivants:
- rdi pointe vers un bout de la fin de la chaîne au retour (pour que l'appelant puisse faire len = end-start)
- l'appelant peut simplement savoir combien de lignes étaient en entrée et compter les retours à la ligne
- l'appelant peut utiliser un grand tampon mis à zéro et
strlen()
ensuite.
Ils ne sont pas jolis ou efficaces (sauf pour utiliser la valeur de retour RDI d'un appelant asm), mais si vous le souhaitez, n'appelez pas les fonctions asm golfées de C.: P
Limites de taille / gamme
La taille maximale de la chaîne de sortie n'est limitée que par les limitations de l'espace d'adressage de la mémoire virtuelle. (Principalement, le matériel x86-64 actuel ne prend en charge que 48 bits significatifs dans les adresses virtuelles, divisés en deux car ils signent-étendent au lieu de zéro-étendent. Voir le diagramme dans la réponse liée .)
Chaque ligne ne peut avoir qu'un maximum de 2 ** 32 - 1 octets de remplissage, car j'accumule l'exponentielle dans un registre 32 bits.
La fonction fonctionne correctement pour les bases de 0 à 2 ** 32 - 1. (Correct pour la base 0 est 0 ^ x = 0, c'est-à-dire juste des lignes vides sans octets de remplissage. Correct pour la base 1 est 1 ^ x = 1, donc toujours 1 charge par ligne.)
Il est également incroyablement rapide sur Intel IvyBridge et versions ultérieures, en particulier pour les grandes lignes écrites dans la mémoire alignée. rep stosb
est une implémentation optimale de memset()
pour les grands nombres avec des pointeurs alignés sur les CPU avec la fonction ERMSB . Par exemple, 180 ** 4 fait 0,97 Go et prend 0,27 seconde sur mon i7-6700k Skylake (avec ~ 256 k de défauts de page) pour écrire dans / dev / null. (Sur Linux , le pilote de périphérique pour / dev / null ne copie pas les données partout, il retourne juste. Donc , tout le temps est en rep stosb
et les douces fautes de page qui se déclenche lorsque touchant la mémoire pour la première fois. Il est malheureusement, n'utilise pas d'énormes pages transparentes pour le tableau dans le BSS. Un madvise()
appel système pourrait probablement l' accélérer.)
Programme de test :
Construisez un binaire statique et exécutez comme ./string-exponential $'#\n##\n###' $(seq 2)
pour la base 2. Pour éviter d'implémenter un atoi
, il utilise base = argc-2
. (Les limites de longueur de ligne de commande empêchent de tester des bases ridiculement grandes.)
Ce wrapper fonctionne pour les chaînes de sortie jusqu'à 1 Go. (Il ne fait qu'un seul appel système write () même pour des chaînes gigantesques, mais Linux le prend en charge même pour l'écriture dans des tuyaux). Pour compter les caractères, canalisez wc -c
ou utilisez strace ./foo ... > /dev/null
pour voir l'argument de l'appel système d'écriture.
Cela tire parti de la valeur de retour RDI pour calculer la longueur de chaîne comme argument pour 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.
C'était un défi amusant qui se prêtait très bien à asm, en particulier aux opérations de chaîne x86 . Les règles sont bien conçues pour éviter d'avoir à gérer une nouvelle ligne puis un terminateur à la fin de la chaîne d'entrée.
Une exponentielle avec multiplication répétée est tout comme la multiplication avec addition répétée, et j'avais besoin de boucler pour compter les caractères dans chaque ligne d'entrée de toute façon.
J'ai envisagé d'utiliser un opérande mul
ou imul
au lieu du plus long imul r,r
, mais son utilisation implicite d'EAX entrerait en conflit avec LODSB.
J'ai également essayé SCASB au lieu de charger et de comparer , mais j'avais besoin xchg esi,edi
avant et après la boucle interne, car SCASB et STOSB utilisent tous deux EDI. (La version 64 bits doit donc utiliser l'ABI x32 pour éviter de tronquer les pointeurs 64 bits).
Éviter STOSB n'est pas une option; rien d'autre n'est aussi court. Et la moitié des avantages de l'utilisation de SCASB est que AL = filler après avoir quitté la boucle intérieure, nous n'avons donc pas besoin de configuration pour REP STOSB.
SCASB compare dans l'autre sens par rapport à ce que j'avais fait, j'ai donc dû inverser les comparaisons.
Ma meilleure tentative avec xchg et scasb. Fonctionne, mais n'est pas plus court. ( Code 32 bits, en utilisant la astuce inc
/ dec
pour changer le remplissage en séparateur ).
; 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
Pour une entrée de ../.../.
, produit ..../......../../
. Je ne vais pas prendre la peine de montrer un vidage hexadécimal de la version avec separator = newline.
"" <> "#"~Table~#
est 3 octets plus court que"#"~StringRepeat~#
, probablement jouable au golf également.Japt , 7 octets
Prend le graphique comme un tableau de chaînes avec
"
comme remplissage et la base comme entier.Essayez-le en ligne
Ajoutez
}R
à la fin afin de prendre le graphique comme une chaîne séparée par des sauts de ligne. ( Essayez-le )Explication
la source
MATL ,
1411 octetsLe délimiteur est l'espace. Filler est tout autre personnage que l'espace.
Essayez-le en ligne!
Explication
la source
Haskell ,
3733 octets4 octets rasés grâce à sudee
La description:
Malheureusement, ceci est
2 octetsbeaucoup plus court que la version sans point beaucoup plus difficile à lire:la source
replicate(b^length x)'#'
par'#'<$[1..b^length x]
.ReRegex , 105 octets
Essayez-le en ligne!
ReRegex est comme le vilain cousin de Retina qui donne tout l'effort aux expressions régulières, au lieu d'avoir ses propres opérateurs sophistiqués.
Bien sûr, il a également
#import
et#input
pour enregistrer les deux entrées de codage en dur, et réécrire les mêmes expressions encore et encore.Expliqué.
Prend des informations sous la forme de:
sur STDIN, et donne une sortie comme
Premièrement, le programme importe la bibliothèque mathématique , qui bien sûr est entièrement écrite en ReRegex. La majeure partie de ceci est alors trois expressions régulières.
Le premier correspond à notre base d'entrée et cherche une ligne d'unaire après lui. il remplace alors cette ligne par
;$1^d<$4>
, qui est la base, par la puissance de l'Unaire (en décimal). La bibliothèque Math gère la conversion de base et l'exposant. UNE ; est placé au début pour l'identifier plus tard comme terminé.Le second correspond à la base, puis à plusieurs lignes de;, avant de se terminer. Si cela correspond à l'ensemble, il se détache de la base. laissant uf avec juste les réponses et l'
;
art.Le dernier, correspond juste unaire au début, éventuellement, puis une
;
réponse. Il transforme ensuite cette réponse en unaire à nouveau, sans le;
.Parce que la sortie n'est pas assortie par la première expression régulière, elle ne boucle pas indéfiniment, et donc notre solution est sortie.
la source
Python 2 ,
5236 octetsL'entrée et la sortie sont considérées comme des tableaux de chaînes.
#
est le remplisseur.Essayez-le en ligne!
la source
Röda , 19 octets
Essayez-le en ligne!
Prend un tableau en entrée et renvoie un flux de valeurs en sortie.
Explication
la source
Haskell , 32 octets
Essayez-le en ligne! Exemple d'utilisation:
f 3 ["##","#","###","#"]
retours["#########","###","###########################","###"]
.Utilisez
mapM putStrLn $ f 3 ["##","#","###","#"]
pour obtenir une sortie plus agréable visuellement:la source
sum[sum[]^sum[],sum[]^sum[]]
.05AB1E , 9 octets
Les barres sont séparées par des espaces, le caractère de sortie est le même que le caractère d'entrée.
Essayez-le en ligne!
la source
PHP, 69 octets
Essayez-le en ligne!
la source
[str_pad]."\n"
au lieu de"\n".[str_pad]
pour résoudre ce problème (+1 octet). En outre, vous pouvez supposer ce qu'est le remplisseur, vous pouvez donc enregistrer deux octets en le remplaçant$l[0]
par"#"
.Gelée , 7 octets
Un lien monadique prenant et renvoyant des listes de barres (elles-mêmes des listes de caractères, des chaînes AKA) le caractère de remplissage est flexible.
Essayez-le en ligne! (le pied de page embellit la liste résultante en joignant ses éléments à des retours à la ligne.)
Comment?
Alternative à 7 octets:
ṁ"L€*@¥
- obtenir la longueur de chaque barre (L€
), augmenterbase
à cette puissance (*@
), puis compresser ("
) la liste et appliquer la dyade de moule (ṁ
) entre les deux.la source
Rubis , 29 octets
Essayez-le en ligne!
Oui, j'ai découvert la semaine dernière qui
?#
produit une chaîne à un seul caractère. Je n'ai aucune idée de la raison pour laquelle cette fonctionnalité existe, mais je suis sûr qu'elle est heureuse.la source
?X
opérateur, où seX
trouve un caractère, est l' opérateur "obtenir la représentation par défaut de ce caractère". Dans Ruby <1.9, il retournerait le point de code Unicode du caractère, car c'est ainsi que les caractères étaient définis, mais maintenant il renvoie une chaîne contenant le caractère. Cela fait partie d'un changement général vers une gestion Unicode plus cohérente dans Ruby.?X
est-il utilisé? Beaucoup de conventions plus bizarres de Ruby, comme la pléthore de$
variables, existent en raison de la familiarité avec Perl.JavaScript (ES8), 39 octets
Prend la base comme un entier et le graphique comme un tableau de chaînes avec n'importe quel caractère comme remplissage, en utilisant la syntaxe de curry.
Essayez-le
Alternative, 49 octets
Cette version prend le graphique comme une chaîne séparée par des sauts de ligne, toujours avec n'importe quel caractère comme remplissage.
la source
m
drapeau sur l'expression régulière, par défaut.
ne correspond pas aux nouvelles lignes.Mathematica, 86 octets
contribution
la source
Octave, 42 octets
* La chaîne d'entrée / sortie ne correspond pas complètement à l'expression rationnelle mais il est possible de comprendre quelle barre est laquelle.
Une fonction prend comme base d'entrée
b
et un tableau 2D de caractèress
contenant"!"
et la sortie est également un tableau de caractères.Essayez-le en ligne!
Explication:
la source
CJam, 20 octets
Format d'entrée
L'entrée est requise dans le format suivant:
la source
Fusain , 11 octets
Essayez-le en ligne! Le lien est vers la version détaillée du code. Les E / S sont une liste de chaînes de
-
caractères (notez que vous avez besoin d'une ligne vide pour terminer la liste).la source
V , 27 octets
L'idée de base est que nous ajoutons un
'
à chaque ligne (n ^ 0), puis pour chacun,#
nous remplaçons le'
s dans la ligne par[input] * '
. À la fin, j'ai'
à#
nouveau tout échangé contreEssayez-le en ligne!
la source
R , 35 octets
une fonction anonyme qui prend les chaînes comme une liste et la base, et retourne une liste de chaînes.
Essayez-le en ligne!
la source
05AB1E , 10 octets
Le caractère filer est
1
et le délimiteur est une nouvelle ligne.Essayez-le en ligne!
la source
Rétine , 62 octets
Essayez-le en ligne! Après tout, un graphique à barres n'est qu'une liste de nombres unaires. Prend l'entrée comme le graphique (en utilisant
#
s) suivi de la base en décimal (pour éviter toute confusion). Explication: Les premiers préfixes de remplacement 1 et la base de chaque ligne du graphique. Le deuxième remplacement multiplie ensuite le premier nombre sur chaque ligne par la seconde, tant que le troisième nombre est différent de zéro. Le troisième remplacement décrémente ensuite le troisième numéro sur chaque ligne. Ces deux remplacements sont répétés jusqu'à ce que le troisième nombre devienne zéro. Le dernier remplacement supprime la base partout, laissant le résultat souhaité.la source
Convexe , 9 octets
Essayez-le en ligne!
la source
Alice , 23 octets
Essayez-le en ligne!
Non seulement je ne suis pas un lâcheur, mais je suis tellement déterminé à bien faire valoir que j'utilise
!
comme remplissage. Cela attirera certainement l'attention du lecteur.Explication
Des miroirs sont conservés dans cette explication pour le rendre plus clair lorsque le programme bascule entre les modes cardinal et ordinal.
la source
Perl 6 , 26 octets
La liste des chaînes d'entrée est dans le premier paramètre,
@^a
. Le deuxième paramètre$^b
est la base. Une liste de chaînes de sortie est renvoyée.la source