Search

mardi 28 février 2017

Cipher: ASCII shift

Context

The Shift cipher (also known as Caesar cipher) is a very simple and basic encryption technique.
Basically, each letter from alphabet is substituted by the Nth letter after this one in the alphabet (wrapping on alphabet's end to the beginning). The value of N is the cipher key.
Here, let's have a similar algorithm based on ASCII codes. We can of course add a lot of checks to consider only letters, have different behaviors on upper cased / lower cased chars, wrap on alphabet, ... but we will keep it short and simple.
Our cipher will just read a key N (number in its decimal form), then a comma separator, and finally each char will be displayed with an offset of N.
The deciphering tool can be based on the same code, with only one instruction to be replaced (the offset is taken as a negative number)

Initial state

  • Memory: empty
  • Cursor: first cell
  • Input: N,text to encrypt / decrypt

Process

  • Read key
    • Read char
      • If it's a comma, stop reading key
      • Otherwise, consider char as next decimal digit of the current key
  • Cipher / decipher engine
    • Read char
    • Add / remove offset
    • Print char
    • Loop

Code - cipher - try it

read key and comma separator
>,[>++++[-<----------->]+<[---->++++++++[-<<[->+>>+<<<]>>>[-<<<+>>>]<]<[-<+>],>]>[->]<<]

read chars then shift and print
>,[<<[->+>+<<]>[-<+>]>.,]

 Code - decipher - try it

read key and comma separator
>,[>++++[-<----------->]+<[---->++++++++[-<<[->+>>+<<<]>>>[-<<<+>>>]<]<[-<+>],>]>[->]<<]

read chars then shift and print
>,[<<[->+>-<<]>[-<+>]>.,]

 Final state

  • Memory: key 0 0
  • Cursor: third cell
  • Input: empty
  • Output: ciphered text

jeudi 23 février 2017

Sort: Counting sort

Context

Counting sort is a sort algorithm dedicated to lists having values in a small set. Then, one can create an array of same size and store values (or count) while reading.
Using BrainFuck, all our values are stored between 0 and 255. Therefore, it might be an interesting sort algorithm. Let's implement it.
Initial state
  • Memory: 0 0 [array] 0 0 0 0 0 ... where array is a 2 cells block array of nullable integers
  • Pointer: just after array
  • Input: any

Process

  • Generate counter array
    • That's a 2 cells block array of nullable integers, of size 256
    • Generate the first cell
    • Then have 255 in a counter and generate all cells after
  • For each integer N from the list
    • Access item N in array and increment counter
  • Optionally, we can rebuild back the array when sort is completed
    • Define current value (initially: 0)
    • For each counter C in counter array
      • Add current value C times
      • Increment current value

Code - try it

generate counter array: 2 cells array with 256 items
->>>>>+<<<<<[->>>>>[>>]+[<<]<<<]

read integers one by one and store into array
go to first integer and loop
<<[<<]>>
[
  remove cell indicator
  -
  move value to end
  >[->[>>]>+<<<[<<]>]
  go to value and move backward
  >[>>]>[-<+>]
  access array cell given by index
  <[->>>[>>]+>>->[-<<+>>]<<<[<<]<]
  increment count for given value
  >>>[>>]>>>+
  move array back to position
  <<<<<[[->>+<<]>[->>+<<]<<<]
  go to next integer
  <<<[<<]>>
]

rebuild array of values
>>>>>
[
  move cell
  -<<+>>>[-<<+>>]<<
  add value N that many times
  [
    -
    go to current value
    <[<<]<
    copy / move to end of array
    [->+>+<<]>[-<<<[<<]>+>[>>]>]
    set cell flag
    <<<[<<]+[>>]
    restore current value copy
    >>[-<<+>>]
    go back to value counter
    >[>>]<
  ]
  increment current value
  <[<<]<+
  >>>[>>]>>
  go to next cell and loop
]

clear counter array
<<<<[->[-]<<<]

Final state

  • Memory: 0 0 [sorted array] 0 0 0...
  • Cursor: 2 cells after array
  • Input: unchanged
  • Output: unchanged
Notes
  1. Due to our array rebuild, the sort is descending, but this can be easily changed to ascending sort
  2. Despite a shorter algorithm, execution is really longer than Quicksort

mercredi 22 février 2017

Sort: Quicksort

Context

After Bubble sort, let's see how to implement another algorithm : Quicksort.
Quicksort is really efficient, its mechanism is:

  • Select a pivot cell (any cell from the list is fine, we will pick the last one though it has been proven that's the worst case)
  • For each other cell
    • If value is greater than pivot: move to set B
    • Otherwise, move to set A
  • Then, recursively sort A and B, and finally rebuild the concatenation [ sorted A ] + pivot + [ sorted B]
Example: let's sort 4, 5, 3, 1, 2
  • Pivot: 2, and we generate one set [1] and one set [4, 5, 3]
    • Sort [1] -- done
    • Sort [4, 5, 3]: pivot is 3 and generates only one set [4, 5]
      • Sort [4, 5]: pivot is 5 and generates set [4]
        • Sort [4] -- done
      • Merge: [4, 5]
    • Merge: [3, 4, 5]
  • Merge: [1, 2, 3, 4, 5]

Initial state

  • Memory: 0 ... (fifteen times) ... [array length] 0 0 [2 cells array to handle null values] 0 0 ...
  • Initial pointer:  just after array
  • Input: any

Process

  • Note: array length can be either computed (see this post - though it's for 1 cell array only but can be adapted easily), or directly computed while reading input if integers are provided by end-user
  • We will define a queue of ranges to process
    • Memory will looks like 0 0 ... 0 0 [array] 0 0 0 [queue of ranges to process]
    • Range: first item index + range length
    • First range: {1, array length}, because we will process the whole array
    • Therefore, move array length and store value 1 in the queue
  • Pick one range definition from queue
  • Split array to isolate range
    • Items before range start index are moved to the far left
    • Items after range index are moved to the (just a bit less) far left as well until range length is reached
    • Items after that are unchanged
    • Memory : 0 0 [out of range] 0 0 [range to process] 0 ... 0  [out of range] 0 ... 0 [queue]
  • Then
    • Select a pivot (last item in range to process)
    • Store a copy
    • This will be our memory during range processing
      [range to process] 0 0 [processed left] pivot 0 pivot 0 0 0 0 [processed rigth]
    • For each other item to process
      • Store a copy
      • Compare to pivot
        • Pivot is greater than value : move value to processed left
        • otherwise
          • move value to processed right
          • move processed left to the left
          • move pivot and copy to the left as well
        • Note: this allows an in-place processing...
    • Count items in processed left and merge with pivor
    • Merge the whole array
      [out of range] [ processed left] pivot [processed right] [out of range]
    • Compute new range definitions - current range starts at S and has length L
      • If processed left's length L' is not null: we need to process this block, defined by a start at S and this particular length L'
      • Processed right range definition is given by start at S + L' + 1 and length L - L' - 1
      • For both ranges, if length is not null, then enqueue range definitions
  • Finally, shift the queue to the left (as first elements have been removed), and loop on new range definition
  • Stops when no range definition is available (array is sorted)
move count and create first range
<<[<<]<[->>>[>>]>>>>+<<<<<<[<<]<]>>>[>>]>>>+

while there is a range to process
[
  copy range start index and split array at this position
  <+>-[-<<<<<[<<]>>[-<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>]>[-<<<<<<<<<<<<<<<<+>>>>>>>>>>>>>>>>]>[>>]>>+>]

  copy range length and split array for this number of items
  >[-<<<<<<[<<]>>[-<<<<<<<<<<<<<<+>>>>>>>>>>>>>>]>[-<<<<<<<<<<<<<<+>>>>>>>>>>>>>>]>[>>]>>>+>]

  go to array isolated range
  <<<<<<[<<]<<<<<<<<<<<<<<

  copy pivot
  ->[->>>+>>+<<<<<]

  go to first element to process
  <<<

  while there is an element to process
  [
    copy element for save and comparison
    ->[->>>[>>]>>>+>>+<<<<<<<[<<]<]>>>[>>]>>>>
    compare copies
    >>+<<[->[->]>[<<[-]>>->>]<<+<<]>[[-]<+>]>-<+<
    [>-<
      pivot is less than current: move current into the right part
      -<[->>>>>>>+<<<<<<<]>>>>>>+
      move the left part to reuse free space
      <<<<<<<<<<<[<<]>>[[-<<+>>]>[-<<+>>]>]
      move pivot for same reason and copy it
      >>[-<+<+>>]<[->+<]>>
    ]
    >[-
      pivot is greater than current: move current into the left part
      <<[-<<<<<[<<]>+>[>>]>>>]<<<<<[<<]+[>>]
      copy pivot
      >>[->+>+<<]>[-<+>]>>>
    ]
    in all cases current position is 2 cells after second pivot copy
    go to next cell to process
    <<<<<<<<[<<]<<
  ]
  reset pivot copy
  >>>>[>>]>>[-]
  count left part length and merge
  <<<<[
    move
    ->[->>+<<]>+
    increment counter
    >>[>>]>>>>>+<<<<<<<[<<]<<
  ]
  include pivot
  >>>>[>>]+>>[-<+>]
  move left part length outside array
  >>>[->>>>[>>]>>>[>>]>+<<<[<<]<<<[<<]<<]
  rebuild whole array
  move right part of sorted range
  >>>>[>>]<<[->[->>>+<<<]>>+<<<<<]
  move left part of sorted range and pivot
  <<<<<<<[->[->>>>>>>>>>+<<<<<<<<<<]>>>>>>>>>+<<<<<<<<<<<<]
  move left part outside range
  <<<<<<[->[->>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<]>>>>>>>>>>>>>>>+<<<<<<<<<<<<<<<<<<]

  compute next range(s) definitions
  got to left part length
  >>>>>>>>>>>>>>>>>>[>>]>
  [
    if not null: add left range definition
    start: same as current start
    length: given by current value

    copy and push start
    >[-<<+>>>>>[>]>+<<[<]<<]<<[->>+<<]
    copy and push length
    >[-<+>>>>>[>]>>+<<<[<]<<<]
    add new range on queue
    >>>>[>]>[[-<+>]>]<<[<]<<<
    move left part length to the left to break loop (which is actually an if)
  ]
  compute second possible range start (current range start increased by left range length and pivot)
  compute as well second possible range length (current range length decreased by left range length and pivot)
  <[->>+>-<<<]>>+>-
  [
    if length is not null: move to queue
    <[->>>[>]>+<<[<]<<]
    >[->>[>]>>+<<<[<]<]
    >>[>]>[[-<+>]>]<<[<]<
  ]
  reset second range length if needed
  <[-]
  shift the queue
  >>>[[-<<+>>]>]
  go to next range definition in queue
  <<<[<]>
]

Final state

  • Memory: [eighteen zeros] [sorted array] 0 0 0 0
  • Cursor: 4 cells after array
  • Input: unchanged
  • Output: unchanged
Note: one can check that this code, even if more complex, is really faster, even in brainfuck, than the bubble sort implementation.

mardi 21 février 2017

Sort: Bubble sort

Context

There are multiple ways to sort an array in computer theory. One of them is called Bubble sort.
This algorithm is clearly not the best one, though it's quite easy to implement:

  • Consider 2 adjacent cells
  • Compare values
    • If second is smaller, then swap the 2 values
  • Move to next pair of 2 cells (the one that includes the second element of my current pair)
  • When reaching end of array, redo the whole loop as long as at least one swap has been made (sort is not over)
The name comes from the behaviour of biggest / smaller elements (according to the sort direction) : they are pushed to the top of the list like bubbles in liquids.

Let's write our own bubble sort implementation

Initial state

  • Memory: 0 0 [array] 0 0 0 0 0 0 ... where array is an array of nullable integers (2 cells per item, one set to 1 and second set to the value)
  • Cursor: just after array
  • Input: any

Process

  • Set a loop indicator to 1 and start a loop
    • Reset loop indicator
    • Go to last two cells
      • Move and copy the second cell to the right (leave enough space for comparison)
      • Copy first cell value
      • Compare first and second value copies
      • If second is smaller than first
        • Swap the 2 values
        • RESET the loop indicator and set to 1. Reset is mandatory, otherwise (simply adding 1) will fail if there are 256 changes (loop indicator will then be 0). Here, instead, let's just have the indicator reset and set to 1
      • Move to the next pair if any
    • Move array back to its original location
    • Go back to loop indicator and loop if needed

Code - try it

set loop indicator and loop
>>>>>>>+[
  reset indicator and go to last 2 cells
  -<<<<<<<<<<<
  while there are 2 cells to compare
  [
    copy / move second cell
    >>->[->+>>>>>+<<<<<<]>>>>>+<<<<[-<+>]
    copy first cell
    <<<[->+>>+<<<]>[-<+>]
    compare 2 values
    >>>+<<[->[->]>[<<[-]>>->>]<<+<<]>[[-]<+>]>-<<
    [-
      second is smaller : swap
      temporarily move first cell
      <<[->+<]
      move first cell
      >>>>>>>>[-<<<<<<<<+>>>>>>>>]
      reset and set loop indicator
      >[>>]>[-]+<<<[<<]<<<<
      move second value
      [->>>>>>>+<<<<<<<]
      go back to position before if
      >
    ]
    move to last 2 cells and loop
    <<<<<
  ]
  move array back to initial location
  >>>>>>>>>>[[-<<<<<<+>>>>>>]>[-<<<<<<+>>>>>>]>]
  go to loop indicator and loop
  >
]

Final state

  • Memory: 0 0 [array] 0 0 0 0 0 0
  • Cursor: 6 cells after the array
  • Input: unchanged
  • Output: unchanged

lundi 20 février 2017

Pascal's triangle

Context

Pascal's triangle is a triangular array of binomial coefficients: cell k of row n indicates how many combinations exist of n things taken k at a time.
Pascal's triangle looks like
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
...
One can prove that a given cell  is the sum of 2 cells from previous row: the one just above and the one on top left.
Given that, let's see how we can generate the Nth row of Pascal's triangle

Initial state


  • Memory: N 0 0 0
  • Cursor: on third cell
  • Input: any

Process

  • First, let's initialize the first row of the triangle
    • Set third cell to 1
  • Then, while our counter N is not null
    • Decrease counter
    • Generate next row
      • Go to array's last cell
      • Move current cell twice on the right (next 2 cells)
      • Go to previous cell and loop
    • This will generate next row. Actually, move is a "decrease here / increase there" process, so the first moves will transform 
      • ... A B C 0 into ... A B 0 C C
      • then into ... A 0 B (B+C) C
      • then into ... 0 A (A+B) (B+C) C
    • Finally, move the whole new row one cell to the left (because of an extra 0 introduced by steps above)
    • Go back to counter

Code - try it

initialize first row
>>+<<
[
  decrease counter
  -
  go to last cell
  >>[>]<
  [
    move twice
    [->+>+<<]
    go to previous cell
    <
  ]
  move whole array to the left
  >>[[-<+>]>]
  back to counter
  <<[<]<
]

Final state


  • Memory: 0 0 Nth row of Pascal's triangle
  • Cursor: first cell
  • Input: unchanged
  • Output: unchanged 
Note: because of cell size limitation, the row is computed modulo 256.
And because of that and 'one-cell' array implementation, this algorithm will fail as soon as one cell equals 0 modulo 256.
Anyway, this should not occur until counter = 256, meaning that it would overflow the cell limit for counter itself.
Any triangle row generated through this algorithm is therefore valid as long as it can be generated.

vendredi 17 février 2017

Integer square root, round 2

Context

The Newton's method we used to compute integer square root is generally a very efficient algorithm to compute integer square roots.
However, using BrainFuck, the algorithm itself takes time to be processed, a lot of instructions are needed in order to iterate on different approximations.

Another intuitive but generally inefficient way to compute integer square root is to iterate on all integers and compute their squares until we go over our target number.
A bit less naive approach is to compute the difference between 2 squares, and subtract this difference to N while it's positive.
Example: 27

  • 1: remove 1, result is 26
  • 2: remove 3 (2*2 - 1*1), result is 23
  • 3: remove 5 (3*3 - 2*2), result is 18
  • 4: remove 7 (4*4 - 3*3), result is 11
  • 5: remove 9 (5*5 - 4*4), result is 2
  • 6: remove 11 (6*6 - 5*5), result is negative, so integer square root is 5
We'll see that, due to the nature of BrainFuck, this algorithm is actually really more efficient than Newton's method.

Initial state

  • Memory: A 0 0 0 0
  • Cursor: first cell
  • Input: any

Process

  • Store 1 (square difference) on third cell and 1 (actual result) on fourth
  • While A is not null
    • remove 1
    • remove 1 to square difference
    • If square difference is null
      • Copy actual result's double on third cell
      • Increase both actual result and cell square difference by 1
  • Decrease result by 1 (because the current result is over our target)

Code - try it

start from 1
>>>+>+<<<<

while N is not null
[
  decrease N
  -
  set else bit
  >>+
  decrease square difference
  >-
  if current square difference is null
  [<-]<[-
    reinit square difference
    copy current integer double
    >>[-<++>>+<]>[-<+>]<
    increase both integer and square difference by 1
    +<+
    <
  <]
  <
]
decrease result by 1 and print
>>>[-]>-
[>>>>++++++++++<<<<[->+>>+>-[<-]<[->>+<<<<[->>>+<<<]>]<<]>+[-<+>]>>>[-]>[-<<<<+>>>>]<<<<]<[>++++++[<++++++++>-]<-.[-]<]

Final state


  • Memory: 0 0 0 D R with R=isqrt(A) and D the remaining part of the square difference 
  • Cursor: first cell
  • Input: unchanged
  • Output: unchanged
This algorithm takes about 15 times less instructions to be executed...

Fibonacci numbers

Context

Fibonacci sequence F is defined by :

  • F(1) = F(2) = 1
  • F(n+2) = F(n+1) + F(n) for any n > 1
Hence, the sequence is 1, 1, 2, 3, 5, 8, ...

Let's display all Fibonacci numbers up to the Nth one, N given by user, using a comma-separated display

Initial state

  • Memory: empty
  • Cursor: first cell
  • Input: a number N > 1

Process

  • First, let's store the comma code, to display it more quickly
  • Then, read the number N
  • Initialize the sequence with values 1 and 1
  • Display "1,1" as the first sequence values
  • Decrease counter N by 2 (because first 2 values are already displayed)
  • While counter is not null
    • Decrease counter
    • Copy the second value of the sequence
    • Compute the sum of the 2 current sequence values
    • Store the second's copy as first, and sum as new second
    • Print comma
    • Copy / print newly computed sum

Code - try it

store comma
>++++[-<+++++++++++>]

read number
>,[>++++++[-<-------->]>+++++++++[-<<<[->+>+<<]>>[-<<+>>]>]<<[-<+>],]

print first numbers (1)
<<+++++.-----.+++++.-----

initialize sequence
>-->+>+

start loop
<<
[-
  move / copy second
  >>[->+>+<<]
  sum first / second
  <[->>>+<<<]
  move second to first place
  >>[-<<+>>]
  move / copy sum
  >[-<<+>>>+<]
  print comma
  <<<<<.
  print number
  >>>>>>[>>>>++++++++++<<<<[->+>>+>-[<-]<[->>+<<<<[->>>+<<<]>]<<]>+[-<+>]>>>[-]>[-<<<<+>>>>]<<<<]<[>++++++[<++++++++>-]<-.[-]<]<<<<
]

Final state

  • Memory: 44 0 F(N-1) F(N)
  • Cursor: second cell
  • Input: empty
  • Output: all Fibonacci numbers up to F(N), comma-separated


jeudi 16 février 2017

Display all prime numbers

Context

A prime number is a number that allows only 2 distinct divisors: 1 and itself.
First prime numbers are 2, 3, 5, 7, 11 ...
Computing all prime numbers up to a limit can be implemented using a sieve, that works like this

  • Generate an array of all integers from 2 up to the limit
  • Go to the first non-null item in the array
    • This number N is prime
    • Then, reset array items every N cells starting from this one
Example: 2 is prime, and all multiple of 2 are removed, then 3 is a prime and all multiples of 3 are removed, including 6 that will be reset twice (once for 2, and once for 3), then 4 is null (reset by 2), so move to 5, ...

Initial state

  • Memory: empty
  • Cursor: first cell
  • Input: any

Process

  • First, define a 'comma' on first cell (code: 44), because we'll display comma-separated numbers, so it avoids building the comma everytime.
    • Note: this is equivalent to a constant value in other programming languages...
  • Then, generate all numbers from limit to 2, in decreasing order, in a 2-cells array, but this time it'll be [{value, 1}]  instead of [{1, value}] (code is a bit shorter)
    • Generate the limit (using '-', we'll have 255, the biggest possible value)
    • Set next cell to 1
    • While current value is not null
      • Copy 2 cells ahead
      • Set next cell to 1
      • Decrease copied value by 1, and stay on this value
    • Result will be an array from 255 to 0, let's just remove the extra cells at the end.
  • Finally, go to the first cell
    • If value is not null
      • Print the number
      • Print comma
      • Copy / move it twice: one for backup, one for counter
      • Iterate on all array cells
        • Move 2-cells block to the right
        • Decrease counter. If null
          • Reset copied cell
          • Reset counter to initial value (copy from backup)
      • Move whole array at its original position
    • Go to next cell

Code - try it

generate comma
>++++[-<+++++++++++>]
generate all numbers from max to 2
>-[[->+>+<<]>[-<+>]+>-]<-<-<

[
  go to current number
  -<
  [
    number is not null: prime
    copy twice and print
    [->>+>>+<<<<]
    >>>>[>>>>++++++++++<<<<[->+>>+>-[<-]<[->>+<<<<[->>>+<<<]>]<<]>+[-<+>]>>>[-]>[-<<<<+>>>>]<<<<]<[>++++++[<++++++++>-]<-.[-]<]
    print comma
    <<<<[<<]<.>>>[>>]
    copy counter
    >[->+>>+<<<]>[-<+>]<

    for each cell
    <<<[
      move
      -<[->>+<<]>>>+
      go to counter and decrease then check if null
      [>>]+<-[>-]>[-
        reset last copied cell
        <<[<<]>[-]>[>>]
        reset counter
        >>[-<+<<+>>>]<[->+<]
      ]
      process next cell
      <<<[<<]<<
    ]
    move back all cells
    >>>>[<[-<<+>>]<+>>->>]
    clear counters
    <[-]>>>[-]<<<<<
  ]
  process next number
  <
]

Final state

  • Memory: 44 0 0 0 (44 is code for comma)
  • Cursor: second cell
  • Input: unchanged
  • Output: all primes below 255, comma separated, with extra comma - note that extra comma can be removed by adding a Backspace character (code = 10). Simply add ++++++++++. at the end of the code.

Integer square root using Newton's method

Context

The integer square root is the operation that returns the integer part of a square root. Namely, isqrt(n) is the biggest integer k so that (k+1)^2 > n.
In most programming languages, there is a fast implementation for this operation, using Newton's method.

Considering a function f, so that f(z) = 0 and let's find z: Newton's method states that, starting with a 'good' value x0, we can define x1 = x0 - f(x0)/f'(x0), and use this formula as a recursive definition, to finally converge to z.

Applied to square root, we have a function f(x) = x^2-n, which is null for x = sqrt(n). So, starting with x0=n, and using the recurrence formula x(k+1) = (x(k) + n/(x(k)) / 2, we can converge to n's square root.

And applied to integers, this gives us:

  • Start with X = N
  • Define Y = (X+N/X) / 2
    • N/X is euclidian quotient
    • division by 2 can be done using a shift...
  • If X > Y then assign value Y to variable X and loop
  • Returns X
Example: sqrt(57)
  • X = 57, Y = (57 + 57/57) / 2 = 29
  • X = 29, Y = (29 + 57/29) / 2 = 15
  • X = 15, Y = (15 + 57/15) / 2 = 9
  • X = 9, Y = (9 + 57/9 ) / 2 = 7
  • X = 7, Y = (7 + 57/7) / 2 = 7
  • Result is 7, and actually 7x7 = 49 < 57 < 8*8 = 64
Example (limit case): sqrt(63)
  • X = 63, Y = (63 + 63/63) / 2 = 32
  • X = 32, Y = (32 + 63/32) / 2 = 16
  • X = 16, Y = (16 + 63/16) / 2 = 9
  • X = 9, Y = (9 + 63/9) / 2 = 8
  • X = 8, Y = (8 + 63/8) / 2 = 7
  • X = 7, Y = (7 + 63/7) / 2 = 8
  • Result is 7, and we can see here that X = Y is not a valid stop condition. It has to be X ≤ Y !

Initial state

  • Memory: N 0 0 0 0 0 0 0 0 0 0
  • Cursor: first cell
  • Input: any

Process

  • Here is what our memory should looks like when starting a loop
    • N [loop indicator] [current value X] 0 0 0 0 0 0 0 0
  • Set loop indicator to 1 and current value to N
  • Then, while loop indicator is 1
    • Reset loop indicator
    • Note
      • X will be needed
        • once to compute Y
        • once to be compared to Y
        • once to be the final result, if needed
      • N will be needed
        • once to compute Y
    • Hence, copy N once and X twice to get a memory like this
      • N 0 X 0 X N 0 0 0 X 0
    • Divide N by X. Division also produces R and R'
      • R being the remainder
      • and R' so that X = R+R'
      • N 0 X 0 X 0 R 0 0 R' N/X
    • Compute 2Y, by adding X to N/X, so by adding R and R' to N/X
      • N 0 X 0 X 0 0 0 0 0 2Y
    • Note: Y will be needed
      • once to be compared to X
      • once to replace X if needed
    • Hence, divide 2Y by 2 and store the results at the right places
      • N 0 X Y X 0 0 0 Y 0 0
    • Compare Y and X
      • N 0 X res 0 0 0 0 Y 0 0
    • If res = 1
      • set loop indicator to 1
      • reset X
      • copy Y where X stands

Code - try it

initialize memory
[->+>+<<]>[-<+>]+
Memory : N 1 X 0 0 0 0 0 0 0 0
[
  reset loop indicator
  -
  copy X twice
  >[->+>+>>>>>+<<<<<<<]>[-<+>]
  copy N
  <<<[->+>>>>+<<<<<]>[-<+>]
  Memory: N 0 X 0 X N 0 0 0 X 0

  divide N by X
  >>>>[->+>>+>-[<-]<[->>+<<<<[->>>+<<<]>]<<]
  Memory: N 0 X 0 X 0 R 0 0 R' N/X

  add X to quotient
  >[->>>+<<<]>>>[->+<]
  Memory: N 0 X 0 X 0 0 0 0 0 2Y

  divide 2Y by 2 and store result twice)
  >[-[-<<+<<<<<+>>>>>>]<[>]>]
  Memory: N 0 X Y X 0 0 0 Y 0 0

  compare Y less than X
  <<<<<+<<[->[->]>[<<[-]>>->>]<<+<<]>[[-]<+>]>-<<
  Memory: N 0 X res 0 0 0 0 Y 0 0

  if true replace X by Y and loop
  [
    set loop indicator
    -<<+
    reset X
    >[-]
    replace by Y
    >>>>>>[-<<<<<<+>>>>>>]<<<<<
  ]
  go back to loop indicator
  <<
]

Final state

  • Memory: N 0 R 0 0 0 0 0 R' 0 0 where R = isqrt(N) and R' = R unless N = R^2 + 1 (then, R' = R+1)
  • Cursor: second cell
  • Input: unchanged
  • Output: unchanged

mercredi 15 février 2017

Shifts

Context


Right or left bit shifts are operators in algorithmic, with a general syntax >> or <<.
These operators append B bits at the beginning or end of A, and therefore truncate the last or first B bits of A.
Example: 47 << 2 = 188, as 47 is 00101111, and 10111100 = 188 in base 10.
One useful application is the multiplication / division by powers of 2. Actually, X<<Y = X*2^Y, and X>>Y = X/2^Y.
Let's implement operations <<1 and >>1, namely multiplication / division by 2.

Initial state

  • Memory: X 0 0
  • Cursor: first cell
  • Input: any

Process - left shift

  • While first cell is not null
    • Remove 1 to first cell
    • Add 2 to second cell
    • Loop invariant: first cell + (second cell) / 2 = X
    • When first cell is null: second cell equals 2X

Code - try it

[->++<]

Final state - left shift

  • Memory: 0 2X
  • Cursor: first cell
  • Input: unchanged
  • Output: unchanged

Process - right shift

  • Division by 2 can be implemented in a different way than regular division:
  • While first cell if not null
    • Remove 1 to first cell
    • If possible, remove 1 again to first cell, increase third cell and stay on second
    • Move right (i.e. on second cell if first is now null, third otherwise, and third is not null in this case)
    • Move left if current is not null (i.e. fallback on second cell in all case)
    • Loop invariant: first cell + 2*third cell = X or X-1
    • When first cell is null: third is half X, rounded to floor

Code - try it

[-[->>+<]>[<]<]

 Final state - right shift

  • Memory: 0 0 X/2
  • Cursor: first cell
  • Input: unchanged
  • Output: unchanged



Evaluate GT / LT / GE / LE

Context

We already went through the EQ / NEQ evaluation, let's now see how to check, given 2 numbers A and B, if A > B or if B ≤ A
Initial state

  • Memory: A B 0 0
  • Cursor: first cell
  • Input: any

Process

  • Set else bit on fourth cell
  • While A is not null
    • Decrease A by 1
    • If B is not null
      • Decrease B by 1
      • Reset else bit
    • If B is null (i.e. if else bit is not null)
      • A is definitely greater than or equal to B: no need to continue the loop, we can reset A
    • Set else bit to 1 and go back to A
  • Do some result formatting (A is always null, B is null if and only if B ≤ A, so this would be our result)

Code - try it

set else bit
>>+<<
[
  decrease A by 1
  -
  if B is not null: decrease B and reset else bit
  >[->]>
  if B is null (else bit not null)
  [
    reset A
    <<[-]
    clear else
    >>-
    go to same position
    >>
  ]
  set else bit and back to A
  <<+<<
]

format / copy result
if B is not null then set result to true
>[[-]<+>]
reset else bit
>-<<

Code - minified

>>+<<[->[->]>[<<[-]>>->>]<<+<<]>[[-]<+>]>-<< 

Final state

  • Memory: result 0 0 0 with result = 1 if and only if A < B
  • Cursor: on result
  • Input: unchanged
  • Ouput: unchanged

Note

This can be easily modified to compute GE / LT: simply set result to true before final check on B, that will do a reset instead of set. As B is not null if and only if B > A, then
  • set result to true
  • and reset if B > A
will give true for A ≥ B

lundi 13 février 2017

Recursion vs iteration: compute factorial

Context

There are two ways to deal with loops in computer science: the recursive way and the iterative way.

  • Recursion: a function that contains calls to itself, creating a loop through nested calls
  • Iteration: do something until a condition is met
Both have pros and cons.

Using recursion, it's generally easier to prove algorithm validity by induction: if you can prove that your result is correct for smallest input possible, and that having a valid result for an input N implies a valid result for N+1, then your algorithm is valid for any input.
But recursion also implies to carefully watch at the memory stack: each recursive call uses memory, and you may reach a 'stack overflow' after too many recursive calls...

Using iterations, you can do something a given number of times, not necessarily using more memory on each loop. Validity can be proven as well, but using an invariant that might be complex to find.

In BrainFuck however, there are no functions, so how can we use recursive calls?
It actually requires to understand one of the key points of recursion: what happens when you have something like
function F(n)
  do something
  F(n')
  do something
Calling function F (any function actually) will

  • Push on stack some details about the returning point
  • Push also function parameters
  • Then execute code
In BrainFuck, we can easily do something similar.
Let's implement the factorial function, denoted by n! = product of all integers from 1 to n.
  • The iterative definition of this function is n! = 1 * 2 * 3 ... * n
  • The recursive definition is n! = n * (n-1)!, with 0! = 1
Note: as factorial grows faster than any polynomial or exponential function, we may reach integer limit pretty quickly. Actually, BrainFuck uses memory cells valued from 0 to 255, modulo 256, so:
  • 5! is the biggest integer we may have (120)
  • 6!, 7!, 8!, 9! will be computed modulo 256
  • 10! is divisible by 256, so any n! mod 256 with n ≥ 10 will be 0
Therefore, there is no need to read complex integers in our algorithms. A single digit is enough

Initial state


  • Memory: empty
  • Cursor: first cell
  • Input: one char

Process

  • Read character, convert to integer (-48). This would be n
  • Iterative way
    • Set a cell to 1 (this would be our result)
    • While n is not null
      • Decrease n
      • Increase the cell besides (let's call it j)
      • Multiply our result by j
      • Loop invariant: result equals j! and n+j = initial value of n
      • When n is null, then j = initial n, and result = initial n!
  • Recursive way
    • While current cell is not null
      • Copy value 2 cells on the right
      • Decrease copy by 1, and stay on this copy
    • Move to left, set result to 1, move to left
    • While current cell is not null
      • multiply this value with the next cell's one
      • store result on the previous cell
      • move 2 cells on the left compared to the original position
Note: the recursive algorithm is a bit harder to understand. We will
  • Generate a memory that looks like n 0 n-1 0 n-2 0 ... 2 0 1 0 0
  • Then, set the red cell to 1 : ... 2 0 1 1 0
  • Multiply 1 by 1 and store the result after 2 : ... 3 0 2 1 0 0 0
  • Multiply 2 by 1 and store the result after 3 : ... 4 0 3 2 0 0 0 0 0
  • Multiply 3 by 2, and so on...

Code (iterative version) - try it


read n
,>++++++++[-<------>]
set result to 1
>>>+<<<<
while n is not null
[
  decrease n and increase j by one
  ->+
  copy current value of j
  [->+>+<<]>[-<+>]>
  multiply current result by j
  -[->[->+>+<<]>[-<+>]<<]>>>[-<<+>>]<<<
  go back to n and loop
<<<]

Code (recursive version) - try it

read n (leave some free space to stop the final loop)
>>,>++++++++[-<------>]
generate all integers from n to 0 separated by 0
<[[->+>+<<]>[-<+>]>-]
set result to 1 and move to integer '1'
<+<
While current integer is not null
[
  multiply result by current integer
  [->[->+>+<<]>[-<+>]<<]
  store result at its new location (and reset temporary cells)
  >>>[-<<<<+>>>>]<<[-]<
  go to previous integer in the stack
<<]

Final state 


  • Memory : 
    • Iterative method: 0 n 0 0 n!
    • Recursive method: 0 n! 0 0 
  • Pointer: first cell (in both cases)
  • Input: one character read
  • Output: unchanged

jeudi 9 février 2017

Self-interpreter: Bonus - Enhancements (10)

Context

Wait, wait, we said the interpreter was over, didn't we?
Well, yes, we have a working interpreter that uses 1137 instructions, and performs about 80k operations for one interpreted instruction.
We can do better !

The most expensive part of our interpreter is memory accesses: moving all items (blocks of 2 cells) to the left, then back to the right, takes time, moreover it's used by all operations but > and <.
The second most expensive part we can improve is the instruction access, for the same reason.
Let's see how we can get rid of instruction and memory pointers

Global idea: here is what our memory map should be
0 {instr_before} 0 0 {instr_after} 0/bit_else 1/instruction_copy inactive_counter direction_flag 0 0 {mem_before} 0 0 {mem_after}
No pointer here. But 2 arrays for instructions and same for memory.
Then, as a convention, we can define current instruction or current memory cell as the first item in the corresponding x_after array.
Thanks to this:
  • Instruction can be accessed more quickly (no need to move X items back and forth)
  • Same for memory
  • Drawbacks
    • Incrementing the instruction pointer is done by moving one element from one array to the other
    • And instructions > and < are executed by moving one memory cell from one array to the other
  • However, these drawbacks are nothing compared to the improvement of fetch or all other operations
One last thing we can improve is the evaluation of while and loop. There is no real need to copy memory cell outside the memory array to evaluate it. It was a quick and dirty way to have all our variables closed together, but it's actually useless as we can directly check the value at it's original location.

Initial state

  • Memory: empty
  • Cursor: on first cell
  • Input: code to execute#input of interpreted program

Code (minified - only 695 instructions - 38% shorter !!!)

>>>,[>+++++[-<------->]+<[>,>]>[->]<<]>+[-<<[<]>[-<+<+>>]<[->+<]<[->>[>]>+<<[<]<]>>[>]+>>[<<++++++[->--------<]+>[--[<->[-]]<[->>-<<]>]<[->>+<<]>+>[<->[->>+<<]]<[->>-<<]+>]>>[-<<+>>]<<<[--------[-[-[-[<+[->-------<]+>[--[<+++[->-------<]+>-[--[<->[-]]<[->>>>>>[>>]>>>[<<<<<[<<]<+<->>>>[>>]>]<[<<]<<[<<]>[-<<<+>>>]<<<<<]>]<[->>>>>+>[>>]>>>[<<<<<[<<]>->[>>]>]<[<<]<<[<<]>[-<<<+>>>]<<<<<]>]<[->>>>>>[>>]+>>[-]>[-<<+>>]>[-]+<<<<[<<]<<<<]>]<[->>>>>>[>>]+<[->>+<<]<-<<[<<]<<<<]>]<[->>>>>>[>>]>>>.<<<<<[<<]<<<<]>]<[->>>>>>[>>]>>>-<<<<<[<<]<<<<]>]<[->>>>>>[>>]>>>,<<<<<[<<]<<<<]>]<[->>>>>>[>>]>>>+<<<<<[<<]<<<<]>[-]<]>>>>+<[<<<<[<]<<[->>+<<]>>[>]>>>>-]>[-<<<<<[<]>[-<<+>>]>[>]>>>>>]<<<<<<[<]>[[>]>+<]>]

Code - with comments, character codes and memory map

separator 35/0
inc 43/8
read 44/9
dec 45/10
print 46/11
left 60/25
right 62/27
while 91/56
loop 93/58

0 {instructions_before} 0 0 {instructions_after} 0 1/instruction_copy inactive_counter direction_flag 0 0 {memory_before} 0 0 {memory_after}

>>>,[>+++++[-<------->]+<[>,>]>[->]<<]>+
[-
  fetch
  <<[<]>[-<+<+>>]<[->+<]<[->>[>]>+<<[<]<]>>[>]
  inactive check processing
  +>>[
    check instruction
    <<++++++[->--------<]+>
    [
      --
      [
        not while nor loop
        reset else bit
        <->[-]
      ]<[-
        instruction loop
        decrease inactive_counter
        >>-<<
      ]>
    ]<[-
      instruction while
      increase inactive_counter
      >>+<<
    ]>
    reset instruction (but not null)
    +
    >[
      if counter is not null
      copy inactive_counter (to break loop)
      <->[->>+<<]
    ]<[
      else reset direction_flag
    ->>-<<
    ]+>
  ]
  reload inactive_counter
  >>[-<<+>>]<<
  <[
    Execute instructions
    --------
    [
      -
      [
        -
        [
          -
          [
            <+[->-------<]+>
            [
              --
              [
                <+++[->-------<]+>-
                [
                  --
                  [
                    not an instruction
                    <->[-]
                  ]<[-
                    instruction loop
                    >>>>>>[>>]>>>[<<<<<[<<]<+<->>>>[>>]>]<[<<]<<[<<]>[-<<<+>>>]<<<<<
                  ]>
                ]<[-
                  instruction while
                  >>>>>+>[>>]>>>[<<<<<[<<]>->[>>]>]<[<<]<<[<<]>[-<<<+>>>]<<<<<
                ]>
              ]<[-
                instruction right
                >>>>>>[>>]+>>[-]>[-<<+>>]>[-]+<<<<[<<]<<<<
              ]>
            ]<[-
              instruction left
              >>>>>>[>>]+<[->>+<<]<[-]<<[<<]<<<<
            ]>
          ]<[-
            instruction print
            >>>>>>[>>]>>>.<<<<<[<<]<<<<
          ]>
        ]<[-
          instruction dec
          >>>>>>[>>]>>>-<<<<<[<<]<<<<
        ]>
      ]<[-
        instruction read
        >>>>>>[>>]>>>,<<<<<[<<]<<<<
      ]>
    ]<[-
      instruction inc
      >>>>>>[>>]>>>+<<<<<[<<]<<<<
    ]>
    clear instruction
    [-]
    <
  ]
  >>>>+<
  check direction
  [
    <<<<[<]<<[->>+<<]>>[>]>>>>-
  ]>
  [
    -<<<<<[<]>[-<<+>>]>[>]>>>>>
  ]
  <<<<<
  <[<]>[[>]>+<]>
]

Final state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: unchanged
  • Output: unchanged

Example

Live BF Interpreter.
Be careful not to execute too long code: one single BrainFuck operation to interpret is rather long (parsing, processing, ...). This version still allows you to run longer code (in theory).
With the same code sample than previous interpreter version:
,.---.>+++[-<++>]<+..+++.>++++[->++[->++++<]<]>>.<<<[->+>+<<]++[->++++<]>.>.+++.<<++[->-----<]>-.<++[->----<]>.#H
This new interpreter can execute the code in only 13.675.534 operations, which is really good compared to the 65.622.379 operations for same input using previous interpreter code !!!
This gives an average ratio of 16.107 operations to execute one instruction.
Again, this ratio is definitely not accurate, as operations are also used to read program, or in inactive mode, ... but it's still a good indicator of execution complexity.


Conclusion: this num interpreter needs 38% less code, and is 5x faster to execute !

Back to previous step
(No more next step this time 😅)