# Counting Quipu: Base 10 in the New World

Quipus are an ancient device used by the Inca in the Precolumbian era to record numbers in a base ten positional system of knots on a cord, which works as follows:

Each cluster of knots is a digit, and there are three main types of knots: simple overhand knots; "long knots", consisting of an overhand knot with one or more additional turns; and figure-eight knots.

• Powers of ten are shown by position along the string, and this position is aligned between successive strands.
• Digits in positions for 10 and higher powers are represented by clusters of simple knots (e.g., 40 is four simple knots in a row in the "tens" position).
• Digits in the "ones" position are represented by long knots (e.g., 4 is a knot with four turns). Because of the way the knots are tied, the digit 1 cannot be shown this way and is represented in this position by a figure-of-eight knot.
• Zero is represented by the absence of a knot in the appropriate position.

## Details

For this challenge, each strand of a quipu represents a single number (though, as the Wikipedia article states, you can represent many numbers on one strand, in this challenge, we shall not).

Knots

Each knot will be represented by a single ASCII character.

• . represents a simple knot
• : represents a one turn of a long knot
• 8 represents a figure-eight knot
• | represents the absence of a knot as well as a delimiter between the digits.

Constructing Quipus

Quipu are constructed following these rules.

1. Strands run top to bottom in descending order of position (as in, the units digit will be at the bottom end of a strand). Digits along a strand are separated by the character (|).
2. The power of 10 a digit represents is determined by its position along the strand in the same way a digit's power of 10 would be calculated using its index in a number with our number system. That is, 24 with a 2 in the tens place and a 4 in the units place, will be represented by two knots, a delimiter (|), then four knots.
3. Digits in the same position are aligned towards the bottom of the strand. If one digit in a position will have less knots than other digits of other numbers in the same position, the absence of those knots is represented by (|).
4. Consecutive simple knots (.) represent a value in their position.
5. Every digit is represented by at least 1 character. When a digits value is 0 for all numbers in a quipu, it is represented by the absence of a knot (|).
6. The units place is treated specially. A one in the units place is represented by a figure-eight knot (8). A value of two or more in the units place is represented by consecutive long knots (:).
7. When the units digit is 0 for all numbers in a quipu the absence of a knot is not printed but, the trailing delimiter for the tens digit is preserved.
8. There is no delimiter following the units digit.

## Rules

• Input will consist of a non-empty list of non-negative integers that may be received through any of the default input methods. You may assume that these integers are all less than or equal to 2147483647 or 2^31-1. While the test cases are space-delimited, your input format can separate inputs in any way that is convenient for your language, whether that be comma-separated, newline-separated, in an array, and so on.
• Output consists of a single Quipu constructed according to the rules described above. Output may be given through any of the default output methods.
• Your code should be a program or a function, though it does not need to be a named function.
• Knots take some time to tie so to save time, your code as short as possible.

As always, if the problem is unclear, please let me know. Good luck and good golfing!

## Examples

Input:

5 3 1 0


Output:

:|||
:|||
::||
::||
::8|


Input:

50 30 10 0


Output:

.|||
.|||
..||
..||
...|
||||


Input:

330


Output:

.
.
.
|
.
.
.
|


Input:

204 1


Output:

.|
.|
||
||
||
:|
:|
:|
:8


Input:

201 0 100 222


Output:

.||.
.|..
||||
|||.
|||.
||||
|||:
8||:


Input:

1073741823 2147483647


Output:

|.
..
||
|.
||
.|
.|
.|
..
..
..
..
||
|.
|.
|.
|.
..
..
..
||
.|
.|
.|
..
..
..
..
||
|.
|.
|.
|.
..
..
..
..
||
|.
|.
..
||
.|
.|
..
..
..
..
..
..
||
|.
|.
..
..
||
|:
|:
|:
|:
::
::
::


Input:

0


Output:

|


Longer test cases

This was a fun challenge to work on, on and off, in between Christmas celebrations. Thank you for posting! Golfing this was interesting because the specification is full of exceptions and special cases, which required lots of if conditions. Also, while I didn’t need to convert to and from decimal this time, I did need a “max” function of sorts to determine the largest number of digits in each number and the largest value of the digits in each place.

The first version of this was 4844 bytes, just to give you an idea of how much I golfed this.

The program expects the input as a comma-separated list of integers. No spaces or newlines. Using those will produce undefined behavior.

