Cells and Axes

Fundamentals of high rank arrays

From the APL Wiki:

A cell is a subarray which is formed by selecting a single index along some number of leading axes and the whole of each trailing axis. Cells are classified by their rank, which may be between 0 (scalars) and the array's rank (in which case the cell must be the entire array). Cells with rank k are called k-cells of an array. A major cell is a cell whose rank is one less than the entire array, or a 0-cell of a scalar.

If the text above feels confusing, don't worry. Maybe after this chapter, or even after the next section on selecting from arrays, you can read it again and say to yourself "oh yeah, that makes sense". For now just know that that arrays are arranged like rectangles in many dimensions. The three simplest cases should feel a bit familiar to you.

      0            ⍝ A scalar
0
'APL'        ⍝ A vector
APL
0 1 2∘.*⍳5   ⍝ A matrix
0 0 0  0  0
1 1 1  1  1
2 4 8 16 32

Now let us look at an array with 3 dimensions. We will call it a cuboid:

      ⍴cuboid←2 3∘.+3 4 5∘.×4 5 6 7
2 3 4 ← trailing (last) axis
↑
leading (first) axis

In the array cuboid defined above, there are 2 major cells, which are those of rank ¯1+≢⍴cuboid.

Here is another 3D array containing letters of the alphabet:

      2 3 4⍴⎕A
ABCD
EFGH
IJKL

MNOP
QRST
UVWX
≢2 3 4⍴⎕A      ⍝ Tally counts the major cells
2

The display may look like 2 separate matrices, but the array 2 3 4⍴⎕A is a single, 3 dimensional array.

The dimensions of an array are also known as axes. The most major cells, the rank k-1 cells for an array of rank k, lie along the first axis. The least major cells are columns which lie along the last axis.

In Dyalog, arrays can have up to 15 dimensions.

For more details on the APL array model in Dyalog and other array languages, see the APL Wiki article on the array model.

Now that we have the words to describe the structure of an array in terms of its sub-arrays, let us look at how to apply functions to those sub-arrays.

The rank operator

A lot of the time you might want to take a subset of the data and do stuff to it. Sometimes you want to think of the data as a collection of similar parts and apply the same processing to each part. In fact, this idea is built in to the "array-at-a-time" nature of some primitive functions and operators, but the rank operator lets us do this for all functions.

Let us take a 3D array representing the cost of 3 products each day over 2 weeks.

cost ← ?3 2 7⍴9

The total cost over all 3 products each day of each week is the sum between the layers (rank 2, matrices):

      +⌿cost
12 21 13 14 17 20 17
12 25 18 10  9 26 14

      ⍴+⌿cost       ⍝ 2 weeks, 7 days per week
2 7

To get the total spent on each week day over the two weeks, we can tell the function +⌿ to only see the tables for each product — the rank-2 subarrays. The first axes of each rank-2 subarray are the columns of each product table:

      (+⌿⍤2)cost
 4 18 12  7 9 13  6
17 14  9  7 9 17 13
3 14 10 10 8 16 12

      ⍴(+⌿⍤2)cost   ⍝ 3 products, 7 days per week
3 7

The total spent on each product each week is the row-wise sum.

      (+⌿⍤1)cost
28 41
51 35
35 38

This is the same as +/:

      +/cost
28 41
51 35
35 38

      ⍴(+⌿⍤1)cost   ⍝ 3 products, 2 weeks
3 2

Many operators accept one or more function operands and apply them to their arguments in some particular way. The enclose function ⊂⍵ can be used to get a view of the arguments as the operand function sees them.

Apply our function to rank-2 subarrays of the cost array.

      (⊂⍤2)cost
┌─────────────┬─────────────┬─────────────┐
│2 9 4 1 5 5 2│9 6 8 5 7 8 8│1 6 1 8 5 7 7│
│2 9 8 6 4 8 4│8 8 1 2 2 9 5│2 8 9 2 3 9 5│
└─────────────┴─────────────┴─────────────┘

The result of (+⌿⍤2)⍵ is like doing +⌿⍵ to each of these matrices.

      (+⌿⍤2)cost
 4 18 12  7 9 13  6
17 14  9  7 9 17 13
3 14 10 10 8 16 12

In the dyadic case, the rank operator is a powerful way to pair up subarrays of ⍺ with a conforming collection of subarrays from ⍵.

