Variation on Iota
November 12, 2025
Several updates below!
We just encountered the following real-life problem, which oddly, if memory serves, we have never encountered before: Given a list of items, find the location of each item in a list of lists of items. An example will make it clear:
a← 'abc' 'de' '' 'fgha'
w←'hafxd'
a {...} w
3 0 3 4 1
So, it's just like Index Of (⍳), only we are looking a level deeper on the left for the existence of an item on the right. We are not concerned where in the sublist on the left the item on the right is found. It is simply the location of the sublist (where the item is found) within the main list.
Our first inclination is to flatten out a, and then lookup items in w in the new flat array:
⊃,/a
abcdefgha
(⊃,/a)⍳w
7 0 5 9 3
Update: We should have noted that the items that we are dealing with might not be scaler, thus we must use
⊃,/rather than simply∊.
Then we need to map these indices, which index into the flattened array, back to indices apropriate for the original nested array, which are given by:
⍳≢a
0 1 2 3
We can do this by counting the items in each sublist:
≢¨a
3 2 0 4
And then replicating:
(≢¨a)/⍳≢a
0 0 0 1 1 3 3 3 3
Now we can map the first set of indices into the second set of indices to get the desired indices:
(⊂(⊃,/a)⍳w)⌷(≢¨a)/⍳≢a
INDEX ERROR
Oops, we need one more index for things not found:
(1,⍨≢¨a)/⍳1+≢a
0 0 0 1 1 3 3 3 3 4
And now, with a little code golf to remove a set of parentheses:
(⊂w⍳⍨⊃,/a)⌷(1,⍨≢¨a)/⍳1+≢a
3 0 3 4 1
Bingo, we are done. Let's make it a dfn:
liota←{(⊂⍵⍳⍨⊃,/⍺)⌷(1,⍨≢¨⍺)/⍳1+≢⍺}
Now let's try a completely different approach. Consider the outer product of membership (∊):
w∘.∊a
0 0 0 1
1 0 0 1
0 0 0 1
0 0 0 0
0 1 0 0
If we look for the first occurance of a 1 in each row we get our answer:
(↓w∘.∊a)⍳¨1
3 0 3 4 1
And a little golf:
1⍳⍨¨↓w∘.∊a
3 0 3 4 1
For what it is worth, we can get rid of nesting the Boolean matrix and the each operator by using the rank operator , un-golfing in the process:
1⍳⍨(⍤1)w∘.∊a
3 0 3 4 1
Maybe we can go tacit. It looks like we have a function between a and w, and then a function on the result, that is:
g a f w
Which can be rewritten as an atop:
a (g f) w
Where:
f←∘.∊⍨
g←⍳∘1⍤1
Let's see if it works:
a (g f) w
3 0 3 4 1
Oh yeah! Now we can just combine into one function:
liota2←⍳∘1⍤1∘.∊⍨
a liota2 w
3 0 3 4 1
We can guess that while the tacit version is short and sweet, it's going to be a dog in terms of time and space due to the outer product when both arguments get large, and indeed the flat version is almost infinitely faster. That being said, in our use case, neither argument will ever be very large.
Update
Josh David provides a much nicer flat array solution using interval index that is both shorter and faster:
{1+(+\≢¨⍺)⍸⍵⍳⍨⊃,/⍺}
It's amazing the various uses of ⍸.
Another Update
Aaron Hsu writes in with another solution using ⍸, but in its monadic form Where, which we had completely forgotten or maybe never knew can take an argument of non-negative integers, as well as the more typical Boolean:
{((⍸≢¨⍺),≢⍺)[(∊⍺)⍳⍵]}
The Dyalog docs state that the model for Where can be expressed as {(,⍵)/,⍳⍴⍵}, which is exactly what we where doing in our first solution above.
We should have mentioned in the original post that our use case is not scaler integer or character items, but vectors of vectors representing names. So replacing ∊ with ⊃,/, and a little golf yields:
{(⍸≢¨⍺,1)[⍵⍳⍨⊃,/⍺]}
I was happy to discover that (⍸≢¨⍺),≢⍺ can be replaced by ⍳≢¨⍺,1.
Aaron also sent a tacit version (for scaler items only) with the new behind operator which is not even on my keyboard:
(⊂∊⍛⍳)⌷((⍸≢¨),≢)⍤⊣
I wonder if this can be simplified a bit, given the golf above.
Yet Another Update
No discussion of ⍸ should conclude without a thank you to the late Roger Hui. Thank you Roger!
We have a function, still in use, that is probably close to 40 years old, originally a trad fn, but converted to a dfn somewhere along the way:
ComputeRange←{ ⍝ ⍺ breaks, ⍵ Values
v←⍺,⍵ ⍝ Gary Bergquist Page 70
g←⍋v ⍝ Sort
l←⍴⍺ ⍝ First
s←⍴⍵
f←+\((l⍴1),s⍴0)[g] ⍝ Slighly faster than: f←+\((⍴v)↑l⍴1)[g]
i←(⍴f)⍴0 ⍝ Init
i[g]←f ⍝ Index
s⍴l↓i ⍝ Drop breaks points, enforce vec/scalar
}
Note the reference to the legendary Gary Bergquist in the original comments, from whom we probably stole the entire function. This was used for one purpose only: grouping a numeric database column.
This is equivalent to the atop:
1∘+⍸
It never would have occurred to us the many uses of this algorithm, and we never would have thought to apply this function in other situations.
Thank you Roger!