'""""""'""'""'""'""'""'"""'""""""'"""'""'""'""'""'""'""'""'""'""'""'""""""'""'""'""'""'""'""'"""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""'""""""'""""""""'"""'""'""'""'""'""'""""""'""'"""'""'""'""'""'""'""'""'""'"""'"""""'""""""'"""'""""""""'"""""""'"""'""""""'""""""""'"""'""'""'"""""""'""""""""'"""'"""""'""'""""""'""'""'""'"""'""""""""""'""""'""""""'""'""'"""'"""""""'""'""'""'""'""'""'"""'"""""""""'""""""'""""""'""'"""'""'"""""""'""'"""'""'"""""'""""""'""'""'"""'""""""""'"""""""'""'""'"""'""""""'""'""'""'"""'""""""""'"""""""'""'""'""'"""'""""""'""""""'"""'""""""""'"""""""'"""'"""'""""""'"""'""""""""'"""'""""""'""'""'"""'"""""'"""""""'"""""""'"""'""""""'"""'""""""""'"""""""'"""'"""""'""""""'"""'""'"""""""'"""'""""'""""""'""'""'""'"""'""""""""'"""'"""""'""""""""'""""""'"""""""'"""'""'"""""""'"""""""'"""'"""""""""'"""""""""'"""""""'"""""""'"""'"""'"""""""""'""'"""""""'"""'"""'""""""""'"""'""""'""""'""""""'""'"""'""'""'""'""'""'""'""'""'"""'"""""'"""""""'""""""'""'"""'""'"""""""'""'"""'""""'""""'"""""'""""""'""'""'""'""'""'""'""'"""'""""""'""'""'""'""'"""'"""""""'""""""'""'"""'""'"""""""'""'"""'"""'"""""'"""""""'""""""'""'"""'""""""""'"""""""'""'"""'"""""""""'""""""""'"""""""'""""""'""'""'""'""'""'""'""'"""'""""""""'"""""""'""'""'""'""'""'""'""'"""'"""""""""'""""""""'""""""""'"""""""'"""""""'"""'"""""""""'"""""""'""'""'""'"""'""""""'""'""'""'""'"""'"'"""""""""'""'"""""""'"""""""'""'"""'"""""""""'""'""'"""""""'"""""""'""'"""'""""'""""""'"""""""'""'"""'"""""""""'"""""""""'""'"""""""'"""'"""'"""""""""'""'""'"""""""'"""""""'"""'"""'"""""""""'""'""'""'"""""""'"""""""'""'"""'"""'""""""""'"""'""'"""""""'"""""""'""'"""'""""""""'""""""""'"""'"""""""'""""""""'"""'"""""""""'"""""""'"""""""'"""'"""""""""'""'"""""""'"""'"""""""'""'""'""'""'""'""'"""'""'""'"""""""'""'""'""'""'""'"""'"""""""'""""""""'"""'"""""""'""'""'""'""'""'"""'""""'"""""""""'"""""""""'""'"""""'"""""""'""""""'""'""'"""'""""""""'"""""""'""'""'"""'""""""'"""""""'""'"""'""""""""'"""""""'"""""""'""'"""'"""'"""""""""'"""""""'""'""'""'""'""'"""""""'""'""'"""'"""'"""""""""'""'"""""""'"""'"""'""""""""'"""'""""""'"""""""'""'"""'""""""""'"""""""'"""""""'""'"""'"""'"""""'"""""""'""""""'""'""'"""'""'"""""""'""'""'"""'"""'""""'""""'"""""'""""""'"""""""'""'"""'""""""""'"""""""'"""""""'""'"""'""""""'""""""'""'""'"""'""""""""'"""""""'""'""'"""'"""'"""""'"""""""'""""""'""'""'"""'""'"""""""'""'""'"""'""""""'"""""""'""'"""'""'"""""""'"""""""'""'"""'"""""""""'""'"""""""'"""'""""""'""""""""'""""""""'""""""""'""""""""'"""""""'""'""'"""'"""'"""'"""'"""""""""'"""""'"""""""'""""""'""'"""'""'"""""""'""'"""'"""""""'""'""'""'""'"""'"""'"""""""""'"""""""'""'""'""'"""'"'"""""""'""""""""'"""'"""'"""""""""'""""""""'""""""""'""""""""'"""""""'"""""""'"""'"""""""""'"""""""'""'""'""'"""'"'""'""'""'""'""'""'""'""'""'"""'"""'""""""'"""""""'"""'""""""""'""""""'""'""'""'"""'"""""'"""""""'""""""'""'""'"""'""""""""'"""""""'""'""'"""'""""""'"""""""'"""'""""""""'"""""""'"""""""'"""'"""

## Explanation

I’m going to walk you through how the program works by showing you how it processes the specific input 202,100,1.

In the beginning, we construct a few values that we will need later — mostly the ASCII codes of the characters we will output. As you can see, '8' and '.' are already available. '|', however, is really 124, not 14. We use a while loop to add twice the temporary value in slot #1 onto it to get 124 (which is 14 + 55×2, because the while loop runs for 56−1 = 55 iterations). This saves some bytes because large integer literals like 124 are really long. In the following diagram, I show the location of every variable the program uses. Next, we want to input all the characters and store them on the tape starting at cell #12 (p is the running pointer for this). At the same time, we want to know how long the longest number is (how many digits). To achieve this, we keep a running total in unary going leftwards starting at cell #−1 (we use q as the running pointer). After the first input number (202), the tape now looks like this: You will have noticed that the numbers are off by 4. Well, when we first input them, they are their ASCII values, so they’re “off” by 48 and the comma is 44. For each character, we copy the 46 from '.' into r and then subtract it with a while loop (which subtracts 45) and then we add 1. We do that so that the comma (our separator) is 0, so we can use a conditional to recognize it.

Also, you will have noticed that we leave cell #11 at 0. We need that to recognize the boundary of the first number.

The next character will be a comma, so we store a 0 in #15, but of course this time we don’t advance q. Instead, we set q back to 0 and start “overwriting” the 1s we’ve already placed.

After all the remaining characters are processed, we get this: As you can see, the 1s written by q now indicate (in unary) the length of the longest number.

We now use a while loop to move q to the very left, and then place another pointer there which I will call r2. The purpose of r2 will become clear later. At this point, let me clarify the terminology that I will use throughout this.

• By number, I mean one of the input numbers that are separated by commas. In our example, they are 202, 100 and 1.
• By digit, I mean a single digit in a specific one of the numbers. The first number has 3 digits.
• By place, I mean the ones place, tens place, hundreds place, etc. So if I say “the digits in the current place”, and the current place is the ones place, those digits are 2, 0, and 1 in that order.

Now back to our regular programming. The entire rest of the program is a big loop that moves q forward until it reaches cell #0. Each of the cells along the way represents a place, with the ones place on the far right, and q will start at the most significant. In our example, that is the hundreds place.

We proceed by incrementing the cell q points at (that is, *q). We are now at “stage 2” for the hundreds place. In this stage, we will find out what the largest digit is among all the digits in the hundreds place. We use the same unary counting trick for this, except this time the pointer is called r and the pointer r2 marks its starting position to which we need to reset it every time we move on to the next number.

Let’s start with the first number. We begin by setting p to 11 (the hard-coded starting position of all the numbers). We then use a while loop to find the end of the number and set p2 there to mark the position. At the same time, we also set q2 to 0: Don’t be distracted by the fact that q2 is pointing into the vars. We don’t have a padding of a blank cell there because we can detect cell #0 simply because it’s number zero.

Next, we go through the current number by decrementing p and q2 together until *p is zero. At each place, the value of *q2 tells us what we need to do. 1 means “do nothing”, so we keep going. Eventually we encounter the 2 in cell #−3. Every time *q2 is not equal to 1, q2 is always equal to q. As I already stated, stage 2 is “determine the largest digit in this place”. So we set r to r2, use a while loop to decrement *p and move r leftwards and fill the tape with 1s, and then use another while loop to move r back to the right and increment *p again to restore the value. Remember that every while loop runs for one fewer iteration than the value we use it on; because of this, the number of 1s written will be 3 more (rather than 4 more) than the digit value, and the final value stored back in *p will be 2 more. Thus, this has effectively decremented *p by 2.

After that, we set p to the value of p2 and then we do all that again. For the second time, set q2 to 0, find the end of the number by moving p to the right, and then go through the digits of this number by decrementing p and q2 together. Once again we will encounter the 2 in cell #−3 and write that many 1s left of *r.

In the case of the third number, we end up doing nothing because it doesn’t have a hundreds place (so q2 never reaches q), but that’s OK because that doesn’t affect the calculation of the maximum digit value. We also set the cell *(r − 4), which I’ve marked with an unlabeled arrow here, to 1 (even though it’s already at 1). I’m not going to tell you why yet, but maybe you’ve already guessed?

The next increment of *q takes us to stage 3, which is “subtract the maximum digit from all digits in the current place”. As before, we reset p to 11 and q2 to 0 and then go through all the numbers just like we did in the previous stage; except this time, *q = 3 instead of 2. Every time q2 meets q and p is in a hundreds place, we use a while loop to decrement *p as many times as there are 1s in the block left of *r2 (5 in our example) by using r as a running pointer. We actually decrement it once more than that so that the largest digit ends up at −2, for a reason that will become clear later: After we’ve processed all the numbers, we are now at the end of stage 3. Here we perform two singular things.

• First, we also subtract the size of the r-block (plus 1) from *q, but using the r2 pointer, which leaves it on the left. *q becomes negative this way. In our case, the r-block has five 1s, so *q becomes −3.
• Second, we set a variable out to a non-zero value to indicate that we are now entering the output stage. (Technically, the fact that *q is negative already indicates the output stage, but this is too difficult to check for, hence the extra variable.)

You now understand that we keep going through the numbers, find the current place (indicated by the non-1 value of *q) within each number, and do something depending on the value of *q. We see that *q is first incremented to 2 (= calculate maximum digit value), then 3 (subtract maximum digit value from every digit in this place) and then we subtract from it to make it negative. From there, it will continue to go up until it reaches 1, thus restoring the value that means “do nothing”. At that point, we move on to the next place.

Now, when *q is negative, we’re outputting. *q is at exactly the right value so that we will output the right number of rows of characters before it reaches 1; if the largest digit is 2, we need to output 3 rows. Let’s see what happens at each value of *q:

• *q = −2:
• For the first number, *p is −2, which indicates that we need to output a '.' (dot) or a ':' (colon). We decide which by looking at q: if it’s −1, we’re in the ones place, so output a ':' (which we calculate as '8'+2), otherwise a '.'.
• For the second number, *p is −3. Anything that is not −2 means we output a '|' (pipe) and then increment the value. This way it will reach −2 in the right place and then we output '.'s/':'s for the rest of that digit.
• In each case, we also set a variable pd to 0 before we process the number, and set pd (= “printed”) to a non-zero value to indicate that we have printed a character.
• For the third number, no processing occurs because the third number does not have a hundreds place. In this case, pd will still be 0 after processing the number, indicating that we still need to output a '|' (but only if out is non-zero, because otherwise we’re still in stage 2 or 3).
• After processing all numbers, if out is non-zero, output a newline. Note that we need the out variable so that we don’t output the newline in stage 2 or 3.
• *q = −1: Same as before, except that *p is −2 for both the first two numbers, so both output a '.' (and the third outputs a '|' as before).
• *q = 0: When *q is 0, this means “do nothing if we’re in the ones place, otherwise output a row of '|'s regardless of *p”. This way we get the padding between the digits.

Now we increment q to move on to the next place, the tens place, and increment *q there. At the beginning of Stage 2, the tape looks like this: Then we perform Stage 2 just as before. Remember this effectively subtracts 2 from every digit in this place and also leaves a unary number left of *r2 indicating the maximum digit. We leave the previous unary number alone and just keep extending the tape to the left; it would only cost unnecessary extra code to “clean up”. When we’re done and we increment *q, at the start of Stage 3 the tape is now: Actually, this is a lie. Remember earlier where I said we set *(r − 4) to 1 and I didn’t tell you why? Now I’ll tell you why. It’s for cases like this one, where the largest digit is in fact 0, meaning all the digits in this place are 0. Setting *(r − 4), indicated by the unlabeled arrow above, to 1 extends the unary number by 1, but only in this special case. This way we pretend as if the largest digit was 1, which means we will output one extra row.

After Stage 3 (subtract maximum digit from all digits in the current place), including the extra step that makes *q negative, the tape looks like this. Last time the biggest digit was represented by −2 in the *p block, but this time they’re all −3 because they’re all actually zeroes but we’re pretending as if the maximum digit was a 1. Now let’s see what happens as *q progresses towards 1:

• When *q = −1, the *p values are all −3, which means we output '|'s and increment them.
• When *q = 0, we output '|' because that’s what we always do when *q = 0, regardless of *p.

Thus, we get two rows of pipes.

Finally, we move *q to the one’s place. This one gets interesting because we need to output ':'s if the actual digit is anything but 1, but an '8' if it’s 1. Let’s see how the program proceeds. First, we increment *q to initiate Stage 2: After Stage 2 (“calculate maximum digit value”), we’re left with this: After Stage 3 (“subtract maximum digit value from all digits in current place”) the tape looks like this: Now let’s go through each iteration of *q in turn:

• *q = −2:
• First number: already at −2, so output a ':' (rather than a '.' because q = −1).
• Second number: at −4, so output a '|' and increment.
• Third number: at −3, so output a '|'. However, this time, instead of incrementing, a special case triggers. Only if we’re outputting the last place (q = −1), and we’re in the second-last row for that (*q = −2), and the digit is actually a 1 (*p = −3), then instead of incrementing it to −2, we set it to −1. In other words, we use −1 as a special value to indicate that in the next iteration, we will need to output '8' instead of ':'.
• *q = −1:
• First number: already at −2, so output a ':'.
• Second number: at −3, so output a '|'. The special condition does not trigger because *q is no longer −2. Therefore, increment.
• Third number: at −1, so output '8'.
• *q = 0: Ordinarily, we would output the padding row of '|'s here, but in the special case where we’re in the ones place (q = −1), we skip that.

After this, q is incremented to 0 and the big while loop ends.

Now you know how an input like 202,100,1 works. However, there’s one more special case that we still haven’t covered. You may remember that while we were processing the last place, when *p was −3 we set it to −1 for the 1 (instead of incrementing it to −2) so that the next iteration would output an '8' instead. This only works because we have an iteration in which *p is −3 and we make a decision as to whether to increment it or set it to −1. We do not have such an iteration if all of the digits in the ones place are 0 or 1. In such a case all the *p values for the 1s would start out at −2; there is no opportunity to decide to set it to −1 rather than to increment it from −3. Because of this, there is another special-casing condition inside Stage 3 (“subtract maximum digit from every digit in the current place”). I claimed that after subtracting the maximum digit value from every digit (at which point the maximum digit is at −1), we just decrement it once more, but actually there’s a condition on that which goes as follows:

If the digit we’re looking at is equal to the maximum digit in this place (*p = −1), and this place is the ones place (q = −1), and the maximum digit is 1 (*(r + 5) = 0, i.e. the unary block at the very left is only 5 cells long), only then we leave *p at −1 to indicate that the only iteration of the output must output an '8'. In all other cases we decrement it once more.

Done. Happy new year!

• Edit 1 (3183 → 3001): Some Happy New Year golfing! I managed to get rid of the variables p2 and r2 entirely! p now races back and forth to keep finding the beginning and end of numbers, but it seems to be shorter in code. I tried to get rid of q2 as well, but I couldn’t make the code shorter that way.

I also found a few more places where I could apply typical Unreadable golfing tricks like re-using the last value of a while loop. To give you an example, instead of

while *(++p) { 1 }         // just increment p until *p is 0; the 1 is a noop
if (pd) { x } else { y }   // where pd is a variable


I can save the '"""" (do the first, then the second) and the '""" (constant 1) by writing it in a way that is kind of like

