Skip to content

Matchmaking

This part will cover

  • Scalar matching
  • Scalar-vector matching
  • Higher dimensional matching

In chapters 5 and 6, we've encountered many different ways to match arbitrary rank cells of arrays along different functions. We've seen that the arithmetic operators always match scalars of the arrays it is applied to, which can be done generally using the ¨ each operator; that the enclose function can be used in combination with ¨ each to match arrays with scalars and, in combination with the split function, matching arbitrary rank cells together. We've also discussed the ∘.f outer product operator which matches every pair of scalars from its array arguments, and the f.g inner product which reduces along the matching of the rows of its left argument and columns of its right argument, f[N] bracket axis notation which specified what axis functions are to act and, of course, the rank operator which generalises many of the rank operations discussed.

In this section, we review all these different methods and provide some more practical details which have been omitted for brevity in previous sections.

Scalar matching

In depth

As mentioned previously, arithmetic functions in APL are pervasive; this means that they match scalars from their argument arrays, no matter the rank or depth. Take, for example, the following highly nested array A

      A  \,\ 2/1 0
┌─┬─────┬───────────┬─────────────────┐
1│┌─┬─┐│┌───┬─────┐│┌─────┬─────────┐│
 ││11│││┌─┐│┌───┐│││┌───┐│┌───────┐││
 │└─┴─┘│││1│││1 0│││││┌─┐│││┌─────┐│││
      ││└─┘│└───┘│││││1│││││1 0 0││││
      │└───┴─────┘│││└─┘│││└─────┘│││
                 ││└───┘│└───────┘││
                 │└─────┴─────────┘│
└─┴─────┴───────────┴─────────────────┘

When multiplying by a simple scalar array, each element of A is matched to an element of the simple scalar array, and each scalar within the elements of A, no matter how deep, is multiplied by that element

      1 2 3 4 × A
┌─┬─────┬───────────┬─────────────────┐
1│┌─┬─┐│┌───┬─────┐│┌─────┬─────────┐│
 ││22│││┌─┐│┌───┐│││┌───┐│┌───────┐││
 │└─┴─┘│││3│││3 0│││││┌─┐│││┌─────┐│││
      ││└─┘│└───┘│││││4│││││4 0 0││││
      │└───┴─────┘│││└─┘│││└─────┘│││
                 ││└───┘│└───────┘││
                 │└─────┴─────────┘│
└─┴─────┴───────────┴─────────────────┘

This also applies to arithmetic functions between nested arrays, consider the sum of the following arrays B and C of the same type

      B  A[1 2 3]
┌─┬─────┬───────────┐
1│┌─┬─┐│┌───┬─────┐│
 ││11│││┌─┐│┌───┐││
 │└─┴─┘│││1│││1 0│││
      ││└─┘│└───┘││
      │└───┴─────┘│
└─┴─────┴───────────┘
      C  2 × \,\ 3/1
┌─┬─────┬───────────┐
2│┌─┬─┐│┌───┬─────┐│
 ││22│││┌─┐│┌───┐││
 │└─┴─┘│││2│││2 2│││
      ││└─┘│└───┘││
      │└───┴─────┘│
└─┴─────┴───────────┘
       B + C
┌─┬─────┬───────────┐
3│┌─┬─┐│┌───┬─────┐│
 ││33│││┌─┐│┌───┐││
 │└─┴─┘│││3│││3 2│││
      ││└─┘│└───┘││
      │└───┴─────┘│
└─┴─────┴───────────┘

However, this is not true for arbitrary functions, consider the function {'NZP'[2+×⍵]} which marks whether a number is negative, zero, or positive

      {'NZP'[2]} 4 ¯5 0 0 0 ¯3 3
PNZZZNP

      {'NZP'[2]} A[1]
P

      {'NZP'[2]} A[2]
RANK ERROR

      A[2]
┌─────┐
│┌─┬─┐│
││11││
│└─┴─┘│
└─────┘

      {'NZP'[2]}¨ A[2]
┌──┐
PP
└──┘

