How could I make it look more like APL (and less like Python)?
Assuming you mean "in a more functional and less imperative way" by this line,
I don't think you can largely achieve that, for a good reason.
Basically, the array-based heap (and other common algorithms you see on algorithm textbooks) is designed for imperative languages. Translating it into a language whose main strength isn't imperative makes the code feel awkward and unfitting. It may also lead to code whose time complexity is actually worse than designed. See how it looks like when a similar algorithm is written in Haskell.
APL is not 100% functional, but definitely is more functional than imperative (especially when you mainly use dfns). If you want, search for "functional algorithms" and try implementing those. In the case of a heap, leftist tree isn't too complex, and supports one more \$O(\log n)\$ operation (heap merge) compared to an imperative binary heap. You can check out a nice illustration too.
But you can still improve some parts of the code.
Improvement in the algorithm
Array-based heap uses 0-based indexing by default, so the parent-child relationship is slightly awkward:
$$
\begin{align}
\text{left child}&=1+2\times\text{parent} \\
\text{right child}&=2+2\times\text{parent} \\
\text{parent}&=\Bigl\lfloor \frac{\text{child} - 1}2 \Bigr\rfloor
\end{align}
$$
If you use 1-based indexing instead, it becomes slightly cleaner:
$$
\begin{align}
\text{left child}&=2\times\text{parent} \\
\text{right child}&=1+2\times\text{parent} \\
\text{parent}&=\Bigl\lfloor \frac{\text{child}}2 \Bigr\rfloor
\end{align}
$$
I don't have other better ideas to utilize the strengths of APL, due to the underlying algorithm being purely imperative.
General tips for writing APL code
- Let the right argument of dyadic functions be the primary one (i.e. the heap).
- If you see a negation of a comparison (e.g.
~heap[chp]<heap[rpos]
), use a single equivalent function (e.g. heap[chp]≥heap[rpos]
).
- Prefer concatenation (e.g.
0,≢⍺
) over stranding (e.g. 0(≢⍺)
) when you concatenate two scalars.
- Try not to modify existing variable's contents (e.g. avoid
chp←((rpos<≢heap)∧~heap[chp]<heap[rpos])⊃chp rpos
which refers to chp
and then modifies it) when it isn't essential in implementing the algorithm. Try to choose a separate and meaningful name instead.
- Parenthesize stranding assignments (e.g.
(start pos)←⍵
instead of start pos←⍵
).
- Consider following a naming convention, and a little more descriptive names. (e.g. I can't easily see what
chp
stands for.)
- Consider adding comments to each function which briefly describe the input(s) and the output.