Skip to content

Compose out spaces

Agenda

  1. write a simple lambda to remove multiple embedded blanks from a string
  2. rewrite the lambda as a composition.

Firing blanks

We start with a good example of how vector solutions differ from looping solutions.

A classic looping solution would iterate through the characters, keeping a blank only if the previous character was not blank.

A vector solution starts by flagging the blanks.

q)str:"The quick    brown fox  jumps."
q)1 null\str
T h e   q u i c k         b r o w n   f o x     j u m p s .
0 0 0 1 0 0 0 0 0 1 1 1 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 0
As an atomic function, null returns a vector the same length as its argument.

It is often helpful to compare corresponding argument and result items. (str;null str) would do that. The ‘Zen monks’ pattern1 1 f\ is slightly easier to type. (But make friends with the monks: they will help us later.)

We want to flag every position which is not a blank preceded by a blank,

q)(str;null str;not prior[and] null str)
T h e   q u i c k         b r o w n   f o x     j u m p s .
0 0 0 1 0 0 0 0 0 1 1 1 1 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 0
1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1
which easily becomes a lambda.
q){x where not prior[and] null x}str
"The quick brown fox jumps."
And we note with pleasure that – liberated from the minutiae of looping – the lambda reads very much like a statement of the solution.

The condition of unaries

All art constantly aspires towards the condition of music.
Walter Pater, “The School of Georgione” (1888)

Walter Horatio Pater was an English essayist, art and literary critic, and fiction writer, regarded as one of the great stylists. — Wikipedia

Ken Iverson (the mathematician who devised the notation from which q’s ancestor languages APL and J derived) saw even binary operations as sequences of unaries.

Thus

2+3-4*5%6

could be read as

2+ 3- 4* 5% 6
Taylor’s Aphorism:

All q expressions constantly aspire to be sequences of unaries.

Get composed

There is nothing wrong with the eminently legible lambda, but we can improve our grasp of q syntax by rewriting it as a composition.

First of all we note that where not prior[and] null is a sequence of unaries, so

where not prior[and] null ::
is a composition which, recalling the syntax of composition, we can rewrite as
where not prior[and] " "=
For the moment, name that composition f.
f:where not prior[and] " "=
Now we see the solution as str f str. Consider some other ways to write that.
str f str
str@f str
@[str;f str]
.[@;(str;f str)]
.[@] (str;f str)
.[@] 1 f\str
Ah, those Zen monks again! Can they be composed?2

To be precise, can 1 \f be written as a unary and so composed? It’s slightly tricky.

Recall that all derived functions are variadic, so in x f\ the x could be its left argument or the next unary along; i.e. x@f\. Composing 1 f\ requires us to disambiguate. We do that by writing f\[1;] making it clear that variadic f\ is being projected as a binary onto left argument 1.

q)g:.[@] f\[1;] ::
q)g str
"The quick brown fox jumps."
Substituting its definition for f
q)rmeb:.[@] (where not prior[and]" "=)\[1;] ::
q)rmeb str
"The quick brown fox jumps."

Exercises

  1. Good q style prefers keywords prior to the Each Prior iterator ': and and to &.

    Never mind: rewrite the composition using & and ':. Enlightenment awaits!

    Answer

    Recall that iterators use postfix syntax, so the following are equivalent.

    prior[and]
    (and) prior
    (&':)
    
    The parentheses make a function atom of the variadic derived function And Each Prior (&':).

    A function atom has noun syntax: it can be applied with prefix syntax – i.e. as a unary – so (&':) can be composed as a unary.

    q)rmeb:.[@] (where not (&':) " "=)\[1;] ::  / remove multiple embedded blanks
    q)rmeb str
    "The quick brown fox jumps."
    

  2. The posed problem is to remove duplicate embedded spaces. The solution given also removes duplicate trailing spaces, and all leading spaces.

    Notice the difference below between b&prev b and &':[b].

    q)(b;prev b;b&prev b;&':[b])
    1100010000011110000010001100000011b
    0110001000001111000001000110000001b
    0100000000001110000000000100000001b
    1100000000001110000000000100000001b
    
    In the lambda, use and and prev instead of prior[and] to make a lambda that collapses all duplicate spaces.

    Answer
    q)str:"  The quick    brown fox  jumps.  "
    q){x where not n and prev n:null x}str
    " The quick brown fox jumps. "
    
  3. Write the previous lambda as a composition.

    Answer
    q)cws:.[@] (where not .[&] prev\[1;] " "=)\[1;] ::  / collapse white space
    q)cws str
    " The quick brown fox jumps. "
    

  1. How many Zen monks does it take to change a lightbulb? Two: one to change it and one not to change it. 

  2. Zen monks try to be composed at all times.