Let us say that we want to add each of the numbers $$1$$ to $$5$$ to corresponding rows in a matrix. We cannot simply add a vector to a matrix because these arrays have different ranks.

      1 2 3 4 5 + 5 3⍴0 10 100
RANK ERROR: Mismatched left and right argument ranks
1 2 3 4 5+5 3⍴0 10 100
∧

The rank operator (F⍤k) allows us to pair up scalars (rank 0) from ⍺ and vectors (rank 1) from ⍵ and apply our function + between these.

      1 2 3 4 5 (+⍤0 1) 5 3⍴0 10 100
1 11 101
2 12 102
3 13 103
4 14 104
5 15 105

In the same way that some functions can apply between a single value and an array of values, we can apply between an array of rank $$n$$ and an array of rank $$n$$ subarrays. For example, a single vector (rank 1) and a matrix (rank 2) as a collection of vectors.

      1 0 ¯1 (×⍤1 1) 5 3⍴⍳15
 1 0  ¯3
4 0  ¯6
7 0  ¯9
10 0 ¯12
13 0 ¯15

If we derive a dyadic function using an operator, the dfn {⍺⍵} can be used to get a view of the arguments as the operand function sees them.

      1 0 ¯1 ({⍺⍵}⍤1 1) 5 3⍴⍳15
┌──────┬────────┐
│1 0 ¯1│1 2 3   │
├──────┼────────┤
│1 0 ¯1│4 5 6   │
├──────┼────────┤
│1 0 ¯1│7 8 9   │
├──────┼────────┤
│1 0 ¯1│10 11 12│
├──────┼────────┤
│1 0 ¯1│13 14 15│
└──────┴────────┘

In the case where we apply to sub-arrays of the same rank in both ⍺ and ⍵, we only need specify that rank once:

      1 0 ¯1 (×⍤1) 5 3⍴⍳15
 1 0  ¯3
4 0  ¯6
7 0  ¯9
10 0 ¯12
13 0 ¯15

When applying a function F⍤j k, we must ensure that there are the same number of rank-j subarrays in ⍺ as rank-k subarrays in ⍵ - or that one of them has just 1.

      1 0 ¯1 (×⍤0 1) 5 3⍴⍳15
LENGTH ERROR: It must be that either the left and right frames match or one of them has length 0
1 0 ¯1(×⍤0 1)5 3⍴⍳15
∧

Note

With functions like + × ÷, arrays must either have the same shape, or one of them be a scalar. The result of the function application has the same shape as the largest of the input arrays. The rank operator generalises this to the concept of frames. For a particular cell rank, the leading axes form the frame and the trailing k axes form the cell shape. For frames to "match" means that there is the same shape of rank j subarrays in ⍺ as there is for rank k subarrays in ⍵ when a function ⍺ F ⍵ is applied as ⍺ (F⍤j k) ⍵.

Transpose

To transpose array is to rearrange its axes. Or rather, to rearrange along which axes its data lies.

Transposing a matrix is to flip along its leading diagonal (top-left to bottom-right):

      3 3⍴⍳9
1 2 3
4 5 6
7 8 9

      ⍉3 3⍴⍳9
1 4 7
2 5 8
3 6 9

In the monadic case, we reverse the order of the axes:

      2 3 4⍴⎕A
ABCD
EFGH
IJKL

MNOP
QRST
UVWX

      ⍉2 3 4⍴⎕A
AM
EQ
IU

BN
FR
JV

CO
GS
KW

DP
HT
LX

Inspecting the shape before and after transposing shows the reversal of axis lengths.

      ⍴2 3 4⍴⎕A
2 3 4

      ⍴⍉2 3 4⍴⎕A   ⍝ 4 3 2 ≡ ⌽2 3 4
4 3 2

In the dyadic case, the left argument says where each corresponding axis in ⍴⍵ should end up in the result.

For example:

• move the 1st axis to become the 2nd
• move the 2nd axis to become the 1st
• leave the 3rd axis as the 3rd

      2 1 3⍉2 3 4⍴⎕A
ABCD
MNOP

EFGH
QRST

IJKL
UVWX

      ⍴2 1 3⍉2 3 4⍴⎕A
3 2 4

The bracket axis operator

Here are two pairs of first- and last-axis primitives.

      n←2 3⍴1 2 3 1 0 ¯1