Notice how the function applied to A[1] and also to A[2] as long as we used the ¨ each operator to "peel off" a layer of the depth. We can continue in this fashion to apply the function to any depth by adding enough ¨ each operators

      {'NZP'[2]}¨¨¨ A[3]
┌──────────┐
│┌───┬────┐│
││┌─┐│┌──┐││
│││P│││PZ│││
││└─┘│└──┘││
│└───┴────┘│
└──────────┘
      {'NZP'[2]}¨¨¨¨ A[4]
┌───────────────┐
│┌─────┬───────┐│
││┌───┐│┌─────┐││
│││┌─┐│││┌───┐│││
││││P│││││PZZ││││
│││└─┘│││└───┘│││
││└───┘│└─────┘││
│└─────┴───────┘│
└───────────────┘

Then, by noticing that applying redundant ¨ each operators to functions acting on a simple scalar does not affect the result, we can apply the function on the whole array using five ¨ each operators

      {'NZP'[2]}¨¨¨¨¨ A
┌─┬─────┬──────────┬───────────────┐
P│┌─┬─┐│┌───┬────┐│┌─────┬───────┐│
 ││PP│││┌─┐│┌──┐│││┌───┐│┌─────┐││
 │└─┴─┘│││P│││PZ│││││┌─┐│││┌───┐│││
      ││└─┘│└──┘│││││P│││││PZZ││││
      │└───┴────┘│││└─┘│││└───┘│││
                ││└───┘│└─────┘││
                │└─────┴───────┘│
└─┴─────┴──────────┴───────────────┘

This also applies to arbitrary functions at any level of depth, consider the , catenate function between the above arrays B and C

      B
┌─┬─────┬───────────┐
1│┌─┬─┐│┌───┬─────┐│
 ││11│││┌─┐│┌───┐││
 │└─┴─┘│││1│││1 0│││
      ││└─┘│└───┘││
      │└───┴─────┘│
└─┴─────┴───────────┘
      C
┌─┬─────┬───────────┐
2│┌─┬─┐│┌───┬─────┐│
 ││22│││┌─┐│┌───┐││
 │└─┴─┘│││2│││2 2│││
      ││└─┘│└───┘││
      │└───┴─────┘│
└─┴─────┴───────────┘
      B , C
┌─┬─────┬───────────┬─┬─────┬───────────┐
1│┌─┬─┐│┌───┬─────┐│2│┌─┬─┐│┌───┬─────┐│
 ││11│││┌─┐│┌───┐││ ││22│││┌─┐│┌───┐││
 │└─┴─┘│││1│││1 0│││ │└─┴─┘│││2│││2 2│││
      ││└─┘│└───┘││      ││└─┘│└───┘││
      │└───┴─────┘│      │└───┴─────┘│
└─┴─────┴───────────┴─┴─────┴───────────┘
      B ,¨ C
┌───┬─────────┬─────────────────────┐
1 2│┌─┬─┬─┬─┐│┌───┬─────┬───┬─────┐│
   ││1122│││┌─┐│┌───┐│┌─┐│┌───┐││
   │└─┴─┴─┴─┘│││1│││1 0│││2│││2 2│││
            ││└─┘│└───┘│└─┘│└───┘││
            │└───┴─────┴───┴─────┘│
└───┴─────────┴─────────────────────┘
      B ,¨¨ C
┌─────┬─────────┬─────────────────┐
│┌───┐│┌───┬───┐│┌─────┬─────────┐│
││1 2│││1 21 2│││┌─┬─┐│┌───┬───┐││
│└───┘│└───┴───┘│││12│││1 02 2│││
              ││└─┴─┘│└───┴───┘││
              │└─────┴─────────┘│
└─────┴─────────┴─────────────────┘
      B ,¨¨¨ C
┌───────┬─────────────┬─────────────────┐
│┌─────┐│┌─────┬─────┐│┌─────┬─────────┐│
││┌───┐│││┌───┐│┌───┐│││┌───┐│┌───────┐││
│││1 2│││││1 2│││1 2│││││1 2│││1 0 2 2│││
││└───┘│││└───┘│└───┘│││└───┘│└───────┘││
│└─────┘│└─────┴─────┘│└─────┴─────────┘│
└───────┴─────────────┴─────────────────┘
      B ,¨¨¨¨ C