if (while *(++p) { pd }) { x } else { y }


Of course, this only works if I know that the while loop will run for at least one iteration, but if it does, its return value is pd so I can use that as the condition for the if.

• "Unreadable" is certainly an apt name... Dec 31, 2015 at 16:20
• -1 not enough explanation Jan 2, 2016 at 0:30

# Javascript (ES6) 750744690604498346245 234 bytes

I'm new to PPCG and thought I might give this one a try thinking it was rather simple. Boy was I wrong!! I've been working on it for a while and I have a lot of golfing to do...
Suggestions are encouraged! - although making sense of this won't be an easy task.

Outputs ropes when the input is an array of numbers (eg: [204, 1] ).

a=>(o=m=s="",l=a.map(n=>(s+="|",l=(n+"").length)>m?m=l:l),h=[],a=a.map((n,i)=>[..."0".repeat(m-l[i])+n].map((d,j)=>d<h[j]?d:h[j]=d)),h.map((n,i)=>{i?o+=s+
:0;for(j=n;j--;o+=
)a.map(d=>o+="|.:8"[d[i]-j<1?0:i<m-1?1:d[i]-1?2:3])}),o)


## Explanation

a=>(

o=m=s="",                      // m = max number of digits in a number, s = separator string
l=a.map(n=>(                   // l = lengths of each number
s+="|",                    // set the separator string
l=(n+"").length                 // convert each number to a string
)>m?m=l:l                    // get max length
),
h=[],
a=a.map((n,i)=>
.map((d,j)=>d<h[j]?d:h[j]=d) // set each digit of h to max
),

h.map((n,i)=>{
i?o+=s+
:0;
for(j=n;j--;o+=
)
a.map(d=>
o+=
"|.:8"[
d[i]-j<1?0
:i<m-1?1
:d[i]-1?2:
3
]
)
}),
o
)