n
1 2  3
1 0 ¯1
+/n                ⍝ Sum along the last axis
6 0
+⌿n                ⍝ Sum along the first axis
2 2 2
'-'⍪2 3⍴'DYALOG'   ⍝ Catenate first
---
DYA
LOG
'|',2 3⍴'DYALOG'   ⍝ Catenate last
|DYA
|LOG

Some functions and operators can be used along specified axes using the bracket-axis operator [] (more duplicitous symbols).

Compare the behaviour of the monadic function ⊂ enclose when applied with the rank operator ⍤ versus when it is applied using bracket-axis (also called the function axis operator or axis specification).

      (⊂⍤1)2 2 5⍴'RIGHTHELLOTHERE'
┌─────┬─────┐
│RIGHT│HELLO│
├─────┼─────┤
│THERE│RIGHT│
└─────┴─────┘

      (⊂⍤2)2 2 5⍴'RIGHTHELLOTHERE'
┌─────┬─────┐
│RIGHT│THERE│
│HELLO│RIGHT│
└─────┴─────┘

      (⊂⍤3)2 2 5⍴'RIGHTHELLOTHERE'
┌─────┐
│RIGHT│
│HELLO│
│     │
│THERE│
│RIGHT│
└─────┘

      ⊂[1]2 2 5⍴'RIGHTHELLOTHERE'
┌──┬──┬──┬──┬──┐
│RT│IH│GE│HR│TE│
├──┼──┼──┼──┼──┤
│HR│EI│LG│LH│OT│
└──┴──┴──┴──┴──┘

      ⊂[2]2 2 5⍴'RIGHTHELLOTHERE'
┌──┬──┬──┬──┬──┐
│RH│IE│GL│HL│TO│
├──┼──┼──┼──┼──┤
│TR│HI│EG│RH│ET│
└──┴──┴──┴──┴──┘

      ⊂[3]2 2 5⍴'RIGHTHELLOTHERE'
┌─────┬─────┐
│RIGHT│HELLO│
├─────┼─────┤
│THERE│RIGHT│
└─────┴─────┘

      ⊂[1 2]2 2 5⍴'RIGHTHELLOTHERE'
┌──┬──┬──┬──┬──┐
│RH│IE│GL│HL│TO│
│TR│HI│EG│RH│ET│
└──┴──┴──┴──┴──┘

      ⊂[2 3]2 2 5⍴'RIGHTHELLOTHERE'
┌─────┬─────┐
│RIGHT│THERE│
│HELLO│RIGHT│
└─────┴─────┘

      ⊂[1 2 3]2 2 5⍴'RIGHTHELLOTHERE'
┌─────┐
│RIGHT│
│HELLO│
│     │
│THERE│
│RIGHT│
└─────┘

Only the following primitive constructs can be used with the axis operator:

↑⍵ and ↓⍵ Mix and Split
⌽⍵ or ⊖⍵ Reverse
,⍵ Ravel with axis
⊂⍵ Enclose with axis
F/⍵ or F⌿⍵ Reductions
F\⍵ or F⍀⍵ Scans
+ × ⌈ ∧ ≤ etc... All scalar dyadic functions
⍺↑⍵ and ⍺↓⍵ Take and Drop
⍺/⍵ or ⍺⌿⍵ Replicate/compress
⍺\⍵ or ⍺⍀⍵ Expand
⍺,⍵ or ⍺⍪⍵ Catenate
⍺⊂⍵ Partitioned-enclose
⍺⊆⍵ Partition
⍺F/⍵ or ⍺F⌿⍵ Windowed-reduction

Rank vs Axis

The bracket-axis operator has always been in Dyalog APL. The rank operator was introduced in version 14.0. They both offer similar functionality, however:

• some uses of bracket-axis are shorter than their equivalent expression using rank and transpose, and vice versa
• sometimes it makes sense to think in terms of axes using bracket-axis, other times in terms of cells using the rank operator
• bracket-axis can only be used with a few particular primitives, whereas the rank operator can be used with any function including those defined by the user
• bracket-axis works slightly differently depending on the function to which it is applied, whereas the rank operator has consistent behaviour for all functions
• bracket-axis is special syntax unlike most other operators

In some ways, bracket-axis can be thought of as syntactic sugar for the behaviour of the rank operator in conjunction with dyadic transpose.