┌─────────┬─────────────────┬─────────────────────┐
│┌───────┐│┌───────┬───────┐│┌───────┬───────────┐│
││┌─────┐│││┌─────┐│┌─────┐│││┌─────┐│┌─────────┐││
│││┌───┐│││││┌───┐│││┌───┐│││││┌───┐│││┌───┬───┐│││
││││1 2│││││││1 2│││││1 2│││││││1 2│││││1 20 2││││
│││└───┘│││││└───┘│││└───┘│││││└───┘│││└───┴───┘│││
││└─────┘│││└─────┘│└─────┘│││└─────┘│└─────────┘││
│└───────┘│└───────┴───────┘│└───────┴───────────┘│
└─────────┴─────────────────┴─────────────────────┘

In the next section, we will introduce recursively-defined operators which make this task easier.

All pairs

In order to instead match all pairs of scalars, we can use ∘.f the outer product operator. Suppose we wanted to match every scalar in the arrays B and C together, we can first apply the enlist function to both arrays, the apply the outer product with the , catenate function

      B
1 1 1 1 1 0
      C 
2 2 2 2 2 2
      (B)∘.,(C)
┌───┬───┬───┬───┬───┬───┐
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
0 20 20 20 20 20 2
└───┴───┴───┴───┴───┴───┘

We already know that the outer product can also be expressed using the rank operator as follows

      (B)(,¨⍤0 1)(C)
┌───┬───┬───┬───┬───┬───┐
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
1 21 21 21 21 21 2
├───┼───┼───┼───┼───┼───┤
0 20 20 20 20 20 2
└───┴───┴───┴───┴───┴───┘

The rank operator here takes in a right argument array (0 1) that matches the 0-cells of the left argument (scalars) with the 1-cells of the right argument (the entire vector), with the left function argument to be applied catenate each, which applies catenate with the scalars of the left argument to each element of the right argument vector. We can also use ⍤0 99 instead of ⍤0 1 to signify taking the whole right argument.

To illustrate this matching, using , catenate instead of catenate each

      (B)
1 1 1 1 1 0
      (C)
2 2 2 2 2 2
      (B)(,0 1)(C)
1 2 2 2 2 2 2
1 2 2 2 2 2 2
1 2 2 2 2 2 2
1 2 2 2 2 2 2
1 2 2 2 2 2 2
0 2 2 2 2 2 2

This is one example of a general pattern that we will see in this section, which is that the rank operator is powerful enough to express most of the matchings without using nested arrays.

Scalar extension

Some functions in APL allow a scalar to act as if it were a higher-dimensional array by implicitly expanding it out to the required shape. The simplest examples are the arithmetic operations, instead of writing

      2 2 2 2 2 2 2 2 2 2 * 10
2 4 8 16 32 64 128 256 512 1024

We can simply write

      2 * 10
2 4 8 16 32 64 128 256 512 1024

More importantly, this also applies to non-simple scalars such as enclosed arrays, and in the following, we will see how to exploit this to match higher dimensional data.

Scalar-vector matching

One vector

Using depth

The enclose function can be used, along with ¨ each, to apply a list of scalars to a vector. The way this works is that the enclose function turns the vector into a scalar, which allows ¨ each to apply each scalar from the left argument to the single scalar on the right argument using scalar extension. Consider the following construction of the CIPHER array from previous write exercises using the rotate function with ¨⊂ each and enclose

      ⎕A
ABCDEFGHIJKLMNOPQRSTUVWXYZ
      (0 1 2)¨⎕A
┌──────────────────────────┬──────────────────────────┬──────────────────────────┐
ABCDEFGHIJKLMNOPQRSTUVWXYZBCDEFGHIJKLMNOPQRSTUVWXYZACDEFGHIJKLMNOPQRSTUVWXYZAB
└──────────────────────────┴──────────────────────────┴──────────────────────────┘
       (0 , 25)¨⎕A