## Example

Input: Array of numbers: [4,8,15,16,23,42]
Output:

|||||.
|||||.
||||..
||....
||||||
|:||||
|:||||
|:|:||
|:::||
::::||
:::::|
::::::
::::::

• +1 impressive golfing. Will you include an example with input and output? Dec 29, 2015 at 5:56
• @DavidC Thanks! And the example is included. Call it from the console and it returns a string. :) Dec 29, 2015 at 6:37

# Python 3, 624598595574561535532527525426345328324294288286283280267265255251245238235234230228236 244 bytes

z=input().split();v=max(map(len,z));d=''.join(i.zfill(v)for i in z);x=['']*len(z)
for k,c in enumerate(d):j=k%v;m=int(max(d[j::v]));c=int(c);x[k//v]+="|"*(m-c+(2>v)+(j>0)+0**(m+(j>v-2)))+":8."[(c<2)|-(j<v-1)]*c
for r in zip(*x):print(*r,sep='')


Try it online!

Well, since this question needed an answer, I have provided one here, where the input should be a space-separated string of numbers, such as "204 1". Boy, is it a long one. Any golfing suggestions (or better answers) are welcome.

Edit: Bytes saved by mixing tabs and spaces.

Edit: I saved a lot of bytes by changing how I obtained the digits of a number (make a list from the zero-padded string of the number, then in the body of the code, transpose to get hundreds digits, ten digits, etc.)

Edit: And I saved some more by incorporating that last :8 loop into the main quipu loop. Now if only I could figure out why b=d[j*v+i]==m(d[i::v]) won't work. Figured it out and the solution takes too many bytes. (Also the byte count dropped because somehow the tabs turned back into four spaces. It was probably the code block formatting on this site)

Edit: I reorganized how made the quipus. Now it creates one strand at a time, then transposes for printing.

Edit: Turned my answer back into a Python 3 program to save some more bytes.

Edit: I found a bug in my code that made it so that it was not printing zeroes in the middle of numbers correctly (see test case 204 1 above). In fixing this, I managed to golf it :)