For a more in-depth look at the relationship between function rank and function axis, watch the Dyalog webinars on Selecting from Arrays and The Rank Operator and Dyadic Transpose.

The section about older features has some more examples of bracket axis.

Problem set 5

1. Write a function FlipBlock which reverses the order of rows in each sub-matrix of its argument array.

      FlipBlock 2 2 3⍴0 0 0 1 1 1
1 1 1
0 0 0

1 1 1
0 0 0

If there is only 1 row in each sub-matrix, reversing does nothing:

      FlipBlock 1 5⍴'abcde'
abcde

If there is only one dimension, the array will be reversed:

      FlipBlock 'abcde'
edcba

FlipBlock ← {(⊖⍤2)⍵}

We can also use the right/identity function ⊢⍵ to separate the array operand of the rank operator from the array argument of the derived function reverse-first-rank-two ⊖⍤2. Otherwise, stranding would bind 2 ⍵ into a vector, causing unexpected behaviour.

This author prefers to isolate operands using parentheses most of the time.

FlipBlock ← {⊖⍤2⊢⍵}

Alternatively, we can write this as a tacit function. This form is also known as a derived function because a new function is derived from functions and operators.

Parentheses are not required, but this author thinks they make derived functions more distinced from array values when viewed together in source code.

FlipBlock ←  ⊖⍤2
FlipBlock ← (⊖⍤2)

These four spellings of FlipBlock are all equivalent. Whichever you choose to write is a matter of taste.

2. Write a function MatchWord which takes a character vector left argument ⍺ and a character array ⍵ with the same number of columns as ⍺ and returns a Boolean array of rank ¯1+≢⍺ in which a 1 indicates rows in ⍵ that match ⍺.

      'has' MatchWord 5 3⍴'hasnotnot'
1 0 0 1 0

      'simon' MatchWord 3 2 5⍴'maybesimonspoke'
0 1
0 0
1 0

Are all characters in a row equal?

MatchWord ← {∧/⍺(=⍤1)⍵}

Or using the match function ⍺≡⍵:

MatchWord ← {⍺(≡⍤1)⍵}

As a tacit definition:

MatchWord ← (≡⍤1)
3. Extend the Grille function from the problem set about array logic to reveal multiple messages in a 3-dimensional array.

grille  ← 4 4⍴'⌺ ⌺⌺ ⌺ ⌺⌺ ⌺  ⌺⌺⌺'
grilles ← 3 4 4⍴'⌺⌺ ⌺⌺⌺⌺ ⌺⌺⌺⌺⌺ ⌺⌺⌺⌺⌺ ⌺⌺⌺⌺ ⌺⌺⌺⌺⌺ ⌺⌺⌺⌺⌺⌺ ⌺ ⌺⌺ ⌺⌺⌺⌺⌺'
grids   ← 3 4 4⍴'AREQEEVASEQALTOFBSMBESCTIRMETOGPGHIAAACPSKLERVRG'

The single grille reveals 3 messages.

      grille Grille grids
REVEAL
SECRET
HACKER

The array grilles reveals 3 different messages when applied to the same grid.

      grilles Grille grids
EAT
BIG
APL

In the individual case, using rank-2 F⍤2 pairs the single grille with each grid in grids:

      grille ({⍺⍵}⍤2) grid
┌────┬────┐
│⌺ ⌺⌺│AREQ│
│ ⌺ ⌺│EEVA│
│⌺ ⌺ │SEQA│
│ ⌺⌺⌺│LTOF│
├────┼────┤
│⌺ ⌺⌺│BSMB│
│ ⌺ ⌺│ESCT│
│⌺ ⌺ │IRME│
│ ⌺⌺⌺│TOGP│
├────┼────┤
│⌺ ⌺⌺│GHIA│
│ ⌺ ⌺│AACP│
│⌺ ⌺ │SKLE│
│ ⌺⌺⌺│RVRG│
└────┴────┘

We can also use a different grille for each grid, provided that we have one grille per grid:

      grilles ({⍺⍵}⍤2) grid
┌────┬────┐
│⌺⌺ ⌺│AREQ│
│⌺⌺⌺ │EEVA│
│⌺⌺⌺⌺│SEQA│
│⌺ ⌺⌺│LTOF│
├────┼────┤
│⌺⌺⌺ │BSMB│
│⌺⌺⌺⌺│ESCT│
│ ⌺⌺⌺│IRME│
│⌺⌺ ⌺│TOGP│
├────┼────┤
│⌺⌺⌺⌺│GHIA│
│⌺ ⌺ │AACP│
│⌺⌺ ⌺│SKLE│
│⌺⌺⌺⌺│RVRG│
└────┴────┘

