Skip to content

Get around

From a question on StackOverflow…

q)q:9.638554216867471 
q)rnd[q;2;`up] / round up 
"9.64" 
q)rnd[q;2;`dn] / round down 
"9.63" 
q)rnd[q;2;`nr] / round to nearest 
"9.64" 
q)rnd[q+0 1 2;3;`up] 
"9.639" 
"10.639" 
"11.639"

Some music to listen to while you work on this.

Exercises

  1. Write rnd without any control words (do, while, if, Cond $).

    Solution by calummack

    up:{[x;nd]string%[;s]ceiling x*s:10 xexp nd}
    dn:{[x;nd]string%[;s]floor x*s:10 xexp nd}
    rnd:{[x;nd;m] d:`up`dn`nr!(up[;nd];dn[;nd];.Q.f[nd;]); (d m) each x}
    
    Solves (1). Note the use of a dictionary where another language would need a control structure.

    The each can be dispensed with. Both up and dn take vector arguments. .Q.f does not, so d[2] could be .Q.f'[nd;]:

    q)rnd:{[x;nd;m] d:`up`dn`nr!(up[;nd];dn[;nd];.Q.f'[nd;]); (d m) x} 
    q)rnd[q+0 1 2;3;`up] 
    "9.639" 
    "10.639" 
    "11.639" 
    q)rnd[q+0 1 2;3;`nr] 
    "9.639" 
    "10.639" 
    "11.639"
    

    Now: can we eliminate repetition? The up and dn functions differ by only a single keyword! 😖 And without delegating one of the modes to .Q.f?

  2. Extend to multiple modes (3rd argument):

    q)rnd[q+0 1 2;3;`up`dn] 
    "9.639" "10.639" "11.639" 
    "9.638" "10.638" "11.638"
    

    A single-expression rnd is possible, <80 characters.

    Solution by jbetz34

    rnd:{[x;nd;m] string%[;s]((ceiling;floor;7h$)`up`dn`nr?m)@\:x*s:10 xexp nd}
    
    Here the number of decimals nd derives a scaling factor s, used to multiply x.

    It remains to round – up, down, or nearest integer – and divide by s before casting to string.

    Rounding to the nearest integer would be composition floor 0.5+, but projection 7h$ (cast to long) does it implicitly.

    Expression (ceiling;floor;7h$)`up`dn`nr?m evaluates to a function atom if m is an atom; to a list if a vector. All the functions are atomic, so the function or functions can be applied to x by @\:.

  3. In the sterling currency before 1971, twelve pennies made a shilling, and twenty shillings a pound.

    Write nearest to round y to the nearest multiple of x so that nearest[12] rounds pennies to shillings, and nearest[20] shillings to pounds.

    Make nearest atomic. (No loops are necessary.)

    Solution

    nearest:{x*7h$y%x}
    q)nearest[12]7 13 19 24 31
    12 12 24 24 36
    q)nearest[20]7 13 19 24 31
    0 20 20 20 40
    
    (Were you expecting something more complicated?)

  4. Extend nearest to accept vector x.

    Solution
    nearest:{x*7h$y%/:x}
    q)nearest[12 20]7 13 19 24 31
    12 12 24 24 36
    0  20 20 20 40