πŸ’Ύ Archived View for l-3.space β€Ί log-240222-1.gmi captured on 2024-05-26 at 14:22:26. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2024-03-21)

-=-=-=-=-=-=-

Time-stamp: <2024-02-22 21h26 UTC>

Formatting strings with separators in BQN

Let's open boldly, this entire post is about the following

(∨`' 'βŠΈβ‰ )⊸/∘»∘β₯Š(βˆ˜β€Ώ3⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’)∾˘⊣

or as CBQN's `)explain` helpfully puts it

 (∨`' 'βŠΈβ‰ )⊸/∘»∘β₯Š(βˆ˜β€Ώ3⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’)∾˘⊣
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚  β”‚β”‚   β”‚ β”‚β”‚  β”‚ β”‚ β”‚ β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚  β”‚β”‚   β”‚ β”‚β”‚  β”‚ β”‚ βˆΎΛ˜β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚  β”‚β”‚   ÷⟜3β”‚  β”‚ β”‚  β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚  β”‚β”‚    β”œβ”€β‰   β”‚ β”‚  β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ Β―3βŠΈΓ—β”‚    β”‚    β”‚ β”‚  β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚   β”œβ”€βŒˆ    β”‚    β”‚ β”‚  β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚   β””β”€β”€β”€βˆ˜β”€β”€β”˜    β”‚ β”‚  β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚       β””β”€β”€β”€β”€β”€β”€βŠΈβ†‘ β”‚  β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β”‚ β”‚ β”‚              β””β”€βˆ˜βŠ’  β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ βˆ˜β”€3 β”‚                β”‚   β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚ β””β”€β”€βŠΈβ₯Š                β”‚   β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚β”‚
  β”‚  β”‚  β”‚  β”‚ β”‚ β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”ΌβŠ£
  β”‚ ' 'βŠΈβ‰   β”‚ β”‚ β”‚                          β”‚
  ∨`   β”‚   β”‚ β”‚ β”‚                          β”‚
   β”œβ”€β”€β”€β”˜   β”‚ β”‚ β”‚                          β”‚
   β””β”€β”€β”€β”€β”€β”€βŠΈ/ β”‚ β”‚                          β”‚
          β””β”€βˆ˜Β» β”‚                          β”‚
            β””β”€βˆ˜β₯Š                          β”‚
              β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β•Άβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Wouldn't it help to know what problem the above piece of code is trying to solve before going further?

Yep, it sure would. Instead of that, however, let's try an example

   F ← (∨`' 'βŠΈβ‰ )⊸/∘»∘β₯Š(βˆ˜β€Ώ3⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’)∾˘⊣
(∨`' 'βŠΈβ‰ )⊸/∘»∘β₯Š(⟨ ∘ 3 ⟩⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’)∾˘⊣

   ',' F¨ ⟨"1", "10", "100", "1000", "10000", "100000", "1000000"⟩
⟨ "1" "10" "100" "1,000" "10,000" "100,000" "1,000,000" ⟩

(indented line is input, un-indentend line is output)

What's going on in the above? First we assign this function to a name so that it's easier to call it. Then we apply it. How? We have the two inputs, ',' on the left and the list on the right, the function in the middle and each (Β¨) modifier indicating we want to apply the function to each member of the list.

Thus you might well guess that using the embedded threes and the ',', this function groups 'digits' into groups of three separated by the provided character. Well ... that's exactly it.

Training⁰ our understanding of BQN

I'm going to take various pieces of BQN for granted, but try to remind us of the larger structural parts as we go along. The first most important part is trains, see eg

2024-02-12 - APL-likes as a notation for thought

for an incompetent account. Recall from thereΒΉ that a 3-train is a composition

f g h

which when applied dyadically, that is to two arguments, yields the arguably intuitive result

𝕨 (f g h) 𝕩 <===> g(f(𝕨, 𝕩), h(𝕨, 𝕩))

BQN (and other languages) offer up an extension of this 3-train parsing rule: a 3-train may be preceded by a monadic function m and the resulting semantics are

m f g h <===> m (f g h)                                                       Β²
𝕨 (m f g h) 𝕩 <===> m(g(f(𝕨, 𝕩), h(𝕨, 𝕩)))

With this in mind, let's see whether we can descry the broad outlines of what is going on above.

Let's trim-down the picture at the top of this page for a second

(∨`' 'βŠΈβ‰ )⊸/∘»∘β₯Š(βˆ˜β€Ώ3⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’)∾˘⊣
              β”‚                         β”‚ β”‚
              β”‚                         βˆΎΛ˜β”‚
              β”‚                          β”‚β”‚
              β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”ΌβŠ£
              β”‚                          β”‚
             ∘β₯Š                          β”‚
             β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

I've purposefully suppressed some detail to emphasise the important features:

Thus the overall form of our function is

m f g h

or in greater detail

m <===> (...∘β₯Š)
f <===> (...)
g <===> ∾˘
h <===> ⊣


m f g h <===> (...∘β₯Š) (...) ∾˘ ⊣

The remaining parts of interest are the leading monadic function and the the left dyadic function in the 3-train. We'll take a quick look at those in what follows, but here, it's dangerous to go alone

