KX Community

Find answers, ask questions, and connect with our KX Community around the world.
KX Community Guidelines

Home Forums kdb+ Flouring the loaf

  • Flouring the loaf

    Posted by SJT on May 22, 2022 at 12:00 am

    Now heres a task that cries out for a simple solution: put a border round a matrix. My matrix is boolean and represents a QR code (yes, well come to that) but its the same problem as, say, putting a 1px border on an image.

    So lets examine it as wrapping a char matrix in spaces. Well use 3 4#"ABCDEGHIJKL".

    Amend At

    Our first strategy is to manipulate indexes, which is often an efficient approach in q. We make a larger blank matrix for the result and write the original matrix in the right place.

    Start with the shape of the matrix; that is, count the rows and columns. (Shape is a concept that q did not inherit from its ancestor APL, but is easy enough to calculate.) We use the Zen monks for a point-free expression.

    q)show M:3 4#"ABCDEFGHIJKL" "ABCD" "EFGH" "IJKL" 
    q)count each 1 first M / shape of M 3 4

    So the result shape is 5 6, and here is our blank template:

    q){n:2+count each 1 firstx; n#" "}M 
    " " " " " " " " " "

    For the last move we could use Amend Each .' to map each item of M to a row-column pair in the result. But it should be more efficient to raze M and use Amend At @ to map all its items to the vector prd[n]#" " and then reshape it. Something like

    q){n:2+s:count each 1 firstx; n#@[prd[n]#" "; ??? ;:;raze x]}M 
    " " " ABCD " " EFGH " " IJKL " " "

     

    Above, ??? is some expression that returns the target indices for the items of M. Lets start with an easy expression wrong, but easy. Well write the items of M into the first positions of the result.

    q){n:2+s:count each 1 firstx; n#@[prd[n]#" ";til prd s;:;raze x]}M 
    "ABCDEF" "GHIJKL" " " " " " "

     

    Next we come to an often-overlooked overload of vs and sv: they encode and decode different (and variable) arithmetic bases. English pounds have 100 pennies (once known as New Pence) but once had 240, of which 12 made a shilling; and 20 shillings a pound.

     

    q)240*4.50 / 4.50 in old pence 
    1080f 
    q)100 20 12 vs 240*4.50 / 4.50 was 4 10s 0d. 
    4 10 0f 
    q)%[;240]100 20 12 sv 4 10 0 / 4/10/- in decimal coinage 
    4.5 
    q)%[;240]100 20 12 sv 4 17 6 / not every sterling amount has an exact equivalent 
    4.875

     

    We can use vs and sv to convert between row-col pairs and equivalent vector indices.

    q){n:2+s:count each 1 firstx; n#@[prd[n]#" ";n sv flip 1 1+/:s vs/:til prd s;:;raze x]}M 
    " " " ABCD " " EFGH " " IJKL " " "

     

    The above has a certain elegance in that it is probably efficient for a large matrix, but it does seem a lot of code for a simple task. If our matrices are small, perhaps we can see a simpler way?

    Join

    Join , looks like an obvious candidate. (And it will lead us to something about flip we might not have known; but well come to that.) We have to apply it to each of four sides, but we have decided we dont necessarily need the fastest expression for this.

    Looks straightforward: Join for top and bottom, Join Each for the sides.

     

    q),[;" "] " ",'" ",M,'" " 
    " " " ABCD " " EFGH " " IJKL " " "

     

    Ah, not quite that straightforward. Joining an atom doesnt use scalar extension the same way Join Each does. We could count the first row

    q){row:enlist(count first x)#" ";" ",'(row,x,row),'" "}M 
    " " " ABCD " " EFGH " " IJKL " " "

     

    Better, but the refactoring itch remains.

    The simplest operation is the Join Each, which exploits scalar extension.

    When I flour an unbaked loaf, I dont daub flour over it, I roll it in the flour.

     

    q)reverse flip ,'[" "] M 
    "DHL" "CGK" "BFJ" "AEI" " " 
    q)reverse flip ,'[" "] reverse flip ,'[" "] M 
    "LKJI " "HGFE " "DCBA " " " 
    q)4{reverse flip ,'[" "] x}/M 
    " " " ABCD " " EFGH " " IJKL " " "

     

    I dont need the (admittedly tiny) overhead of a lambda to apply a series of unaries. I can use a composition.

    q)4(reverse flip ,'[" "]@)/M 
    " " " ABCD " " EFGH " " IJKL " " "

     

    Now heres a surprise: we dont need the Each.

    q)4(reverse flip ,[" "]@)/M 
    " " " ABCD " " EFGH " " IJKL " " "

     

    How does that work? It turns out that flip uses scalar extension. The items of its argument must conform; that is, they must be same-length lists or atoms. But the result will have same-length lists.

     

    q)flip M 
    "AEI" "BFJ" "CGK" "DHL" 
    q)flip M,enlist "XYZ" / must conform! 
    'length [0] flip M,enlist "XYZ" ^ 
    q)flip M,"X" 
    "AEIX" "BFJX" "CGKX" "DHLX" 
    q)

     

    Loaf floured! And the QR code? Watch this space.

    SJT replied 2 months, 2 weeks ago 1 Member · 0 Replies
  • 0 Replies

Sorry, there were no replies found.

Log in to reply.