This is inspired by an 05AB1E answer by Magic Octupus Urn.

Given two arguments, a positive integer and a string/list of characters:

  1. Translate the number to base-n, where n is the length of the string.
  2. For each character, replace every appearance of the index of that character in the base-n number with that character.
  3. Print or return the new string.


2740, ["|","_"]
2740 -> 101010110100 in base 2
     -> Replace 0s with "|" and 1s with "_"
Output: _|_|_|__|_||

698911, ["c","h","a","o"]
698911 ->  2222220133 in base 4
       ->  Replace 0s with "c", 1s with "h", 2s with "a", and 3s with "o"
Output -> "aaaaaachoo"

1928149325670647244912100789213626616560861130859431492905908574660758972167966, [" ","\n","|","_","-"]
    __   __    
   |  |_|  |   
___|       |___
-   -   -   -  
 - - - - - - - 
- - - - - - - -

Input: 3446503265645381015412, [':', '\n', '.', '_', '=', ' ', ')', '(', ',']
( : )
( : )


  • IO is flexible.
    • You can take the number in any base, as long as it is consistent between inputs
    • The list of characters has to be 0-indexed though, where 0 is the first character and n-1 is the last
  • Possible characters can be any printable ASCII, along with whitespace such as tabs and newlines
  • The given list of characters will have a length in the range 2-10 inclusive. That is, the smallest base is binary and the largest is decimal (no pesky letters here)
  • Standard loopholes are forbidden
  • Feel free to answer even if your language can't handle the larger test cases.

As this is , the shortest code for each language wins. (I know all you golfing languages have one byte built-ins ready to go ;)

  • \$\begingroup\$ Sandbox (deleted) \$\endgroup\$
    – Jo King
    Commented Jun 12, 2018 at 11:39
  • 3
    \$\begingroup\$ D'awwh, I feel honored. 05AB1E ascii-art was my favorite awhile back. \$\endgroup\$ Commented Jun 12, 2018 at 12:51
  • \$\begingroup\$ you can create a new challenge: find the permutation of characters in the array to minimize the number :) \$\endgroup\$
    – mazzy
    Commented Jun 12, 2018 at 19:09

33 Answers 33


05AB1E, 7 6 bytes


Since it was inspired by an 05AB1E answer, an answer given in 05AB1E seems fitting. :)

-1 byte thanks to @Emigna by removing the foreach and doing this implicitly.

Try it online or verify all test cases.


g         # `l`: Take the length of the first (string) input
 в        # And then take the second input in base `l`
          # For each digit `y` of this base-converted number:
  ¹sè     #  Push the `y`'th character of the first input
     J    # Join everything together (and output implicitly)
  • 1
    \$\begingroup\$ gв¹sèJ to save a byte. \$\endgroup\$
    – Emigna
    Commented Jun 12, 2018 at 11:49
  • \$\begingroup\$ @Emigna Thanks. Can't believe I hadn't thought about ¹sè myself now.. (I knew changing the ? to a J would give the same output in this case.) \$\endgroup\$ Commented Jun 12, 2018 at 11:57

Python 3, 49 bytes

Cannot comment yet, so I post the Python 2 answer adapted to Python 3.5.