ABCDEFGHIJKLMNOPQRSTUVWXYZ
BCDEFGHIJKLMNOPQRSTUVWXYZA
CDEFGHIJKLMNOPQRSTUVWXYZAB
DEFGHIJKLMNOPQRSTUVWXYZABC
EFGHIJKLMNOPQRSTUVWXYZABCD
FGHIJKLMNOPQRSTUVWXYZABCDE
GHIJKLMNOPQRSTUVWXYZABCDEF
HIJKLMNOPQRSTUVWXYZABCDEFG
IJKLMNOPQRSTUVWXYZABCDEFGH
JKLMNOPQRSTUVWXYZABCDEFGHI
KLMNOPQRSTUVWXYZABCDEFGHIJ
LMNOPQRSTUVWXYZABCDEFGHIJK
MNOPQRSTUVWXYZABCDEFGHIJKL
NOPQRSTUVWXYZABCDEFGHIJKLM
OPQRSTUVWXYZABCDEFGHIJKLMN
PQRSTUVWXYZABCDEFGHIJKLMNO
QRSTUVWXYZABCDEFGHIJKLMNOP
RSTUVWXYZABCDEFGHIJKLMNOPQ
STUVWXYZABCDEFGHIJKLMNOPQR
TUVWXYZABCDEFGHIJKLMNOPQRS
UVWXYZABCDEFGHIJKLMNOPQRST
VWXYZABCDEFGHIJKLMNOPQRSTU
WXYZABCDEFGHIJKLMNOPQRSTUV
XYZABCDEFGHIJKLMNOPQRSTUVW
YZABCDEFGHIJKLMNOPQRSTUVWX
ZABCDEFGHIJKLMNOPQRSTUVWXY

The so-called "chipmunk" operator ⊃¨⊂ is the use of the pick function with ¨⊂ each and enclose to index elements of a right array with left indices, which works even with nested arrays

      X  10 10  ? 100/6
2 2 4 3 1 4 5 3 3 6
5 4 3 3 4 2 1 1 6 1
5 1 3 2 4 4 4 2 3 4
2 1 2 5 5 2 5 4 1 1
1 3 5 3 6 6 6 3 3 5
4 3 6 1 6 2 4 1 2 1
1 1 5 3 4 4 4 6 3 2
5 4 5 3 2 6 6 5 3 3
1 6 1 2 5 6 5 3 6 3
2 1 3 1 3 2 3 2 4 1

      X ¨ '.o○0O⋄'
..O0.O
○○.0..oO0O
o○○○○.o
.oOO.O
Oo.0.O
O○○.O.0
⋄⋄⋄OO0○○
⋄⋄O00.
.0OO.oO
0OO..0

      X  5 5  ? 25/6

      X ¨ 'Welcome' 'Tervetuloa' 'Bienvenue' 'Ahlan wa Sahlan' 'Kruihcuipae' 'Huān Yíng'
┌───────────┬───────────────┬───────────────┬───────────────┬───────────┐
Tervetuloa Bienvenue      Huān Yíng      Huān Yíng      Kruihcuipae
├───────────┼───────────────┼───────────────┼───────────────┼───────────┤
Huān Yíng  Ahlan wa SahlanHuān Yíng      Tervetuloa     Bienvenue  
├───────────┼───────────────┼───────────────┼───────────────┼───────────┤
KruihcuipaeAhlan wa SahlanKruihcuipae    Huān Yíng      Kruihcuipae
├───────────┼───────────────┼───────────────┼───────────────┼───────────┤
Welcome    Welcome        Huān Yíng      Welcome        Welcome    
├───────────┼───────────────┼───────────────┼───────────────┼───────────┤
Tervetuloa Ahlan wa SahlanAhlan wa SahlanAhlan wa SahlanBienvenue  
└───────────┴───────────────┴───────────────┴───────────────┴───────────┘

Using rank

