Thank goodness. My immediate thought was the solution you gave last. I was watching the preceding solutions in confusion over how inefficient they were.
Pleased to see that my first reaction is the best answer! Still getting to grips with APL and wouldn’t have known the notation exactly but knew what I wanted it to do.
The monadic fork! (f g h)Y → (f Y) g (h Y) Love your videos by the way Could you do more videos on BQN, Elixir, and programming linguistic typology and geneology?
I thought of the final solution immediately, when prompted with the problem, and wrote a solution in Elixir. Quite nice, though not as concise as in APL.
This is one case where the J equivalent of the final solution surprisingly doesn't involve any wrangling to get a B-combinator instead of an S-combinator - the APL version perfectly translates character by character to the J solution */%]
The solution I came up with in J is: [: */ ] (] + [ * [: -. ]) e. @ i. @ # It's the same idea as trying to filter out elements and taking the product over, but very different in how the filtering is done! It's done by replacing unwanted elements with 1 instead of removing them. The 'e. @ i.@ #' creates an identity matrix, and this is combined with the original list using '] + [ * [: -. ]'. This 'nots' the matrix, multiplies it by the original list (producing zeros at the removed elements!), then adds the original identity matrix to increment those zeros. Then, the product across the columns is taken. The thing I find neat about it is the use of a more involved function in the center of the fork -- normally I tend towards composing things onto the outer tines of the fork, but here it just felt natural to modify the center. This made it really easy to combine the list with its derived identity matrix! -Cassie
I just did this in lua, it was a bit verbose. It's amazing how much can be done in such little space with an array lang. That last solution is wizardly.
It's unfortunate that a specific website featuring this problem disallows the last solution by forbidding you from using division, even though that method is the most efficient. But it is fun to see all the other ways you can do it.
Perhaps I'm biasing programmers to an education in mathematics, but isn't the last solution fairly straightforward from, say, factorials + algebra? I agree with your point about languages (both computer and natural) influencing how you think, but it seems APL lead the first 3 solutions down the garden path, no?
// My obligatory JS solution const arrayOfProduct = n => n.map((_, i) => n.filter((_, j) => i != j).reduce((n, m) => n * m)) // Edit: Oh, that final solution is so much better. I didn't catch the significance of the positive integer constraint. JS equivalent is of course nowhere near as pretty as APL: const finalSolution = n => { const product = n.reduce((a, b) => a * b); return n.map(e => product / e); }
With some setup, we JS devs can also use those fancy combinators! const reduce = f => a => a.reduce(f) const mapIfArray = x => Array.isArray(x) ? f => x.map(f) : f => f(x) const multiplication = a => b => a * b const multiply = (a, b) => multiplication(a)(b) const division = a => b => a / b const divide = a => b => mapIfArray(b)(division(a)) // chain combinator (a → b → c) → (b → a) → b → c const S_ = f => g => x => f (g (x)) (x) const solution = S_ ( divide ) ( reduce ( multiply ) ) solution ( [5,2,1,4,3] ) // [24, 60, 120, 30, 40]
It really is beautiful, although I would avoid calling an answer the "final solution"
Thank goodness. My immediate thought was the solution you gave last. I was watching the preceding solutions in confusion over how inefficient they were.
Pleased to see that my first reaction is the best answer! Still getting to grips with APL and wouldn’t have known the notation exactly but knew what I wanted it to do.
I quite like the 1st solution ,I for one would never have thought of using a Boolean mask for this problem
The monadic fork!
(f g h)Y → (f Y) g (h Y)
Love your videos by the way
Could you do more videos on BQN, Elixir, and programming linguistic typology and geneology?
I thought of the final solution immediately, when prompted with the problem, and wrote a solution in Elixir. Quite nice, though not as concise as in APL.
can you please make a video about using RIDE? There isn't much resources on that matter.
Thank you
This is one case where the J equivalent of the final solution surprisingly doesn't involve any wrangling to get a B-combinator instead of an S-combinator - the APL version perfectly translates character by character to the J solution */%]
The best video ever ❤️❤️❤️
I just realized there's only 2 more days until AoC! Commenting here because I figure y'all will understand! So exciting!
How do you get the boxes around the embedded vectors? I'm just starting to pick up APL and that makes visualizing the embedded vectors so much easier.
Type: "]boxing on"
I was staring at this for a minute thinking "there should be a linear time solution for this. What am I missing" then it hit me haha
The solution I came up with in J is:
[: */ ] (] + [ * [: -. ]) e. @ i. @ #
It's the same idea as trying to filter out elements and taking the product over, but very different in how the filtering is done! It's done by replacing unwanted elements with 1 instead of removing them.
The 'e. @ i.@ #' creates an identity matrix, and this is combined with the original list using '] + [ * [: -. ]'. This 'nots' the matrix, multiplies it by the original list (producing zeros at the removed elements!), then adds the original identity matrix to increment those zeros. Then, the product across the columns is taken.
The thing I find neat about it is the use of a more involved function in the center of the fork -- normally I tend towards composing things onto the outer tines of the fork, but here it just felt natural to modify the center. This made it really easy to combine the list with its derived identity matrix!
-Cassie
I just did this in lua, it was a bit verbose. It's amazing how much can be done in such little space with an array lang.
That last solution is wizardly.
Why is meli from toki pona in comments
It's unfortunate that a specific website featuring this problem disallows the last solution by forbidding you from using division, even though that method is the most efficient. But it is fun to see all the other ways you can do it.
Two notes:
The ×/ may overflow, where the final answer doesn't.
X≠Y ←→ ~X=Y
“running the tilde”
I stopped the video went to learn the basics of this alien language and came back, I'm still dizzy
Perhaps I'm biasing programmers to an education in mathematics, but isn't the last solution fairly straightforward from, say, factorials + algebra? I agree with your point about languages (both computer and natural) influencing how you think, but it seems APL lead the first 3 solutions down the garden path, no?
// My obligatory JS solution
const arrayOfProduct = n => n.map((_, i) => n.filter((_, j) => i != j).reduce((n, m) => n * m))
// Edit: Oh, that final solution is so much better. I didn't catch the significance of the positive integer constraint. JS equivalent is of course nowhere near as pretty as APL:
const finalSolution = n => {
const product = n.reduce((a, b) => a * b);
return n.map(e => product / e);
}
With some setup, we JS devs can also use those fancy combinators!
const reduce = f => a => a.reduce(f)
const mapIfArray = x => Array.isArray(x) ? f => x.map(f) : f => f(x)
const multiplication = a => b => a * b
const multiply = (a, b) => multiplication(a)(b)
const division = a => b => a / b
const divide = a => b => mapIfArray(b)(division(a))
// chain combinator (a → b → c) → (b → a) → b → c
const S_ = f => g => x => f (g (x)) (x)
const solution = S_ ( divide ) ( reduce ( multiply ) )
solution ( [5,2,1,4,3] )
// [24, 60, 120, 30, 40]
I extended the last, most efficient solution in J to allow for zeroes in the input.
case =: [: 2&