Edit: I changed the printing to save 10 bytes. And I put all of the old byte counts back, just because.

Edit: Golfed the assignment of v using map for four bytes. Credit to CarpetPython, as I got the idea from their answer here.

Edit: Turned the middle "for loop inside a for loop", into just one for loop for six bytes.

Edit: Now using enumerate. No longer using l=len(z). Turned the ternary if-else into a list ternary. See below for details.

Edit: Sp3000 suggested an edit to print and an edit to the ternary condition that saved a byte each.

Edit: +6 bytes fixing a bug where the code did not follow rule 7. +8 more bytes in continuing to fix this bug. O bytes from more bug fixes.

Ungolfed:

s = input()
z = s.split()
v = max(map(len, z))                # the amount of digits of the largest number
d = ''.join(i.zfill(v) for i in z)  # pad zeroes until every number is the same length
# then join the numbers into one string
x = ['']*len(z)                     # a list of strings for the output, one for each number

for k,c in enumerate(d):          # for every digit in every number
i,j = divmod(k, v)            # i is the index of the number we're on
# j is the index of the digit of the number we're on
m = int(max(d[j::v]))         # the largest of all the digits in the j-th place
c = int(c)                    # the digit in j-th place of the i-th number
x[i] += "|"*(m-c+(j>0)+0**(m+(2>v)*(j>v-2)))
# pad | to size m-c, until the knots are correctly spaced
# add a | if all inputs are single-digit numbers
# add a | if j>0, if it's not at the start, as delimiters
# add a | if the maximum is 0 and it's *NOT* the ones digit
x[i] += ":8."[(c<2)|-(j<v-1)] * c
# this is essentially the following code
# if j<v-1:
#     x[i] += "."*c      # . knots if not in the units place
# else:
#     if c == 1:
#         x[i] += "8"*c  # 8 knots for ones in the units place
#     else:
#         x[i] += ":"*c  # : knots for something else is in the units place