The rank operator can be straightforwardly applied here with the right argument (0 1) to apply the scalars of the left argument to the vector right argument, or vice versa.

      (2×⍳10) (0 1) 'Ha'
Ha                  
HaHa                
HaHaHa              
HaHaHaHa            
HaHaHaHaHa          
HaHaHaHaHaHa        
HaHaHaHaHaHaHa      
HaHaHaHaHaHaHaHa    
HaHaHaHaHaHaHaHaHa  
HaHaHaHaHaHaHaHaHaHa

      (⌽⍳99) (∊,¨⍤0 1) 'Bottles of beer on the wall' 'Bottles of beer. Take one down pass it around,'
99 Bottles of beer on the wall 99 Bottles of beer. Take one down pass it around, 98 Bottles of beer on the wall 98 Bottles of beer ...

Note that the rank operator does not remove layers of depth, see the following example

      'Who' 'What' 'When' 'Why' 'How' (,0 1) ' is it?'
┌────┬─┬─┬─┬─┬─┬─┬─┐
Who  is it?
├────┼─┼─┼─┼─┼─┼─┼─┤
What is it?
├────┼─┼─┼─┼─┼─┼─┼─┤
When is it?
├────┼─┼─┼─┼─┼─┼─┼─┤
Why  is it?
├────┼─┼─┼─┼─┼─┼─┼─┤
How  is it?
└────┴─┴─┴─┴─┴─┴─┴─┘

Here, the string 'Who', taken as a scalar, is catenated to ' is it?', taken as a vector, hence the result is the same as (⊂'Who') , ' is it?'. To combine these strings properly, we need to match the vector 'Who' with the vector ' is it?', which will be covered in the Vector matching subsection.

Many vectors

Using depth

In order to match scalars with vectors in a single operation, the vectors must be compiled in an array, either using a nested array or a matrix. If a nested array is used, the vectors can be treated as scalars, and the same methods mentioned above can be used. Note that using enclose is not necessary if the vectors are already in a nested array.

      LHS  (¨ 10) ,¨ '! = '
┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬──────┐
1! = 2! = 3! = 4! = 5! = 6! = 7! = 8! = 9! = 10! = 
└─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴──────┘

      RHS  ¨ !⍳10
1 2 6 24 120 720 5040 40320 362880 3628800

      LHS,¨RHS
┌──────┬──────┬──────┬───────┬────────┬────────┬─────────┬──────────┬───────────┬─────────────┐
1! = 12! = 23! = 64! = 245! = 1206! = 7207! = 50408! = 403209! = 36288010! = 3628800
└──────┴──────┴──────┴───────┴────────┴────────┴─────────┴──────────┴───────────┴─────────────┘

Using rank

The same argument to the rank operator mentioned above ⍤0 1 can be used to match the scalars (0-cells) of the left array argument to the vectors (1-cells) of the right array argument, thus the vectors have to be assembled in a matrix

      'Ha' 'Hi' 'Hu' 'He' 'Ho'
┌──┬──┬──┬──┬──┐
HaHiHuHeHo
└──┴──┴──┴──┴──┘
      'Ha' 'Hi' 'Hu' 'He' 'Ho'
Ha
Hi
Hu
He
Ho
      (2×⍳5) (0 1) 'Ha' 'Hi' 'Hu' 'He' 'Ho'
Ha        
HiHi      
HuHuHu    
HeHeHeHe  
HoHoHoHoHo

Higher dimensional matching

Using depth

As above, nesting turns arrays into scalars, which allows operators like ¨ each to match arbitrary rank arrays together as if they were scalars.

For example, we can create the conjugation table of the finnish verb 'haluta' in the present by matching vectors of strings together

      pronouns  'Minä' 'Sinä' 'Hän' 'Me' 'Te' 'He'
┌────┬────┬───┬──┬──┬──┐
Minä│Sinä│HänMeTeHe
└────┴────┴───┴──┴──┴──┘

      present  'n' 't' 'a' 'mme' 'tte' 'vat'
┌─┬─┬─┬───┬───┬───┐
ntammettevat
└─┴─┴─┴───┴───┴───┘

       pronouns ,¨ (' halua') ,¨ present
