Last week, I shared some code that, while imperfect, wasn’t that bad. I then issued a challenge: make it worse. Or better, if you really want. As many comments noted: one case covers only the first iteration of the loop, and one case only covers the last iteration of the loop. You could easily pull those out of the loop, and not need a for-case at all. Others noticed that this pattern looked like odd slices out of an identity matrix.
With that in mind, we got a few numpy
, Matlab
, or MatrixUtils
based solutions generally were the “best” solutions to the problem: generate an identity matrix and take slices out of it. This is reasonable and fine. It makes perfect sense. Let’s see if we can avoid making sense.
I’ll start with Abner Qian’s Ruby solution.
module MagicalArrayGenerator
def magical_array_generator
main_array = []
self.times do |i|
inner_array = []
self.times do |j|
i == j ? inner_array << 1 : inner_array << 0
end
main_array << inner_array
end
e_1 = []
n_1 = []
e_n = []
n_n = []
self.times do |i|
e_1 << [main_array[i].first]
e_n << [main_array[i].last]
n_1 << main_array[i][1..-1]
n_n << main_array[i][0..-2]
end
[e_1, n_1, e_n, n_n]
end
end
class Integer
include MagicalArrayGenerator
end
e_1, n_1, e_n, n_n = 4.magical_array_generator
At it’s core, this is simply an implementation that generates an identity matrix and slices it up. The actual implementation, however, is a pitch-perfect parody of Ruby development: “There’s no problem that can’t be solved by monkey-patching a method into a built-in type”. That’s what happens here- the include
statment injects this method into the build-in Integer
data-type, meaning you can call 4.magical_array_generator
and get your arrays. Abner also points out that Ruby uses 62-bit integers, just in case you want some 4611686018427387903 by 4611686018427387904 arrays.
Several folks looked at the idea of taking slices, and said, “Gee, I bet you I could do this with pointers in C”. My personal favorite in that category would have to be Ron P’s approach.
#include <stdio.h>
int main( int argc, char **argv)
{
int *e_1 = 0;
int *e_n = 0;
int **n_1 = 0;
int **n_n = 0;
int *fugly = 0;
int i,j;
if ( argc != 2 ) return 1;
int n = atoi(argv[1]);
fugly = calloc( n*(n+1),sizeof(int));
n_1 = calloc(n,sizeof(int *));
n_n = calloc(n,sizeof(int *));
for ( i = 0, j=n; i < n; ++i, j+=n+1 )
{
fugly[j]=1;
n_1[i]=fugly+n*i;
n_n[i]=n_1[i]+n;
}
e_1 = fugly+n;
e_n = fugly+1;
printf( "e_1\n" );
for ( i = 0; i < n; ++i ) {
printf( " %d\n", e_1[i]);
}
printf( "\ne_n\n" );
for ( i = 0; i < n; ++i ) {
printf( " %d\n", e_n[i]);
}
printf( "\nn_1\n" );
for ( i = 0; i < n; ++i ) {
printf( " " );
for ( j = 0; j < n-1; ++j ) {
printf("%d ", n_1[i][j]);
}
printf("\n" );
}
printf( "\nn_n\n" );
for ( i = 0; i < n; ++i ) {
printf( " " );
for ( j = 0; j < n-1; ++j ) {
printf("%d ", n_n[i][j]);
}
printf("\n" );
}
return 0;
}
Now, Martin Scolding gets bonus points for two reasons: first, he uses one of the worst languages in the world (not designed as an esolang), and second, this language doesn’t technically support multi-dimensional arrays. I speak, of course, of PL/SQL. Note the use of substrings to figure out what number to put in each position of the array.
DECLARE
TYPE data_t IS TABLE OF INTEGER INDEX BY PLS_INTEGER;
TYPE array_t IS TABLE OF data_t INDEX BY PLS_INTEGER;
e_1 array_t;
e_n array_t;
n_1 array_t;
n_n array_t;
l_array_size INTEGER := 0;
PROCEDURE gen_arrays(n INTEGER, p_e_1 IN OUT array_t, p_e_n IN OUT array_t, p_n_1 IN OUT array_t, p_n_n IN OUT array_t)
--' Generate 4 Arrays of the form (example n=4)
--
-- ' | 1 | | 0 0 0 |
-- ' e_1 = | 0 | n_1 = | 1 0 0 |
-- ' | 0 | | 0 1 0 |
-- ' | 0 | | 0 0 1 |
-- '
-- ' | 0 | | 1 0 0 |
-- ' e_n = | 0 | n_n = | 0 1 0 |
-- ' | 0 | | 0 0 1 |
-- ' | 1 | | 0 0 0 |
--
IS
l_n_string LONG := RPAD('1',n+1,'0');
BEGIN
For i in 1..n Loop
p_e_1(i)(1) := TO_NUMBER(SUBSTR(l_n_string, 1, 1));
p_e_n(i)(1) := TO_NUMBER(SUBSTR(l_n_string, n, 1));
For j in 1..n-1 Loop
p_n_1(i)(j) := TO_NUMBER(SUBSTR(l_n_string, j+1, 1));
p_n_n(i)(j) := TO_NUMBER(SUBSTR(l_n_string, j, 1));
End Loop;
l_n_string := LPAD(SUBSTR(l_n_string, 1, n), n+1, '0');
End Loop;
END;
BEGIN
l_array_size := &inp_array;
gen_arrays(l_array_size, e_1, e_n, n_1, n_n);
--==========================================================================
-- DISPLAY RESULTS
--==========================================================================
DBMS_OUTPUT.PUT_LINE('e_1 = ');
For i in 1..l_array_size Loop
DBMS_OUTPUT.PUT_LINE(' | ' || e_1(i)(1) || ' |');
End Loop;
DBMS_OUTPUT.PUT_LINE('--------------------------------------------------');
DBMS_OUTPUT.PUT_LINE('e_n = ');
For i in 1..l_array_size Loop
DBMS_OUTPUT.PUT_LINE(' | ' || e_n(i)(1) || ' |');
End Loop;
DBMS_OUTPUT.PUT_LINE('--------------------------------------------------');
DBMS_OUTPUT.PUT_LINE('n_1 = ');
For i in 1..l_array_size Loop
DBMS_OUTPUT.PUT(' | ');
For j in 1..l_array_size-1 Loop
DBMS_OUTPUT.PUT(n_1(i)(j) || ' ');
End Loop;
DBMS_OUTPUT.PUT('|');
DBMS_OUTPUT.NEW_LINE;
End Loop;
DBMS_OUTPUT.PUT_LINE('--------------------------------------------------');
DBMS_OUTPUT.PUT_LINE('n_n = ');
For i in 1..l_array_size Loop
DBMS_OUTPUT.PUT(' | ');
For j in 1..l_array_size-1 Loop
DBMS_OUTPUT.PUT(n_n(i)(j) || ' ');
End Loop;
DBMS_OUTPUT.PUT('|');
DBMS_OUTPUT.NEW_LINE;
End Loop;
DBMS_OUTPUT.PUT_LINE('--------------------------------------------------');
--==========================================================================
--
--==========================================================================
END;
/
Finally, though, I have to give a little space to Airdrik. While the code may contain some errors, it is in Visual Basic, as was the original solution, and it knows that recursion makes everything better.
Public Sub GenerateIdentitySquare(ByVal n As Long, ByRef sq As Variant, ByVal i As Long, ByVal j As Long)
Select Case j
Case i:
sq(i, j) = #1
If i < n Then
GenerateIdentitySquare(n, sq, i, j+1)
End If
Case n:
sq(i, j) = #0
GenerateIdentitySquare(n, sq, i+1, 1)
Case Else:
sq(i, j) = #0
GenerateIdentitySquare(n, sq, i, j+1)
End Select
End Sub
Public Sub CopyRowValues(ByVal n As Long, ByRef sq As Variant, ByRef e As Variant, ByVal sq_i As Long, ByVal e_i As Long, ByVal j As Long)
e(e_i, j) = sq(sq_i, j)
if j < n Then
CopyRowValues(n, sq, e, sq_i, e_i, j+1)
End If
End Sub
Public Sub CopyRows(ByVal n As Long, ByRef sq As Variant, ByRef e_1 As Variant, ByRef e_n As Variant, ByRef n_1 As Variant, ByRef n_n As Variant, ByVal i As Long)
Select Case i
Case 1:
CopyRowValues(n, sq, e_1, i, 1, 1)
CopyRowValues(n, sq, n_n, i, i, 1)
CopyRows(n, sq, e_1, e_n, n_1, n_n, i+1)
Case n:
CopyRowValues(n, sq, n_1, i, i-1, 1)
CopyRowValues(n, sq, n_n, i, i, 1)
Case Else:
CopyRowValues(n, sq, n_1, i, i-1, 1)
CopyRowValues(n, sq, e_n, i, 1, 1)
CopyRows(n, sq, e_1, e_n, n_1, n_n, i+1)
End Select
End Sub
Public Sub DefineProjectionArrays(ByVal n As Long, ByRef e_1 As Variant, ByRef e_n As Variant, ByRef n_1 As Variant, ByRef n_n As Variant)
Dim i As Long, j As Long
' Generate 4 Arrays of the form (example n=4)
' | 1 | | 0 0 0 |
' e_1 = | 0 | n_1 = | 1 0 0 |
' | 0 | | 0 1 0 |
' | 0 | | 0 0 1 |
'
' | 0 | | 1 0 0 |
' e_n = | 0 | n_n = | 0 1 0 |
' | 0 | | 0 0 1 |
' | 1 | | 0 0 0 |
Dim sq(n, n) As Variant
GenerateIdentitySquare(n, sq, 1, 1)
ReDim e_1(n, 1)
ReDim e_n(n, 1)
ReDim n_1(n, n - 1)
ReDim n_n(n, n - 1)
CopyRows(n, sq, e_1, e_n, n_1, n_n, 1)
End Sub
Functional programming is always the best approach, obviously.