⌷ — Materialise / Index

Function. Keys: Alt-q Backslash. Hanzi: 索. Ranks: ∞ monadic, 1 ∞ dyadic

I⌷Y indexes Y. The left argument lists selectors for leading axes of Y. Axes that it omits are taken whole. ⌷Y is identity. A subscript writes a single position: m₁ is 1⌷m. After an array, Dot indexing, x.[I], pairs arrays of positions with each other instead: m.[[0 2] [1 3]] reads the items at 0,1 and 2,3, where [[0 2] [1 3]]⌷m reads a 2 by 2 block.

m←3 4⍴⍳12ₓ
1⌷m                ⍝ [4 5 6 7]ₓ
1 2⌷m              ⍝ 6ₓ

Each item of the left argument is a single position, an array of positions, an array of coordinates, :, ∞ or ¯∞. Selecting with a single position drops that axis from the result. Selecting with an array of positions puts the array’s shape in place of the axis. Arrays of positions for several axes give their cross product. An array of positions for one axis is one item, as in [[2 0]], because [2 0] gives a position on each of two axes.

v←10 20 30
[[2 0]]⌷v          ⍝ 30 10
m←3 4⍴⍳12ₓ
[1 2;0 3]⌷m        ⍝ [4 7⋄8 11]ₓ

Positions count from 0. Negative positions count from the end. ¯1 is the last position.

v←10 20 30
¯1⌷v               ⍝ 30
[[¯1 0]]⌷v         ⍝ 30 10

: takes one whole input axis. ∞ takes all remaining axes here, as Python’s ... does. At most one ∞ is allowed. ¯∞ inserts an unnamed, unkeyed axis of length one without consuming an input axis. Write each marker as a separate bracket item: [: 1], not [:1], which applies Unkey to 1. Reverse, ⌽, reverses the last axis:

m←3 4⍴⍳12ₓ
[: 1]⌷m            ⍝ [1 5 9]ₓ
⍴[¯∞ ∞]⌷m          ⍝ [1 3 4]ₓ
⍴[: ¯∞ :]⌷m        ⍝ [3 1 4]ₓ
⍴m.[∞ ¯∞]          ⍝ [3 4 1]ₓ
⌽₀m                ⍝ [8 9 10 11⋄4 5 6 7⋄0 1 2 3]ₓ

On a keyed axis, a string is one key.

T←"aa" "bb":1 2
"bb"⌷T             ⍝ 2

An array whose items are coordinates selects the item or cell at each, as ⍸ gives them. A coordinate holds a position or key for each of several leading axes, and covers them, so the next item of the left argument selects along the axis after them. A coordinate longer than the rank goes on into the item it reaches, as a path of Pick does.

m←3 4⍴⍳12ₓ
[⍸m>9]⌷m           ⍝ [10 11]ₓ
a←2 3 4⍴⍳24ₓ
[[[0 1] [1 2]] [3 0]]⌷a   ⍝ [7 4⋄23 20]ₓ
[[[1 2] [0 0]]]⌷[[10 20] [30 40 50]]   ⍝ 50 10

A path array’s axes precede the cells it reaches. ¯∞ before the path array inserts before those axes. After paths enter nested items, more selectors select within each reached cell, as they would from that cell alone, using its own keys and names. Their selections must agree in shape:

nest←[[1 2 3⋄4 5 6] [7 8 9⋄10 11 12]]
paths←[[0 1] [1 0]]
⍴[¯∞ paths]⌷nest       ⍝ [1 2 3]ₓ
⍴[paths ¯∞]⌷nest       ⍝ [2 1 3]ₓ
⍴[paths : ¯∞]⌷nest     ⍝ [2 3 1]ₓ
⍴nest.[paths ∞ ¯∞]     ⍝ [2 3 1]ₓ
[paths 0]⌷nest         ⍝ 4 7