f=lambda n,s:n and f(n//len(s),s)+s[n%len(s)]or''
  • 2
    \$\begingroup\$ Welcome to PPCG! Feel free to include a TIO link to help showcase your solution. \$\endgroup\$
    – Jo King
    Commented Jun 12, 2018 at 13:34

Java 8, 72 50 bytes


-22 bytes thanks to @OlivierGrégoire by returning an IntStream instead of printing directly.

Try it online.


a->n->                  // Method with char-array and BigInteger parameters
  n.toString(a.length)  //  Convert the input-number to Base `amount_of_characters`
   .chars()             //  Loop over it's digits (as characters)
   .map(c->a[c          //   Convert each character to the `i`'th character of the input
              -48])     //   by first converting the digit-character to index-integer `i`
  • 2
    \$\begingroup\$ a->n->n.toString(a.length).chars().map(c->a[c-48]) (50 bytes) since "IO is flexible" \$\endgroup\$ Commented Jun 12, 2018 at 12:54
  • \$\begingroup\$ String f(char[]a,int n){return n>0?f(a,n/a.length)+a[n%a.length]:"";} (69 bytes) recursive one, for the fun. \$\endgroup\$ Commented Jun 12, 2018 at 13:15

Japt, 2 bytes

Can take the second input as an array or a string. Fails the last 2 test cases as the numbers exceed JavaScript's max integer. Replace s with ì to output an array of characters instead.


Try it


Haskell, 40 39 bytes

n!l=cycle l!!n:div n(length l)!l

Try it online!

As Haskell's Int type is limited to 9223372036854775807, this fails for larger numbers.

-1 byte thanks to Laikoni.


(!) :: Int -> [Char] -> [Char]

0 ! _ = []  -- If the number is 0, we reached the end. Base case: empty string
n ! l = 
  let newN = (n `div` length l) in   -- divide n by the base
    cycle l!!n                       -- return the char encoded by the LSD ... 
    : newN!l                         -- ... prepended to the rest of the output (computed recursively)

Try it online!

  • \$\begingroup\$ Nice idea to use cycle instead of mod! div n(length l) saves a byte. \$\endgroup\$
    – Laikoni
    Commented Jun 12, 2018 at 18:39

MATL, 2 bytes


Try it online!

Inputs are a number and a string.

Fails for numbers exceeding 2^53, because of floating-point precision.


What do YA know, a builtin (base conversion with specified target symbols).


JavaScript (ES6), 48 bytes

Takes input in currying syntax (c)(n), where c is a list of characters and n is an integer.

Safe only for n < 253.


Try it online!

JavaScript (ES6), 99 bytes

With support for big integers

Takes input in currying syntax (c)(a), where c is a list of characters and a is a list of decimal digits (as integers).


Try it online!


x86 32-bit machine code (32-bit integers): 17 bytes.

(also see other versions below, including 16 bytes for 32-bit or 64-bit, with a DF=1 calling convention.)

Caller passes args in registers, including a pointer to the end of an output buffer (like my C answer; see it for justification and explanation of the algorithm.) glibc's internal _itoa does this, so it's not just contrived for code-golf. The arg-passing registers are close to x86-64 System V, except we have an arg in EAX instead of EDX.

On return, EDI points to the first byte of a 0-terminated C string in the output buffer. The usual return-value register is EAX/RAX, but in assembly language you can use whatever calling convention is convenient for a function. (xchg eax,edi at the end would add 1 byte).

The caller can calculate an explicit length if it wants, from buffer_end - edi. But I don't think we can justify omitting the terminator unless the function actually returns both start+end pointers or pointer+length. That would save 3 bytes in this version, but I don't think it's justifiable.

  • EAX = n = number to decode. (For idiv. The other args aren't implicit operands.)
  • EDI = end of output buffer (64-bit version still uses dec edi, so must be in the low 4GiB)
  • ESI/RSI = lookup table, aka LUT. not clobbered.
  • ECX = length of table = base. not clobbered.

nasm -felf32 ascii-compress-base.asm -l /dev/stdout | cut -b -30,$((30+10))- (Hand edited to shrink comments, line numbering is weird.)

   32-bit: 17 bytes        ;  64-bit: 18 bytes
                           ; same source assembles as 32 or 64-bit
 3                         %ifidn __OUTPUT_FORMAT__, elf32
 5                         %define rdi edi
 6   address               %define rsi esi
11          machine        %endif
14          code           %define DEF(funcname) funcname: global funcname
16          bytes           
22                         ;;; returns: pointer in RDI to the start of a 0-terminated string
24                         ;;; clobbers:; EDX (tmp remainder)
25                         DEF(ascii_compress_nostring)
27 00000000 C60700             mov     BYTE [rdi], 0
28                         .loop:                    ; do{
29 00000003 99                 cdq                    ; 1 byte shorter than   xor edx,edx / div
30 00000004 F7F9               idiv    ecx            ; edx=n%B   eax=n/B
32 00000006 8A1416             mov     dl, [rsi + rdx]   ; dl = LUT[n%B]
33 00000009 4F                 dec     edi               ; --output  ; 2B in x86-64
34 0000000A 8817               mov     [rdi], dl         ; *output = dl
36 0000000C 85C0               test    eax,eax           ; div/idiv don't write flags in practice, and the manual says they're undefined.
37 0000000E 75F3               jnz     .loop         ; }while(n);
39 00000010 C3                 ret
           0x11 bytes = 17
40 00000011 11             .size: db $ - .start

It's surprising that the simplest version with basically no speed/size tradeoffs is the smallest, but std/cld cost 2 bytes to use stosb to go in descending order and still follow the common DF=0 calling convention. (And STOS decrements after storing, leaving the pointer pointing one byte too low on loop exit, costing us extra bytes to work around.)


I came up with 4 significantly different implementation tricks (using simple mov load/store (above), using lea/movsb (neat but not optimal), using xchg/xlatb/stosb/xchg, and one that enters the loop with an overlapping-instruction hack. See code below). The last needs a trailing 0 in the lookup table to copy as the output string terminator, so I'm counting that as +1 byte. Depending on 32/64-bit (1-byte inc or not), and whether we can assume the caller sets DF=1 (stosb descending) or whatever, different versions are (tied for) shortest.

DF=1 to store in descending order makes it a win to xchg / stosb / xchg, but the caller often won't want that; It feels like offloading work to the caller in a hard-to-justify way. (Unlike custom arg-passing and return-value registers, which typically don't cost an asm caller any extra work.) But in 64-bit code, cld / scasb works as inc rdi, avoiding truncating the output pointer to 32-bit, so it's sometimes inconvenient to preserve DF=1 in 64-bit-clean functions. . (Pointers to static code/data are 32-bit in x86-64 non-PIE executables on Linux, and always in the Linux x32 ABI, so an x86-64 version using 32-bit pointers is usable in some cases.) Anyway, this interaction makes it interesting to look at different combinations of requirements.

  • IA32 with a DF=0 on entry/exit calling convention: 17B (nostring).
  • IA32: 16B (with a DF=1 convention: stosb_edx_arg or skew); or with incoming DF=dontcare, leaving it set: 16+1B stosb_decode_overlap or 17B stosb_edx_arg
  • x86-64 with 64-bit pointers, and a DF=0 on entry/exit calling convention: 17+1 bytes (stosb_decode_overlap), 18B (stosb_edx_arg or skew)
  • x86-64 with 64-bit pointers, other DF handling: 16B (DF=1 skew), 17B (nostring with DF=1, using scasb instead of dec). 18B (stosb_edx_arg preserving DF=1 with 3-byte inc rdi).

    Or if we allow returning a pointer to 1 byte before the string, 15B (stosb_edx_arg without the inc at the end). All set to call again and expand another string into the buffer with different base / table... But that would make more sense if we didn't store a terminating 0 either, and you might put the function body inside a loop so that's really a separate problem.

  • x86-64 with 32-bit output pointer, DF=0 calling convention: no improvement over 64-bit output pointer, but 18B (nostring) ties now.

  • x86-64 with 32-bit output pointer: no improvement over the best 64-bit pointer versions, so 16B (DF=1 skew). Or to set DF=1 and leave it, 17B for skew with std but not cld. Or 17+1B for stosb_decode_overlap with inc edi at the end instead of cld/scasb.

With a DF=1 calling convention: 16 bytes (IA32 or x86-64)

Requires DF=1 on input, leaves it set. Barely plausible, at least on a per-function basis. Does the same thing as the above version, but with xchg to get the remainder in/out of AL before/after XLATB (table lookup with R/EBX as base) and STOSB (*output-- = al).

With a normal DF=0 on entry/exit convention, the std / cld/scasb version is 18 bytes for 32 and 64-bit code, and is 64-bit clean (works with a 64-bit output pointer).

Note that the input args are in different registers, including RBX for the table (for xlatb). Also note that this loop starts by storing AL, and ends with the last char not stored yet (hence the mov at the end). So the loop is "skewed" relative to the others, hence the name.

                            ;DF=1 version.  Uncomment std/cld for DF=0
                            ;32-bit and 64-bit: 16B
157                         DEF(ascii_compress_skew)
158                         ;;; inputs
159                             ;; O in RDI = end of output buffer
160                             ;; I in RBX = lookup table  for xlatb
161                             ;; n in EDX = number to decode
162                             ;; B in ECX = length of table = modulus
163                         ;;; returns: pointer in RDI to the start of a 0-terminated string
164                         ;;; clobbers:; EDX=0, EAX=last char
165                         .start:
166                         ;    std
167 00000060 31C0               xor    eax,eax
168                         .loop:                    ; do{
169 00000062 AA                 stosb
170 00000063 92                 xchg    eax, edx
172 00000064 99                 cdq                    ; 1 byte shorter than   xor edx,edx / div
173 00000065 F7F9               idiv    ecx            ; edx=n%B   eax=n/B
175 00000067 92                 xchg    eax, edx       ; eax=n%B   edx=n/B
176 00000068 D7                 xlatb                  ; al = byte [rbx + al]
178 00000069 85D2               test    edx,edx
179 0000006B 75F5               jnz     .loop         ; }while(n = n/B);
181 0000006D 8807               mov     [rdi], al     ; stosb would move RDI away
182                         ;    cld
183 0000006F C3                 ret

184 00000070 10             .size: db $ - .start

A similar non-skewed version overshoots EDI/RDI and then fixes it up.

                            ; 32-bit DF=1: 16B    64-bit: 17B (or 18B for DF=0)
70                         DEF(ascii_compress_stosb_edx_arg)  ; x86-64 SysV arg passing, but returns in RDI
71                             ;; O in RDI = end of output buffer
72                             ;; I in RBX = lookup table  for xlatb
73                             ;; n in EDX = number to decode
74                             ;; B in ECX = length of table
75                         ;;; clobbers EAX,EDX, preserves DF
76                             ; 32-bit mode: a DF=1 convention would save 2B (use inc edi instead of cld/scasb)
77                             ; 32-bit mode: call-clobbered DF would save 1B (still need STD, but INC EDI saves 1)
79                         .start:
80 00000040 31C0               xor     eax,eax
81                         ;    std
82 00000042 AA                 stosb
83                         .loop:
84 00000043 92                 xchg    eax, edx
85 00000044 99                 cdq
86 00000045 F7F9               idiv    ecx            ; edx=n%B   eax=n/B
88 00000047 92                 xchg    eax, edx       ; eax=n%B   edx=n/B
89 00000048 D7                 xlatb                  ; al = byte [rbx + al]
90 00000049 AA                 stosb                  ; *output-- = al
92 0000004A 85D2               test    edx,edx
93 0000004C 75F5               jnz     .loop
95 0000004E 47                 inc    edi
96                             ;; cld
97                             ;; scasb          ; rdi++
98 0000004F C3                 ret
99 00000050 10             .size: db $ - .start
    16 bytes for the 32-bit DF=1 version

I tried an alternate version of this with lea esi, [rbx+rdx] / movsb as the inner loop body. (RSI is reset every iteration, but RDI decrements). But it can't use xor-zero / stos for the terminator, so it's 1 byte larger. (And it's not 64-bit clean for the lookup table without a REX prefix on the LEA.)

LUT with explicit length and a 0 terminator: 16+1 bytes (32-bit)

This version sets DF=1 and leaves it that way. I'm counting the extra LUT byte required as part of the total byte count.

The cool trick here is having the same bytes decode two different ways. We fall into the middle of the loop with remainder = base and quotient= input number, and copy the 0 terminator into place.

On the first time through the function, the first 3 bytes of the loop are consumed as the high bytes of a disp32 for an LEA. That LEA copies the base (modulus) to EDX, idiv produces the remainder for later iterations.

The 2nd byte of idiv ebp is FD, which is the opcode for the std instruction that this function needs to work. (This was a lucky discovery. I had been looking at this with div earlier, which distinguishes itself from idiv using the /r bits in ModRM. The 2nd byte of div epb decodes as cmc, which is harmless but not helpful. But with idiv ebp can we actually remove the std from the top of the function.)

Note the input registers are difference once again: EBP for the base.

103                         DEF(ascii_compress_stosb_decode_overlap)
104                         ;;; inputs
105                             ;; n in EAX = number to decode
106                             ;; O in RDI = end of output buffer
107                             ;; I in RBX = lookup table, 0-terminated.  (first iter copies LUT[base] as output terminator)
108                             ;; B in EBP = base = length of table
109                         ;;; returns: pointer in RDI to the start of a 0-terminated string
110                         ;;; clobbers: EDX (=0), EAX,  DF
111                             ;; Or a DF=1 convention allows idiv ecx (STC).  Or we could put xchg after stos and not run IDIV's modRM
112                         .start:
117                             ;2nd byte of div ebx = repz.  edx=repnz.
118                             ;            div ebp = cmc.   ecx=int1 = icebp (hardware-debug trap)
119                             ;2nd byte of idiv ebp = std = 0xfd.  ecx=stc
126                             ;lea    edx, [dword 0 + ebp]
127 00000040 8D9500             db  0x8d, 0x95, 0   ; opcode, modrm, 0 for lea edx, [rbp+disp32].  low byte = 0 so DL = BPL+0 = base
128                             ; skips xchg, cdq, and idiv.
129                             ; decode starts with the 2nd byte of idiv ebp, which decodes as the STD we need
130                         .loop:
131 00000043 92                 xchg    eax, edx
132 00000044 99                 cdq
133 00000045 F7FD               idiv    ebp            ; edx=n%B   eax=n/B;
134                             ;; on loop entry, 2nd byte of idiv ebp runs as STD.  n in EAX, like after idiv.  base in edx (fake remainder)
136 00000047 92                 xchg    eax, edx       ; eax=n%B   edx=n/B
137 00000048 D7                 xlatb                  ; al = byte [rbx + al]
138                         .do_stos:
139 00000049 AA                 stosb                  ; *output-- = al
141 0000004A 85D2               test    edx,edx
142 0000004C 75F5               jnz     .loop
144                         %ifidn __OUTPUT_FORMAT__, elf32
145 0000004E 47                 inc     edi         ; saves a byte in 32-bit.  Makes DF call-clobbered instead of normal DF=0
146                         %else
147                             cld
148                             scasb          ; rdi++
149                         %endif
151 0000004F C3                 ret
152 00000050 10             .size: db $ - .start
153 00000051 01                     db 1   ; +1 because we require an extra LUT byte
       # 16+1 bytes for a 32-bit version.
       # 17+1 bytes for a 64-bit version that ends with DF=0

This overlapping decode trick can also be used with cmp eax, imm32: it only takes 1 byte to effectively jump forward 4 bytes, only clobbering flags. (This is terrible for performance on CPUs that mark instruction boundaries in L1i cache, BTW.)

But here, we're using 3 bytes to copy a register and jump into the loop. That would normally take 2+2 (mov + jmp), and would let us jump into the loop right before the STOS instead of before the XLATB. But then we'd need a separate STD, and it wouldn't be very interesting.

Try it online! (with a _start caller that uses sys_write on the result)

It's best for debugging to run it under strace, or hexdump the output, so you can see verify that there's a \0 terminator in the right place and so on. But you can see this actually work, and produce AAAAAACHOO for an input of

num  equ 698911
table:  db "CHAO"
    tablen equ $ - table
    db 0  ; "terminator" needed by ascii_compress_stosb_decode_overlap

(Actually xxAAAAAACHOO\0x\0\0..., because we're dumping from 2 bytes earlier in the buffer out to a fixed length. So we can see that the function wrote the bytes it was supposed to and didn't step on any bytes it shouldn't have. The start-pointer passed to the function was the 2nd-last x character, which was followed by zeros.)


Jelly, 4 bytes


Try it online!

is literally a built-in for this. The other three bytes account for Jelly's one-based indexing.

  • \$\begingroup\$ Out of curiosity, why does Jelly even have the builtin "Base decompression; convert x to base length(y) then index into y."? Is it for the very exceptional cases where the base you ant to convert to and length of a string/integer/list are equal? When I search for it, I can only find three answers using it: 1; 2; 3. Kinda a weird builtin in every day code-golf challenges imo. :S \$\endgroup\$ Commented Jun 12, 2018 at 12:46
  • 3
    \$\begingroup\$ @KevinCruijssen it's very useful when you want, for example, to convert N to hexadecimal using hex letters instead of a list of numbers. \$\endgroup\$
    – Mr. Xcoder
    Commented Jun 12, 2018 at 12:48
  • \$\begingroup\$ @KevinCruijssen It's a compression method for strings. In the first example the string desired is “sspspdspdspfdspfdsp”, but with “çƥ÷£ḟ’ṃ“spdf”¤ you save six bytes. It's especially useful with Jelly's base 250 numbers \$\endgroup\$
    – dylnan
    Commented Jun 12, 2018 at 13:27

Python 2, 49 48 bytes

-1 byte thanks to Jo King

f=lambda n,s:n and f(n/len(s),s)+s[n%len(s)]or''

Try it online!

Alternative version (won't finish for large numbers), 45 43 bytes

-2 bytes thanks to Jo King

f=lambda n,s:s*n and f(n/len(s),s)+(s*n)[n]

Try it online!


Charcoal, 3 1 bytes


Try it online! Link is to verbose version of code. Edit: Saved 2 bytes thanks to @ASCII-only. Previous version before the builtin was added, 8 bytes:


Try it online! Link is to verbose version of code. Explanation:

    η       Second input
   L        Length
  θ         First input
 ↨          Base convert to array
⭆           Map over values and join
      η     Second input
       ι    Current value
     §      Index into list
            Implicitly print result
  • \$\begingroup\$ Actually it's supposed to already work but evidently I've messed up yet again >_> \$\endgroup\$
    – ASCII-only
    Commented Jun 13, 2018 at 5:28
  • \$\begingroup\$ Also, it would actually be 1 byte :P (it's currently broken though, so i'm not using number and string array as arguments) (also, i wonder how (un)useful implicit input is) \$\endgroup\$
    – ASCII-only
    Commented Jun 13, 2018 at 5:38
  • \$\begingroup\$ @ASCII-only Don't you mean "1 bytes" (see your -vl output ;-) Also, implicit input would seem to be almost useless in Charcoal, except for challenges like this. \$\endgroup\$
    – Neil
    Commented Jun 13, 2018 at 8:22
  • 1
    \$\begingroup\$ @ASCII-only Plural "bytes" instead of singular "byte" is what Neil means. ;) As for your 1-byte answer, why the crossed-out θη? Looks a bit confusing tbh. Why not just remove it completely and leave ? \$\endgroup\$ Commented Jun 13, 2018 at 14:03
  • 1
    \$\begingroup\$ @Nit I double-checked and the built-in had been added before the question was asked, but I hadn't realised because it had a bug. \$\endgroup\$
    – Neil
    Commented Jun 13, 2018 at 22:37

D, 112 bytes

import std.conv;C u(C,I)(I n,C b){C t;for(I l=b.length;n;n/=l)t=to!C(n%l)~t;foreach(ref c;t)c=b[c-48];return t;}

Try it online!

This is a port of HatsuPointerKun's C++ answer

The calling style is u(ulong(n), to!(char[])(b)), where n and b are the left and right arguments

D specific stuff

Sadly, we need to import std.conv to do any type conversions above very basic type conversion.

Using the golfy template system, and giving the function the required types (which is why calling it is not just u(n,b)), we can shorten occurrences of char[] and ulong to 1 byte each, when inside f the function.

D initializes our types for us, so C t; is short for C t=cast(char[])([])

to!C converts the integer n%l to a character array (using codepoints), and ~ is concatenation

foreach(ref c;t) is like C++'s for(... : ...) loop, but a little longer. ref is like &, it treats c as copied-by-reference (i.e., we can modify t). Luckily, D infers the type of c without any keyword denoting type.


C++, 150 144 bytes, uint64 input

-6 bytes thanks to Zacharý

using s=std::string;s u(uint64_t n,s b){s t;for(;n;n/=b.size())t=std::to_string(n%b.size())+t;for(auto&a:t)a=b[a-48];return t;}

Using a variable to store the size would increase the bytecount by 1

To call the function :

u(2740, "|_")

First the number, second is the string ( char array )

Test cases :

std::cout << u(2740, "|_") << '\n' << u(698911, "chao") << '\n';
return 0;
  • 1
    \$\begingroup\$ 3 bytes to shave off: You don't need the space after #include, you can change ;; to just ;, and '0' can just be 48 \$\endgroup\$
    – Adalynn
    Commented Jun 15, 2018 at 15:52
  • 1
    \$\begingroup\$ Also, the while loop could be a for loop: for(;n;n/=b.size())t=std::to_string(n%b.size())+t; \$\endgroup\$
    – Adalynn
    Commented Jun 15, 2018 at 17:30
  • \$\begingroup\$ @ceilingcat, I forgot that b.size() doesn't change in that loop. \$\endgroup\$
    – Adalynn
    Commented Jun 18, 2018 at 16:02
  • \$\begingroup\$ @ceilingcat That would increase the bytecount by 1, instead of lowering it \$\endgroup\$ Commented Jun 19, 2018 at 13:04

Twig, 66 bytes

Created a macro that must be imported into a template.

{%macro d(n,c)%}{%for N in n|split%}{{c[N]}}{%endfor%}{%endmacro%}

Values expected:

For the first arguments (n):

  • number
  • string

For the second argument (c):

  • Array of numbers
  • Array of strings

How to use:

  • Create a .twig file
  • Add {% import 'file.twig' as uncompress %}
  • Call the macro uncompress.d()

Ungolfed (non-functional):

{% macro d(numbers, chars) %}
    {% for number in numbers|split %}
        {{ chars[number] }}
    {% endfor %}
{% endmacro %}

You can test this code on: https://twigfiddle.com/54a0i9


Pyth, 9 8 7 bytes


Saved a byte thanks to hakr14 and another thanks to Mr. Xcoder.
Try it here


    jElQ      Convert the second input (the number) to the appropriate base.
 @LQ          Look up each digit in the list of strings.
s             Add them all together.
  • \$\begingroup\$ Save a byte by replacing m@Qd with @LQ \$\endgroup\$
    – hakr14
    Commented Jun 13, 2018 at 2:26
  • \$\begingroup\$ Save a byte by replacing vz with E. \$\endgroup\$
    – Mr. Xcoder
    Commented Jun 13, 2018 at 17:50

C89, limited-range signed int n, 64 53 bytes

  • changelog: take a char **out and modify it, instead of taking and returning a char *

Takes the number as an int, the lookup table as an array + length.
Output is written into a char *outbuf. Caller passes (by reference) a pointer to the end of the buffer. Function modifies that pointer to point to the first byte of the string on return.


This is valid C89, and works correctly even with optimization enabled. i.e. doesn't depend on gcc -O0 behaviour when falling off the end of a non-void function or have any other UB.

Passing a pointer to the end of a buffer is normal for an optimized int->string function, such as glibc's internal _itoa. See this answer for a detailed explanation of breaking an integer into digits with a div / mod loop like we're doing here, in C as well as x86-64 asm. If the base is a power of 2, you can shift/mask to extract digits MSD first, but otherwise the only good option is least-significant-digit first (with modulo).

Try it online!. Ungolfed version:

/* int n = the number
 * int B = size of I = base
 * char I[] = input table
 * char **O = input/output arg passed by ref:
 *    on entry: pointer to the last byte of output buffer.
 *    on exit:  pointer to the first byte of the 0-terminated string in output buffer
void ungolfed_g(n,I,B,O)char*I,**O;
    char *outpos = *O;     /* Golfed version uses *O everywhere we use outpos */
    *outpos = 0;           /* terminate the output string */
        *--outpos = I[n%B]; /* produce 1 char at a time, decrementing the output pointer */
    *O = outpos;

In this explicit-length version, the input is a char table[] which does not need a terminating 0 byte, because we never treat it as a string. It could be an int table[] for all we care. C doesn't have containers that know their own length, so pointer+length is the normal way to pass an array with a size. So we choose that instead of needing to strlen.

The maximum buffer size is approximately sizeof(int)*CHAR_BIT + 1, so it's small and compile-time constant. (We use this much space with base=2 and all the bits set to 1.) e.g. 33 bytes for 32 bit integers, including the 0 terminator.

C89, signed int, table as an implicit-length C string, 65 bytes


This is the same thing but the input is an implicit-length string so we have to find the length ourselves.


Bash + core utilities, 49 bytes

dc -e`echo -en $2|wc -c`o$1p|tr -dc 0-9|tr 0-9 $2

Try it online!


This takes command-line arguments as input (the number in base 10, then a single string with the list of characters) and outputs to stdout. Special characters like space, newline, etc may be entered in octal notation (for example, \040 for a space), or \n for a newline, \t for tab, or any other escape sequence that echo -e and tr interpret identically.

A lot of the bytes here are to handle special characters and larger test cases. If I only have to handle non-terrible characters and the numbers are small (for example, the first test case), the following 24 bytes will do it:

dc -e${#2}o$1p|tr 0-9 $2

This uses parameter expansion ${#2} to get the number of characters in the string, builds a dc program to do the base conversion, and then sends the converted number through tr.

This won't handle newlines or spaces or tabs, though, so to deal with escape sequences without affecting the base, I do a character count wc -c after interpreting the escapes with echo -en. This expands the program to 38 bytes:

dc -e`echo -en $2|wc -c`o$1p|tr 0-9 $2

Unfortunately, dc has an annoying "feature" where if it's outputting a large number, it will line wrap it with a slash+newline sequence, so the larger test cases have this extra output. To remove it, I pipe dc's output through tr -dc 0-9 to remove non-numeric characters. And there we are.

  • \$\begingroup\$ I was going to suggest dc -e${#2}o$1p|tr 0-9 "$2" to take the input literally instead of in \escaped form so it can handle spaces, but tr doesn't have an option to not treat - as a range character, for example. If the input has - not at one end of the string, it breaks. Perhaps you can use sed "y/0123456789/$2/". No I guess not, GNU sed requires both args to y to be the same length, and it seems to choke on newline. \$\endgroup\$ Commented Jun 15, 2018 at 9:22

APL (Dyalog Unicode), 14 13 12 bytes


Try it online!

Infix tacit function; can't handle the largest test case because of floating point representation.

Saved 1 2 bytes thanks to @Adám!

13 bytes added for the headers: ⎕IO←0: Index Origin = 0 and ⎕FR←1287: Float Representation = 128 bits. (I'd forgotten that this doesn't apply.)


⊢⌷⍨∘⊂⊥⍣¯1⍨∘≢   ⍝ Tacit function, infix.
           ≢   ⍝ Tally (number of elements in) the right argument.
         ⍨∘    ⍝ Then swap the arguments of
     ⊥⍣¯1      ⍝ Base conversion (to base ≢<r_arg>)
    ⊂          ⍝ Enclose (convert to an array)
  ⍨∘           ⍝ Then swap the arguments of
 ⌷             ⍝ Index
⊢              ⍝ (Into) the right argument.

Attache, 17 bytes


Try it online!


{               }  ?? anonymous lambda; input: _ = dictionary, _2 = encoded
   _2&ToBase       ?? convert _2 to base
            !#_    ?? ... Size[_]
 _[            ]   ?? and take those members from the dictionary

Vyxal, 1 byte


Try it Online!

05AB1E, 3 bytes


Try it online!


Canvas, 7 bytes


Try it here!

Straight forward implementation:

L      get the length of the input (popping the item)
 ┬     base decode the other input
  {    for each number there
   ╵     increment
    ⁸@   in the 1st input get that numbered item
      p  and output that

The input character set can be a string too as long as it doesn't contain a newline, because Canvas doesn't have a newline character & automatically converts it to a 2D object.


Stax, 6 5 bytes


Run and debug it


n%|E@ Full program, implicit input in order list, number
n%    Copy the list to the top and get its length
  |E  Convert the number to that base
    @ Map: index

SOGL V0.12, 10 bytes


Try it Here!

Pretty long, considering the idea is pretty much implemented in the language: here, entering

¶    __   __    ¶   |  |_|  |   ¶___|       |___¶-   -   -   -  ¶ - - - - - - - ¶- - - - - - - -¶_______________¶

gives out a compressed string using this method.


Ruby, 31 bytes


Try it online!


Stax, 2 bytes


Run and debug it

:B is an instruction in stax that does this. It would normally operate on a "string"* instead of an array of "strings". In the end, this means that the output is an array of single character arrays. But output implicitly flattens anyway.

*Stax doesn't actually have a string type. Text is represented by integer arrays of codepoints.

  • \$\begingroup\$ Huh, I never noticed this command ... \$\endgroup\$
    – wastl
    Commented Jun 12, 2018 at 15:11
  • \$\begingroup\$ There are a lot. I once added an instruction to stax that already existed, and I had just forgotten about. (array is non-descending?) \$\endgroup\$
    – recursive
    Commented Jun 12, 2018 at 15:28

J, 12 bytes



      #.inv    convert
           [   the left argument (the number)      
   #@]         to base the length of the right argument (the list of characters)    
  {~           and use the digits as indices to
]              the list of characters        

Try it online!


Wolfram Language (Mathematica), 49 bytes


Try it online!


C (gcc), 110 bytes

char s[99],S[99];i;f(x,_)char*_;{i=strlen(_);*s=*S=0;while(x)sprintf(S,"%c%s",_[x%i],s),strcpy(s,S),x/=i;x=s;}

Try it online!


char s[99],S[99];           // Two strings, s and S (capacity 99)
i;                          // A variable to store the length of the string
f(x,_)char*_;{              // f takes x and string _
    i=strlen(_);            // set i to the length of _
    *s=*S=0;                // empty s and S
    while(x)                // loop until x is zero
        sprintf(S,"%c%s",   // print into S a character, then a string
                _[x%i],s),  // character is the x%i'th character of _, the string is s
        strcpy(s,S),        // copy S into s
        x/=i;               // divide x by the length of the string (and loop)
    x=s;}                   // return the string in s
  • 1
    \$\begingroup\$ If you start at the end of a buffer and work backwards, you can avoid sprintf. My C version is 53 bytes, with the output buffer provided by the caller. You could do one strcpy at the end if you want to copy the bytes to the start of a buffer. \$\endgroup\$ Commented Jun 14, 2018 at 5:01
  • \$\begingroup\$ That is brilliant. I don't care if its an awkward I/O format. Awkward I/O is C claim to fame! Well done, I'd recommend putting that into the C tips post. \$\endgroup\$
    – LambdaBeta
    Commented Jun 14, 2018 at 14:50
  • \$\begingroup\$ Posted an answer on Tips for golfing in C explaining and justifying the technique. \$\endgroup\$ Commented Jun 22, 2018 at 2:16

CJam, 11 bytes


Try it online! Takes input as the number, then a newline, then the characters in the ASCII art.


li           e# Read the first line and convert it to an integer
  q_,        e# Read the rest of the input and push its length n
     @\b     e# Convert the number to its base-n equivalent, as an array of numbers
        \f=  e# Map over the array, taking each element's index from the string
  • \$\begingroup\$ Since "IO is Flexible", I think you can get rid of the liq at the beginning, and that saves you 3 bytes! \$\endgroup\$
    – Chromium
    Commented Jun 14, 2018 at 1:41

JavaScript, 39 bytes

Port of Rod's Python solution.


Try it online

  • \$\begingroup\$ Only works for n up to 64-bit, right? (Python has arbitrary-precision integers built in, but JS numbers are naturally double-precision floats that can be converted to integer). Doesn't seem to work for the larger test cases in the question, like 1928149325670647244912100789213626616560861130859431492905908574660758972167966. Oh, but the question allows answers like that. Still, should be noted. \$\endgroup\$ Commented Jun 14, 2018 at 2:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.