for r in zip(*x):       # transpose so all the rows (the quipu strings) now hang down
print(*r, sep='')    # join the strings together at each knot
# and print each on a separate line

• Is there anything specific to Python 3 here? If not, converting it to Python 2 could save quite a few bytes Dec 26, 2015 at 7:32
• @Cyoce Nothing too Python 3 specific. I just started in Python 3 because that's the version I have. I'll test a Python 2 version on ideone or something. Dec 26, 2015 at 7:43
• @Maltysen That doesn't work with an inputs that start with 0, such as 0 12 4. Dec 26, 2015 at 8:31
• You can save some bytes by alternating tabs and spaces for indentation in Python 2. I believe 1 tab character == 8 spaces according to python's indentation parser Dec 26, 2015 at 20:14
• for r in zip(*x):print(''.join(r)) -> print(''.join(r)for r in zip(*x)) May 9, 2016 at 10:53

# C, 238 235 bytes

Relying heavily on the C preprocessor to make the code as short as possible. As a side effect, also makes it pretty much unreadable.

#define l strlen(a[i])
#define d l<k||a[i][l-k]-48
#define m(e) for(i=1;a[i];e<=r?i++:r++);
#define p(e) {m(!putchar(e?'|':k>1?46:d<2?56:58))puts("");}
k,r;main(int i,char**a){m(l)for(k=r,r=1;k;r=k>1){m(d)for(;r;r--)p(d<r)if(--k)p(1)}}


On Ubuntu 14.04, you can compile the code with a straightforward gcc quipu.c (please ignore the warnings). An example of running the executable:

\$ ./a.out 1 2 3 2 1
||:||
|:::|
8:::8


Tested against all OP's test cases.

Ungolfed source code:

// Standard library; leaving out the includes still gives executable code despite the warnings.
#include <stdio.h>
#include <string.h>

// 4 preprocessor macros.
// Note: some of these actually make use of the fact that parentheses have been left out

// l: length of argument i
#define l     strlen(a[i])

// d: shorthand for a digit
#define d     l<k || a[i][l-k]-'0'

