Visualize Nicomachus's Theorem

MATL, 30 28 27 bytes

t:P"@:s:@/Xk&+@+8MPt&(]30+c

Try it online!

Bonus features:

  • For 26 bytes, the following modified version produces graphical output:

    t:P"@:s:@/Xk&+@+8MPt&(]1YG
    

    Try it at MATL Online!

  • The image is begging for some colour, and it only costs 7 bytes:

    t:P"@:s:@/Xk&+@+8MPt&(]1YG59Y02ZG
    

    Try it at MATL Online!

  • Or use a longer version (37 bytes) to see how the character matrix is gradually built:

    t:P"@:s:@/Xk&+@+8MPt&(t30+cD9&Xx]30+c
    

    Try it at MATL Online!

Example outputs

For input is 8, the following shows the basic version, graphical output, and colour graphical output.

enter image description here

enter image description here

enter image description here

Explanation

General procedure

A numeric matrix is built from outer to inner layers in N steps, where N is the input. Each step overwrittes an inner (upper-left) part of the previous matrix. At the end, the numbers in the obtained matrix are changed to characters.

Example

For input 4 the first matrix is

10 10  9  9  9  9  8  8  8  8
10 10  9  9  9  9  8  8  8  8
 9  9  8  8  8  8  7  7  7  7
 9  9  8  8  8  8  7  7  7  7
 9  9  8  8  8  8  7  7  7  7
 9  9  8  8  8  8  7  7  7  7
 8  8  7  7  7  7  6  6  6  6
 8  8  7  7  7  7  6  6  6  6
 8  8  7  7  7  7  6  6  6  6
 8  8  7  7  7  7  6  6  6  6

As a second step, the matrix

7 7 7 6 6 6
7 7 7 6 6 6
7 7 7 6 6 6
6 6 6 5 5 5
6 6 6 5 5 5
6 6 6 5 5 5

is overwritten into the upper half of the latter. Then the same is done with

6 5 5
5 4 4
5 4 4

and finally with

3

The resulting matrix is

3 5 5 6 6 6 8 8 8 8
5 4 4 6 6 6 8 8 8 8
5 4 4 6 6 6 7 7 7 7
6 6 6 5 5 5 7 7 7 7
6 6 6 5 5 5 7 7 7 7
6 6 6 5 5 5 7 7 7 7
8 8 7 7 7 7 6 6 6 6
8 8 7 7 7 7 6 6 6 6
8 8 7 7 7 7 6 6 6 6
8 8 7 7 7 7 6 6 6 6

Lastly, 30 is added to each entry and the resulting numbers are interpreted as codepoints and converted to characters (thus starting at 33, corresponding to !).

Construction of the intermediate matrices

For input N, consider decreasing values of k from N to 1. For each k, a vector of integers from 1 to k*(k+1) is generated, and then each entry is divided by k and rounded up. As an example, for k=4 this gives (all blocks have size k except the last):

1 1 1 1 2 2 2 2 3 3

whereas for k=3 the result would be (all blocks have size k):

1 1 1 2 2 2

This vector is added, element-wise with broadcast, to a transposed copy of itself; and then k is added to each entry. For k=4 this gives

6  6  6  6  7  7  7  7  8  8
6  6  6  6  7  7  7  7  8  8
6  6  6  6  7  7  7  7  8  8
6  6  6  6  7  7  7  7  8  8
7  7  7  7  8  8  8  8  9  9
7  7  7  7  8  8  8  8  9  9
7  7  7  7  8  8  8  8  9  9
7  7  7  7  8  8  8  8  9  9
8  8  8  8  9  9  9  9 10 10
8  8  8  8  9  9  9  9 10 10

This is one of the intermediate matrices shown above, except that it is flipped horizontally and vertically. So all that remains is to flip this matrix and write it into the upper-left corner of the "accumulated" matrix so far, initiallized to an empty matrix for the first (k=N) step.

Code

t       % Implicitly input N. Duplicate. The first copy of N serves as the
        % initial state of the "accumulated" matrix (size 1×1). This will be 
        % extended to size N*(N+1)/2 × N*(N+1)/2 in the first iteration
 :P     % Range and flip: generates vector [N, N-1, ..., 1]
"       % For each k in that vector
  @:    %   Push vector [1, 2, ..., k]
  s     %   Sum of this vector. This gives 1+2+···+k = k*(k+1)/2
  :     %   Range: gives vector [1, 2, ..., k*(k+1)/2]
  @/    %   Divide each entry by k
  Xk    %   Round up
  &+    %   Add vector to itself transposed, element-wise with broadcast. Gives
        %   a square matrix of size k*(k+1)/2 × k*(k+1)/2
  @+    %   Add k to each entry of the this matrix. This is the flipped
        %   intermediate matrix
  8M    %   Push vector [1, 2, ..., k*(k+1)/2] again
  Pt    %   Flip and duplicate. The two resulting, equal vectors are the row and
        %   column indices where the generated matrix will be written. Note that
        %   flipping the indices has the same effect as flipping the matrix
        %   horizontally and vertically (but it's shorter)
  &(    %   Write the (flipped) intermediate matrix into the upper-left
        %   corner of the accumulated matrix, as given by the two (flipped)
        %   index vectors 
]       % End
30+     % Add 30 to each entry of the final accumulated matrix
c       % Convert to char. Implicitly display

Python 2, 187 178 164 162 152 bytes

-8 bytes thanks to Mr.Xcoder
-1 byte thanks to Stephen
-10 bytes thanks to Jonathan Frech

g=lambda y:y>1and[l+y*f(y,i)for i,l in enumerate(g(y-1))]+y*[''.join(f(y,i)for i in range(y*-~y/2))]or['#']
f=lambda y,i:'0@+#'[(y*~-y/2%y+i)/y%2+y%2*2]

Try it online!


Charcoal, 50 46 bytes

F⮌…·¹N«≔⊘×ι⊕ιθF⊕⊘ι«F§#+@⁺ικ«UO⁻θ×ικθλUOθ⁻θ×ικλ

Try it online! Link is to verbose version of code. Previous 50-byte version with explanation: Try it online!

F⮌…·¹N«≔÷×ι⁺¹ι²θF⁺¹÷鲫F§#+@⁺ικ«UO⁻θ×ικθλUOθ⁻θ×ικλ

F     «     Loop over
  …·¹       Inclusive range from 1 to
     N      Input as a number
 ⮌          Reversed

   ι⁺¹        Add 1 to current index
  ×   ι       Multiply by current index
 ÷     ²      Divide by 2
≔       θ     Assign to q

F     «      Loop over
             Implicit range from 0 to
   ÷ι²       Half the current index
 ⁺¹          Plus 1

F       «    Loop over
  #+@        Literal string
 §           Circularly indexed by
     ⁺ικ     Sum of outer and inner index

    ×ικ     Multiply outer and inner index
  ⁻θ        Subtract from q
UO     θλ   Draw an oblong (q-ik, q) using that character

UOθ⁻θ×ικλ   Draw an oblong (q, q-ik) using that character

Note: I loop over the character rather than trying to assign the character directly to l because you can't directly assign the result of indexing a string to a variable as it's an ambiguous construct in Charcoal. Fortunately the byte count is the same.