Understanding Vector Partitioning in APL#
Hello! This article will simplify a seemingly complicated specification regarding how to partition a vector into two parts. In essence, the task is to split a vector into two sections, where one section’s length is defined, and the other section contains the remaining elements.
Group Specification#
We have two options for how to define the groups:
The first group consists of the first
n
elements, with the remainder in the second group.The second group takes the last
n
elements, leaving the preceding elements for the first group.
The determination of which group is which is based on the sign of the specified length:
A positive number indicates the length of the first group.
A negative number indicates the length of the second group.
This specification is analogous to APL’s take
and drop
primitives. For example, using a positive integer to take three elements can be expressed in APL as:
3↑'DyalogAPL' ⍝ Dya
In this case, the first three elements (‘Dya’) are selected, leaving us with the remaining elements (‘logAPL’):
¯3↑'DyalogAPL' ⍝ APL
Conversely, if we want to take the last three elements:
3↓'DyalogAPL' ⍝ logAPL
Solution Approach#
Let’s outline how to approach solving this problem:
Positive Case#
In the positive case, we can use a small lambda function where:
The left argument is
Alpha
(the vector).The right argument is
Omega
(the specified length).
For example, if we want to extract the first three elements, the second group will automatically have the first three elements removed. Here’s how you could define this in APL:
3{(⍺↑⍵)(⍺↓⍵)}'DyalogAPL' ⍝ ┌───┬──────┐
⍝ │Dya│logAPL│
⍝ └───┴──────┘
Negative Case#
When the sign is flipped (negative), our groups switch order. We can correct this by reversing the groups, but the goal is to conditionally reverse them based on our inputs.
Conditional Reversal#
We can achieve this using a power operator:
The power operator works by taking a number on the right, which indicates how many times to apply the function on the left.
If the left argument (the specified length) is positive, we apply the function zero times using:
0 > left_argument ⍝ This yields 0 (no reversal) or 1 (reverse once)
For example, when applying this to a negative index:
¯3{(⌽(⍺↑⍵)(⍺↓⍵))}'DyalogAPL' ⍝ ┌──────┬───┐
⍝ │Dyalog│APL│
⍝ └──────┴───┘
Elegance in Reversal#
We notice that reverting is similar to a one-step rotation. Thus, the dyadic form of our function allows for a simple rotation instead. If the left argument is positive, we rotate zero steps; if negative, we rotate once. For instance:
3{⌽(⍺↑⍵)(⍺↓⍵)}'DyalogAPL' ⍝ ┌──────┬───┐
⍝ │logAPL│Dya│
⍝ └──────┴───┘
Final Function Structure#
Next, we can convert this function into a tested or point-free form. An observation shows a recurring pattern: the two arguments are linked via a function that groups them. This grouping, which is technically stranding into an array, can be interpreted as concatenation of enclosures.
To derive a proper fork from this concept, we eliminate the explicit mention of arguments from the inner function, leading us to three distinct functions:
The left function
The right function
The middle function, which performs the combination via concatenation of enclosures.
By capitalizing on the condition 0 > left_argument
, we can finalize our tested function. After processing through various transformations and observing patterns, we strip any redundant outer levels, yielding our final function.
For instance, we can illustrate our final function structure with:
F←(0>⊣)⌽↑,⍥⊂↓
¯3 F 'DyalogAPL' ⍝ ┌──────┬───┐
⍝ │Dyalog│APL│
⍝ └──────┴───┘
3 F 'DyalogAPL' ⍝ ┌───┬──────┐
⍝ │Dya│logAPL│
⍝ └───┴──────┘
Conclusion#
The process of partitioning a vector can initially seem daunting, but with a systematic approach rooted in APL’s principles, we can arrive at a concise and efficient solution. Thank you for reading!