Using the definitions of Grille given previously:

Grille ← {⍵[⍸⍺=' ']}⍤2
Grille ← {(,⍺=' ')/,⍵}⍤2
4. The 3D array rain gives the monthly rainfall in millimeters over 7 years in 5 countries.

rain←?7 5 12⍴250

There are 12 columns in each row; the rows represent the months. The sum along the rows...

      (+⌿⍤1)rain
1476 1764 1733 1320 1678
1698 1943  798 2226 1813
2050 1821 1209 1763 1625
2006 1218 1615 1516 1536
1372 1584 1946 1604 1623
1831 1705 1998 1312 1224
1499 1369 1437 1597 1279

      ⍴(+⌿⍤1)rain
7 5

...gives the total rainfall in each year in each country over 12 months. Put another way, it is the total annual rainfall each year in each country.

1. For each expression below, write a brief description of the resulting statistic.

⌈⌿rain
(+⌿⍤2)rain
(⌈⌿⍤1)rain
(⌈⌿⍤3)rain
⌊/rain

Hint

Look at the shapes of the arguments and the results, ⍴rain and ⍴+⌿rain etc.

2. Assign scalar numeric values (single numbers) to the variables years countries months such that the rain data can be summarised as follows:

      ⍴(+⌿⍤years)rain       ⍝ Sum over years
5 12

      ⍴(+⌿⍤countries)rain   ⍝ Sum over countries
7 12

      ⍴(+⌿⍤months)rain      ⍝ Sum over months
7 5

• ⌈⌿rain is the maximum rainfall each month in each country over all 7 years.
• (+⌿⍤2)rain is the total rainfall each year in each month across all 5 countries.
• (⌈⌿⍤1)rain is the maximum monthly rainfall for any month in each year in each country.
• (⌈⌿⍤3)rain is the same as ⌈⌿rain because rain is a rank-3 array.
• ⌊/rain is the minimum rainfall in any month in each year in each country.

1. (years countries months) ← 3 2 1

5. Which of the following functions are affected by the rank operator ⍤ and why are the other functions not affected?
⌽    ⍝ Reverse
⊖    ⍝ Reverse first
+/   ⍝ Plus reduce
+⌿   ⍝ Plus reduce-first

Reverse ⌽⍵ and reduce F/⍵ work along the last axis of an array. The rank operator makes a function act on subarrays defined in terms of trailing axes of an array. These trailing axes always contains the last axis, so there is no change in behaviour for last-axis reverse ⌽⍵ and reduce F/⍵. For example, F⍤2 forces F to work on matrices, and matrices have rows.

As their names suggest, reverse-first and reduce-first act along the first axis of an array. For a cuboid, this is between the planes or across sub-matrices. For a matrix, this is between the rows or along the columns. A vector only has one axis, so both forms act in the same way.

6. Match the following rank operands with their descriptions. Each use of rank (a to e) pairs with two of the 10 description boxes below.

   a    b    c    d     e
┌────┬────┬───┬─────┬──────┐
│⍤1 3│⍤2 1│⍤¯1│⍤0 99│⍤99 ¯1│
└────┴────┴───┴─────┴──────┘
-----------------------------------------
┌─┐ ┌────────────────┐ ┌────────────┐
│⍵│ │major cells of ⍺│ │vectors of ⍺│
└─┘ └────────────────┘ └────────────┘
┌────────────────┐ ┌─┐ ┌──────────────┐
│major cells of ⍵│ │⍺│ │3D arrays of ⍵│
└────────────────┘ └─┘ └──────────────┘
┌────────────────┐ ┌────────────┐
│major cells of ⍵│ │scalars of ⍺│
└────────────────┘ └────────────┘
┌────────────────┐ ┌────────────────┐
│matrices of ⍺   │ │vectors of ⍵    │
└────────────────┘ └────────────────┘