// m: loop across all arguments; calculates r as the maximum of expression e
#define m(e)  for (i=1; a[i]; e<=r ? i++ : r++);

// p: prints one line of output
// note: intentionally does not use the r++ code branch of m;
//       putchar always returns a non-zero number here, so !putchar is zero,
//       which is always <=r (because r is never negative)
// note: the semicolon after m(...) is redundant;
//       the definition of m already contains a semicolon
// note: puts("") outputs a newline
#define p(e)  { m(!putchar(e ? '|' : k > 1 ? '.' : d < 2 ? '8' : ':')); puts(""); }

// k: knot position; 1 for units, 2 for tens, 3 for hundreds...
int k;

// r: input and output value for m
// note: the first time we call m, we need r to be zero;
//       by defining it outside main, it is automatically initialized as such
int r;

// function main
// note: parameter i (normally named argc by convention) is not needed
//       (the last element of argv is known; it is followed by a NULL pointer)
//       but we cannot leave it out (otherwise we cannot access argv)
//       so it serves as a local variable (to loop through arguments; see m)
// note: parameter a (normally named argv by convention)
//       is the array of arguments (starting from index 1)
int main(int i, char **a)
{
// Determine the longest argument; store its length in r.
// This is the number of knot positions to consider.
m(l)

// Iterate k through the knot positions from top to bottom.
// Note: k > 0 has been abbreviated to k.
// Note: for each iteration, we also initialize r with either 0 or 1.
//       0 = suppress printing when all knots are zero
//       1 = always print, even when all knots are zero
for (k = r, r = 1; k > 0; r = k > 1)
{
// Determine the highest digit at this knot position.
// Note: due to the absence of parentheses, d mixes up with <=r into:
// (l < k) || (a[i][l-k]-'0' <= r)
m(d)

// Count the digits down.
for (; r; r--)
{
// Print a single line of output.
// When d (the digit in the current strand) is less than the counter,
// then print a '|', otherwise print a knot.
p(d < r)
}

// Decrement k (go to next knot position).
// If this was not the last iteration...
if (--k > 0)
{
// Print separator line.
p(1)
}
}

// Return exit code zero; redundant.
return 0;
}

• Congratulations! As the shortest answer posted within the bounty period, you've received my bounty of +50 rep. Nice answer! :) Jan 2, 2016 at 6:26

# Mathematica 436 453 357 352 347 bytes

t=Transpose;y=ConstantArray;a=Table;


The above

• Breaks up each integer into a list of digits, using IntegerDigits; pads each number with zeros to the left (to ensure equal spacing); each input number, now decomposed into digits, corresponds to a row of an array; each column represents a place value. Array is transposed.
• Replaces digits with a list of knots with padding. A slightly different pattern-matching routine is used for the units.

Example

g[Range[0, 50]] • Transpose@Join? That should be a t,right? Apr 8, 2016 at 4:54
• Yes. Thanks for catching this. Apr 8, 2016 at 13:19
• Space right before that. Apr 8, 2016 at 14:36

# Pyth, 64 bytes