An array of positions with named axes is a named selector, such as a named index from ⍳["i":n] or •index. A selector whose name no other selector has selects along the axis of Y with that name, wherever the selector stands in the left argument. Without such an axis, it takes the next free axis that has no other name. Its axes replace the axis it selects, with their names and any keys it has. A named vector without keys keeps the keys at the positions it selects. Selectors that share a name pair with each other, as arithmetic pairs named axes. Together they take the free axes in order, as unnamed selectors do. [i i]⌷m is therefore the diagonal. With k along axis j, [k j]⌷m reads row k of each column, as NumPy’s m[k, j] does. Selectors with different names, and unnamed selectors, combine as above. Indexing shows everyday uses.

m←3 3⍴⍳9ₓ ⋄ i←⍳["i":3] ⋄ j←⍳["j":3] ⋄ k←["j":3]⍴2 0 1
[i i]⌷m            ⍝ ["i":3]⍴[0 4 8]ₓ
⍴[i j]⌷m           ⍝ ["i":3 "j":3]ₓ
[k j]⌷m            ⍝ ["j":3]⍴[6 1 5]ₓ
sales←["city":"NY" "LA";"month":"Jan" "Feb" "Mar"]:[10 20 30⋄40 50 60]
t←⍳⍠"month" sales
sales≡[t]⌷sales     ⍝ $t

A named selector counts a negative position back from the end, as an unnamed one does, and a position outside its axis is an Index error. A NaN position reads fill. ⍠ can choose that fill. Other rules for the edges are arithmetic on positions, and •edge names them. It gives NaN outside the axis by default:

x←10ₓ×↦5ₓ ⋄ i←⍳["i":5] ⋄ clamp←•edge "clamp"
x.[i-1]                        ⍝ ["i":5]⍴[50 10 20 30 40]ₓ
x.[5•edge⁝i-1]               ⍝ ["i":5]⍴[0 10 20 30 40]ₓ
x.[(5 clamp i-1)]              ⍝ ["i":5]⍴[10 10 20 30 40]ₓ
[5•edge⁝i-1]⌷⍠["fill":¯1] x  ⍝ ["i":5]⍴[¯1 10 20 30 40]ₓ

Assignment through ⌷ replaces the selected items. A space before ← makes the whole run before it the target, as parentheses would. A missing key is added, even on an axis with no keys.

x←10 20 30
¯1⌷x ←9
x                  ⍝ 10 20 9
T←"aa" "bb":1 2
"cc"⌷T ←3
T                  ⍝ "aa" "bb" "cc":1 2 3

With an axis, ⌷⍠K applies the left argument’s items to the axes K, one axis for each position or key an item holds. A selector of coordinates lists every axis it covers, in any order, and its result takes the place of the first of them. A named selector listed for an axis with another name replaces that axis, and the result axis takes the selector’s name. An axis of Y that keeps a selector’s name pairs with it, which reads one item from each row, as NumPy’s take_along_axis does.

1⌷₁3 4⍴⍳12ₓ           ⍝ [1 5 9]ₓ
⊂[[0 1] [1 2]] (⌷₀ ₂) 2 3 4⍴⍳24ₓ    ⍝ [1 5 9⋄14 18 22]ₓ
m←["r":3 "c":3]⍴⍳9ₓ ⋄ p←["r":3]⍴2 0 1
[p]⌷⍠"c" m         ⍝ ["r":3]⍴[2 3 7]ₓ

Inverse

⌷⁻¹Y is ⌷Y.

⌷⁻¹1 2   ⍝ 1 2

Errors

  • LENGTH: selectors that share a name but differ in length, coordinates that end at cells of different shapes, or suffixes that select different shapes in them
  • INDEX: selectors consuming more axes than the array has, position outside its axis, missing key, two selectors for one axis, a named selector with no axis to select
  • DOMAIN: a position that isn’t whole, NaN, ∞ or ¯∞ inside an array of positions, coordinates of differing lengths beside another selector