Not knowing APL, I unfortunately find the beginning code completely unreadable:

⍸(⊃⍵)@0⊣¯1⌽0 1⍷⍵

[Edit: My thought process here is: "This syntax is so difficult that I can't even begin to reason about it even using web search. Can GPT understand it? Yep, it seems to."

"Could this algorithm be more clearly expressed in another language? Yes, it looks like it."]

----

I asked ChatGPT (4) to explain this expression (Prompt: "Explain what this APL code does: ___"). In case others find it helpful, the response is below.

First, let's break down each symbol:

1. ⍵ represents the right argument of a function.

2. ⊃ is the disclose or first function, which picks the first item of its argument.

3. ⍷ is the "where" function, which returns a boolean mask of where its left argument appears in its right argument.

4. ⌽ is the reverse function. With the argument ¯1, it rotates its argument one place to the right.

5. @ is the "at" function, which modifies certain elements of an array. (⊃⍵)@0⊣ replaces zeros in its right argument with the first element of ⍵.

6. ⊣ is the left function, which returns the left argument, ignoring the right one.

7. ⍸ is the "where" function, which returns the indices where its argument is true (or in the case of numeric data, non-zero).

Now, let's combine these to understand the whole expression:

0 1⍷⍵ returns a boolean mask of where the sequence 0 1 appears in ⍵.

¯1⌽ then rotates this mask one place to the right.

(⊃⍵)@0⊣ replaces zeros in the rotated mask with the first element of ⍵.

Finally, ⍸ returns the indices of non-zero elements in the result.

So, this code is finding the indices in the array ⍵ where a 0 is immediately followed by a 1, and replaces these 0s with the first element of ⍵. It then returns the indices of the non-zero elements in the result.

[Edit: I'm not entirely sure but this explanation might contain an off-by-one error. I don't quite follow APL enough to tell. The translated alternative does for sure though.]

----

On that note, I'll remark that I find APL's syntax to have distinct disadvantages. I know a number of programming languages well, having written production code in them for decades. I've come across basically every mainstream programming language and find them generally readable, even though I don't have much domain knowledge, because they share so many concepts that are typically reflected with similar syntax (for loops, assignments, functions, etc.) Even relatively more "out there" languages like Haskell and Perl are somewhat readable without a ton of domain knowledge.

I find it very difficult to get on board with languages that value terseness of this sort over readability (like in K and APL – especially with APL's special symbols). Code is read so much more than it's written, in general, that transparency is valuable and important – as well as the ability to search for code fragments to understand their meaning.

I asked ChatGPT4 to translate this code into Rust, for example, to see what it would come up with. Here was its literal translation, that relies on mutating the input (analogous to the APL code). This is similar to what you'd get transliterating the code into C/C++/Java:

fn find_zero_one_and_replace(input: &mut Vec<i32>) -> Vec<usize> {
let first_element = *input.first().unwrap_or(&0);
let mut indices = vec![];
let mut prev_zero_index = None;

for (i, &item) in input.iter().enumerate() {
if item == 0 {
prev_zero_index = Some(i);
} else if item == 1 {
if let Some(index) = prev_zero_index {
input[index] = first_element;
indices.push(index);
prev_zero_index = None;
}
}
}

indices
}

[Edit: I haven't checked whether this is correct]

After some back-and-forth, asking it to write a function that doesn't mutate the input, and uses iterators, it comes up with some code that I think is probably readable even if you don't know the language well (barring some particulars of the syntax):

fn find_zero_one_indices<'a>(input: &'a [i32]) -> impl
Iterator<Item = usize> + 'a {
input.windows(2)
.enumerate()
.filter_map(move |(i, window)| {
if window == &[0, 1] {
Some(i+1)
} else {
None
}
})
}

[Edit: This code fails to detect the leading group of `1s` in the input and return a response beginning with `0`. Corrected code might be:]

fn find_zero_one_indices<'a>(input: &'a [i32]) -> impl Iterator<Item = usize> + 'a {
let mut previous = 0;
input.iter().enumerate().filter_map(move |(i, &x)| {
let result = if previous == 0 && x == 1 { Some(i) } else { None };
previous = x;
result
})
}

[Edit: The corrected form is arguably less readable than the original due to the need to detect a leading window that doesn't start with `0`. This version somewhat weakens my argument about the clarity of the alternative, but this is still readable. Perhaps someone else with more Rust experience can suggest a simplification.]

I find this readable since "windows" here is a direct reference to the concept of windowing, which is collecting sequences of items from a stream into groups. "Enumerate" iterates over a pair of sequence elements and their indexes: `(i, window)`. These concepts exist in a number of languages.

But if you didn't know what these code elements meant, you could search for them: "rust windows", "rust enumerate", "rust filter_map" and get useful results. The stdlib documentation will be the first or second link. For example, if you don't know what "filter_map" does: https://doc.rust-lang.org/std/iter/trait.Iterator.html#metho... (And the same would be true for Java, Python, etc.)

Each of these elements (windows, enumerate, filter) is syntactically joined into a conceptual pipeline, providing a framework for reasoning about how they work, in a paradigm used by many languages.

Finally we have the detection logic: `if window == &[0, 1]`. Hopefully that's readable, and I expect makes this solution easier to understand than any of the other variants.

I may have somewhat missed the point of the article: it goes on to describe how the solution can be generalized in various ways. But this is also likely true of the canonical solutions in many languages: iterators (in particular), anonymous functions, etc. enable code snippets to compose and generalize in many ways as well.