=QjRTQjCmj\|+:R"[8:]"\.PdedCmm+*\|-h.MZdk*?tk\:\8kdC.[L0h.MZlMQQ


Try it online!

## How it works

=QjRTQ   Converts each number in input to decimal (as a list)
123 becomes [1,2,3]

----

jCmj\|+:R"[8:]"\.PdedCmm+*\|-h.MZdk*?tk\:\8kdC.[L0h.MZlMQQ

h.MZlMQ   (max length) times

C              transpose

mm                    d    for each digit:
+                        [convert to] sum of
*\|                     "|" repeated
-                    the difference of
h.MZd               maximum digit in same row
k              and itself.. that many times
*?tk\:\8      and (digit > 1 then ":" or "8") repeated
k     itself many times

the list:
[11,23,52]
->[[1,1],[2,3],[5,2]]
->[[1,2,5],[1,3,2]]
->[["||||8","|||::",":::::"],["||8",":::","|::"]]

C      transpose

->[["||||8","||8"],["|||::",":::"],[":::::","|::"]]

m                          for each number
+                      [convert to] sum of
Pd          every element but the last
:R"[8:]"\.            with "8" and ":" replaced by "."
ed        and the last element
j\|                       joined with "|"

C                          transpose
j                           join (with newlines)

• This is an excellent answer except for one problem. The final knot is not always a figure-eight knot 8. In fact, it's only an 8 knot when last digit is a 1 (see rule 6). You're converting all the final knots and that doesn't match the spec. Also, your Try It Online! link has different code from that which is posted here, apparently May 9, 2016 at 5:10

# R - 446 444

I see there is no R solution yet, so here is one. The function takes a vector with integers.

function(x){r=nchar(max(x));c=length(x);m=matrix(0,r,c);for(i in 1:c){t=as.numeric(strsplit(as.character(x[i]),"")[]);m[(r+1-length(t)):r,i]=t};Q=c();for(i in 1:r){d=m[i,];z=ifelse(max(d)>0,max(d),1);q=matrix("|",z,c);for(j in 1:c){v=m[i,j];if(i==r){if(v==1)q[z,j]=8;if(v>1)q[(z-v+1):z,j]=rep(":",v)};if(i<r){if(v>0)q[(z-v+1):z,j]=rep(".",v)}};if(i!=1&sum(d)>0)q=rbind(rep("|",c),q);Q=rbind(Q,q)};for(i in 1:nrow(Q))cat(Q[i,],sep="",fill=T)}


## Ungolfed

# Some test data
test <- c(201, 0, 100, 222, 53)

# Define function
quipu <- function (x) {

# Create matrix with a row for each digit and a column for each number
r=nchar(max(x));c=length(x);m <- matrix(0,r,c)
for(i in 1:c) {
t=as.numeric(strsplit(as.character(x[i]),"")[])
m[(r+1-length(t)):r,i]=t
}

# Loop through each row (digit) starting at the top of the quipu
Q=c() # Empty matrix to store quipu
for(i in 1:r){

d=m[i,]
z=ifelse(max(d)>0,max(d),1)
q=matrix("|",z,c)

# Loop through each column (number in the vector) starting at the leftmost quipu
for(j in 1:c){

# The digit
v=m[i,j]

# If it is the last segment of the quipu
if(i==r){
if(v==1){q[z,j]=8} # If unit digit =1
if(v>1){q[(z-v+1):z,j]=rep(":",v)} # If unit digit >1
}

# If it is not the last segment of the quipu
if(i<r){
if(v>0){q[(z-v+1):z,j]=rep(".",v)} # If non-unit digit >0
}
}

if(i!=1 & sum(d)>0){q=rbind(rep("|",c),q)}
Q=rbind(Q,q)
}

# Print quipu
for(i in 1:nrow(Q)) {cat(Q[i,], sep="", fill=T)}
}

# Test
quipu(test)

• Do you need if(v>0) in your if(i<r) clause? Does R accept a range like z+1:z when v==0? If so q[z+1:z,j] wouldn't be affected at all, I would think. Also, does R have an else keyword and some sort of else if keyword? If so, you'd be able to golf some of these conditionals. May 14, 2016 at 14:00
• if(v>0) is needed because if v=0, the index will be out of bounds (i.e., tries to get row nrow+1). R does have else, and I actually tried your suggestion and used else where possible, but it turned out to be the same number of bytes. May 17, 2016 at 17:33

# APL (Dyalog Unicode), 160137132128123117126119 116 bytes

I sort of wrote this in a frenzy while unable to sleep tonight, so please forgive me if I skip the explanation, and miss any bugs or golfing opportunities. I'll take another look at this after I've gotten some sleep.

Edit: Turns out this doesn't for an input of just 0. Will fix soonest. -23 bytes from bug fixing! -5 bytes from removing no-longer-used variables. -4 bytes from using parts of the approach used in Leaky Nun's excellent Pyth answer (Sidenote: this answer is now exactly twice as long as that answer :D). -5 bytes from rearranging the result matrix mid-computation. -6 bytes from slimming down the generation of the result matrix even further. +9 bytes bug fixing. 0 bytes from more bug fixing. -7 bytes from rearranging how to handle the ones digit special case. -3 bytes from continued rearranging.

{⎕IO←0⋄m←⊖↓10⊥⍣¯1⊢1,⍵⋄1↓⊃⍪/⌽{w←1↓⊃m[⍵]⋄M+←1+0*((1≠≢m)∧⍵=0)+M←⌈/w⋄⊃,/⍵{M 1⍴((M-⍵)/'|'),⍵/'.'(':8'[1=⍵])[0=⍺]}¨w}¨⍳≢m}


Try it online!

Explanation

quipu←{
⎕IO←0         ⍝ Set to 0-indexing
nums ← 1,⍵    ⍝ Append a 1 so that when converting in the next line,
⍝ we always get a matrix, not a vector
digit_list ← ⊖ ↓ 10⊥⍣¯1⊢ nums    ⍝ All the digits in each digit place, reversed

⍝ For each digit in each digit place, create knots
1↓⊃⍪/⌽{    ⍝ After generating knots, these instructions clean up the result
q ← ⍵
w ← 1↓ ⊃ digit_list[⍵]    ⍝ 1↓ drops the 1 we appended before
M +← 1+ 0* ((1 = ≢digit_list) ∧ (⍵ ≠ 0)) + M ← ⌈/w
⍝ M is the number of knot spaces (including delimiters) needed
⊃,/{     ⍝ ,/ concatenates the knots