Minä haluan
Sinä haluat
Hän haluaa 
Me haluamme
Te haluatte
He haluavat

Matching matrices together is also straightforward

      (3 3⎕A) (3 3⍴⍳9)
┌───┬─────┐
ABC1 2 3
DEF4 5 6
GHI7 8 9
└───┴─────┘
      (3 31 0) (3 30 1)
┌─────┬─────┐
1 0 10 1 0
0 1 01 0 1
1 0 10 1 0
└─────┴─────┘
      (3 31 0) (3 30 1) /¨¨ (3 3⎕A) (3 3⍴⍳9)
┌───────┬───────┐
│┌─┬─┬─┐│┌─┬─┬─┐│
││A C│││ 2 ││
│├─┼─┼─┤│├─┼─┼─┤│
││ E │││4 6││
│├─┼─┼─┤│├─┼─┼─┤│
││G I│││ 8 ││
│└─┴─┴─┘│└─┴─┴─┘│
└───────┴───────┘
      ,/[1]  (3 31 0) (3 30 1) /¨¨ (3 3⎕A) (3 3⍴⍳9)
┌─┬─┬─┐
A2C
├─┼─┼─┤
4E6
├─┼─┼─┤
G8I
└─┴─┴─┘

Using rank

For higher dimensional data, the rank operator is the simplest way to match arbitrary rank cells from its right and left array arguments. The rank operator can also be applied multiple times for more complex matching operations. For example, taking the example from the Scalar-vector subsection

      'Who' 'What' 'When' 'Why' 'How' (,0 1) ' is it?'
┌────┬─┬─┬─┬─┬─┬─┬─┐
Who  is it?
├────┼─┼─┼─┼─┼─┼─┼─┤
What is it?
├────┼─┼─┼─┼─┼─┼─┼─┤
When is it?
├────┼─┼─┼─┼─┼─┼─┼─┤
Why  is it?
├────┼─┼─┼─┼─┼─┼─┼─┤
How  is it?
└────┴─┴─┴─┴─┴─┴─┴─┘

Instead, we match the 1-cells of the left array argument with the right array argument

      ↑('Who' 'What' 'When' 'Why' 'How')
Who 
What
When
Why 
How
      (↑'Who' 'What' 'When' 'Why' 'How') (,⍤1 99) ' is it?'
Who  is it?
What is it?
When is it?
Why  is it?
How  is it?

We can also expand this example to more tenses by repeated application of the rank operator. Separating out the perfect tenses into a different dimension

      (' is it?' ' was it?') (' has it been?' ' had it been?')
 is it?      
 was it?     

 has it been?
 had it been?
      ('Who' 'What' 'When' 'Why' 'How') ({ }1 99) (' is it?' ' was it?') (' has it been?' ' had it been?')
┌────┬─────────────┐
Who  is it?      
     was it?     
                 
     has it been?
     had it been?
├────┼─────────────┤
What is it?      
     was it?     
                 
     has it been?
     had it been?
├────┼─────────────┤
When is it?      
     was it?     
                 
     has it been?
     had it been?
├────┼─────────────┤
Why  is it?      
     was it?     
                 
     has it been?
     had it been?
├────┼─────────────┤
How  is it?      
     was it?     
                 
     has it been?
     had it been?
└────┴─────────────┘

We can see that each matching has one string from the left array argument and the whole right argument array. Applying ,⍤1 1 catenate with the rank operator here will match the aforementioned string with the vectors of the right argument array

      ('Who' 'What' 'When' 'Why' 'How') (,1 11 99) (' is it?' ' was it?' ' will it be?' ' would it be?')
Who  is it?      
Who  was it?     

Who  has it been?
Who  had it been?


What is it?      
What was it?     

What has it been?
What had it been?


When is it?      
When was it?     

When has it been?
When had it been?


Why  is it?      
Why  was it?     

Why  has it been?
Why  had it been?


How  is it?      
How  was it?     

How  has it been?
How  had it been?