a:     vectors of ⍺ (⍤1 3)   3D arrays of ⍵
b:    matrices of ⍺ (⍤2 1)   vectors of ⍵
c: major cells of ⍺ (⍤¯1)    major cells of ⍵
d:     scalars of ⍺ (⍤0 99)  ⍵
e:                ⍺ (⍤99 ¯1) major cells of ⍵
7. For each name below, suggest the rank for arrays with that name.

┌────────┬────────────────────┐
│Scalar  │                    │
├────────┼────────────────────┤
│Vector  │rank-1              │
├────────┼────────────────────┤
│Matrix  │                    │
├────────┼────────────────────┤
│Table   │                    │
├────────┼────────────────────┤
│List    │                    │
├────────┼────────────────────┤
│Cube    │                    │
├────────┼────────────────────┤
│4D array│                    │
├────────┼────────────────────┤
│2D array│                    │
└────────┴────────────────────┘

┌────────┬────────────────────┐
│Scalar  │rank-0              │
├────────┼────────────────────┤
│Vector  │rank-1              │
├────────┼────────────────────┤
│Matrix  │rank-2              │
├────────┼────────────────────┤
│Table   │rank-2              │
├────────┼────────────────────┤
│List    │rank-1              │
├────────┼────────────────────┤
│Cube    │rank-3              │
├────────┼────────────────────┤
│4D array│rank-4              │
├────────┼────────────────────┤
│2D array│rank-2              │
└────────┴────────────────────┘
8. Find the values of j and k in each of the two expressions below.

m ← 7 2⍴1 1 2 4 3 7 4 3 5 3 6 2 2 3
1.       0 10(×⍤j k)m
0 10
0 40
0 70
0 30
0 30
0 20
0 30

2.       (2×⍳7)(+⍤j k)m
 3  3
6  8
9 13
12 11
15 13
18 14
16 17

1. (j k) ← 1

2. (j k) ← 0 1

9. Rank Matching
Write a function R1 which uses catenate , with the rank operator ⍤ to merge a vector and matrix into a single 3D array.

      'ABC' R1 2 3⍴⍳6
A 1
B 2
C 3

A 4
B 5
C 6

Hint

You can apply rank multiple times for a single function e.g. F⍤j⍤k.

R1 ← ,⍤0⍤1

When chaining multiple uses of the rank operator, think of doing multiple pairings — from the outside inwards. We have a vector of scalars ABC, and a matrix of rows of scalars 2 3⍴⍳6. The result wants to pair scalars from ⍺ with scalars from ⍵. However, we cannot do this simply F⍤0 because of our rank mismatch. What we can do is use rank once to pair up equivalent shapes, and then use rank 0. Therefore we have to pair rows (vectors, rank 1) first (outside) and then within each of those pairings, pair up our scalars (rank 0) inside.

      'ABC' ({⍺⍵}⍤0) 2 3⍴⍳6
RANK ERROR
'ABC'({⍺ ⍵}⍤0)2 3⍴⍳6
∧

      'ABC'({⍺ ⍵}⍤1)2 3⍴⍳6
┌───┬─────┐
│ABC│1 2 3│
├───┼─────┤
│ABC│4 5 6│
└───┴─────┘

      'ABC'({⍺ ⍵}⍤0⍤1)2 3⍴⍳6
A 1
B 2
C 3

A 4
B 5
C 6

It just so happens that stranding two scalars {⍺⍵} is the same as concatenating them {⍺,⍵}. For more complex arrays these will not be the same.

      'ABC'(,⍤0⍤1)2 3⍴⍳6
A 1
B 2
C 3

A 4
B 5
C 6

Chaining multiple uses of rank goes from right-to-left, outer most pairing to innermost. That is, in (,⍤0⍤1), we pair rows (1) outside and scalars (0) within our row pairings.

10. Split k-cells
The split function ↓⍵ splits an array of rank ≥2 by rows, returning an array of shape ¯1↓⍴⍵. Use enclose ⊂⍵ with the rank operator ⍤ to create a function Split which always splits an array into a nested vector of the major cells of ⍵.

      Split 3 2 2 3⍴⍳9
┌─────┬─────┬─────┐
│1 2 3│4 5 6│7 8 9│
│4 5 6│7 8 9│1 2 3│
│     │     │     │
│7 8 9│1 2 3│4 5 6│
│1 2 3│4 5 6│7 8 9│
└─────┴─────┴─────┘

Split ← ⊂⍤¯1
      +/⍬
,/'APPLE' 'DOG' 'BISCUIT'