take this BQN documentation

The left dyadic function

known to its friends as

 βˆ˜β€Ώ3⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’
 β”‚ β”‚ β”‚ β”‚  β”‚β”‚   β”‚ β”‚β”‚  β”‚ β”‚
 β”‚ β”‚ β”‚ β”‚  β”‚β”‚   ÷⟜3β”‚  β”‚ β”‚
 β”‚ β”‚ β”‚ β”‚  β”‚β”‚    β”œβ”€β‰   β”‚ β”‚
 β”‚ β”‚ β”‚ Β―3βŠΈΓ—β”‚    β”‚    β”‚ β”‚
 β”‚ β”‚ β”‚   β”œβ”€βŒˆ    β”‚    β”‚ β”‚
 β”‚ β”‚ β”‚   β””β”€β”€β”€βˆ˜β”€β”€β”˜    β”‚ β”‚
 β”‚ β”‚ β”‚       β””β”€β”€β”€β”€β”€β”€βŠΈβ†‘ β”‚
 β”‚ β”‚ β”‚              β””β”€βˆ˜βŠ’
 βˆ˜β”€3 β”‚                β”‚
 β””β”€β”€βŠΈβ₯Š                β”‚
    β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β•Άβ”€β”€β”€β”˜

Here our outermost structure is that of a 2-train. Recall that we may combine a monadic function m with a dyadic function f as `m f` with application semantics

𝕨 (m f) 𝕩 <===> m(f(𝕨,𝕩))

In the above our monadic function m is

βˆ˜β€Ώ3⊸β₯Š

which reshapes the list it's applied to into Nx3 (where N, or '∘' in the code above, is determined dynamically based on the length of the list). Not too much going on over there.

On the other hand the dyadic function is somewhat more worth looking at.

(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’

has two important things going on:

Here's the interesting part about about that monadic application: it's really a dyadic function being applied!

The crucial point here are the semantics of before (⊸):

f⊸g 𝕩 <===> g(f(𝕩), 𝕩)
𝕨 f⊸g 𝕩 <===> g(f(𝕨), 𝕩)

Isn't that so cool!? Incidentally the dual of before is after (⟜):

g⟜f 𝕩 <===> g(𝕩, f(𝕩))
𝕨 g⟜f 𝕩 <===> g(𝕨, f(𝕩))

and the fun observation is that using both before and after coincides with 3-trains (almost)

f⊸g⟜h 𝕩 <===> g(f(𝕩),h(𝕩)) <===> (f g h) 𝕩
𝕨 f⊸g⟜h 𝕩 <===> g(f(𝕨),h(𝕩)) <===> 𝕨 ((f⊣) g (h⊒)) 𝕩                          Β³

Nice!

Putting this together, prefixes (↑) is dyadic but we combine it with before of

(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )

which computes -3*ceiling(len( input )/3). What does taking a prefix of negative length do? Why, fill the beginning with the fill element

   Β―20↑"of course"
"           of course"

This is really the heart of the algorithm, after this we reshape the string-which-has-been-prefixed-padded-to-a-length-of-a-multiple-of-three to be a table of three-length strings, and fiddle with the table by joining on each row (∾˘) the separator (⊣).

   βˆ˜β€Ώ3⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘ "1000"
β”Œβ”€
β•΅"  1
  000"
      β”˜
   ',' ((βˆ˜β€Ώ3⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’)∾˘⊣) "1000"
β”Œβ”€
β•΅"  1,
  000,"
       β”˜
   ',' (β₯Š(βˆ˜β€Ώ3⊸β₯Š(Β―3βŠΈΓ—βŒˆ)∘(÷⟜3β‰ )βŠΈβ†‘βˆ˜βŠ’)∾˘⊣) "1000"
"  1,000,"

... unfortunately this leaves us with an ugly prefix of blanks, as well as a trailing delimiter, which brings us to

The leading monadic function

(∨`' 'βŠΈβ‰ )⊸/∘»∘β₯Š

There's not a whole lot going on here, except once again for this story about after (⊸) so that

(∨`' 'βŠΈβ‰ )⊸/ 𝕩 <===> (∨`' 'βŠΈβ‰  𝕩) / 𝕩 <===> (∨`' '≠𝕩) / 𝕩

Note: don't be caught out by ' 'βŠΈβ‰  , ' ' is not a function. In this case ⊸ is called bind

https://mlochbaum.github.io/BQN/doc/hook.html#bind

That's it!

I'm going to stop short of explaining the outstanding details, hopefully with the excellent documentation and some interest the rest can be understood readily. To me what turned this mundane exercise in programming (group digits interspersing separator) into something interesting and worthwhile was first and foremost the lure of leveraging a useful implicit composition semantics with a robust set of combinators to express a functional solution. That we might express our thoughts in terms of arrays was an added and pleasant bonus.

All in all, i do hope that this short post manage to rouse some interest⁴ in you dear reader and that your BQN (or APL-like) journey begins soon!

---

⁰ a soon-to-be-explained pun

ΒΉ or better yet, elsewhere

Β² the language is right associative

³ the parentheses around h⊒ are redundant

⁴ i'll settle for perverse curiosity