Five easy pieces #2: getting around

From a recent question on Stack Overflow:

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”

Write rnd without any control words (do, while, if, Cond $). For bonus points extend to multiple modes (3rd argument):

q)rnd[q+0 1 2;3;updn] “9.639” “10.639” “11.639” “9.638” “10.638” “11.638”

up:{[x;nd]string%[;s]ceiling xs:10 xexp nd}; dn:{[x;nd]string%[;s]floor xs:10 xexp nd}; rnd:{[x;nd;m] d:updn`nr!(up[;nd];dn[;nd];.Q.f[nd;]); (d m) each x};

Seems to work.

Does the job! 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:updnnr!(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?

(Hint: a single expression rnd is possible, ?80 characters.)

building on the above

rnd:{(updn`nr!(f ceiling;(f:{string(x z*s)%s:10 xexp y})floor;.Q.f’))[z][;y;x]}

 

Eliminating up and dn is a good move, but that doesn’t quite work. 

 

q)rnd:{(updnnr!(f ceiling;(f:{string(x z*s)%s:10 xexp y})floor;.Q.f'))[z][;y;x]} q)q:9.638554216867471 q)rnd[q;2;up] {string(x z*s)%s:10 xexp y}[-_-:][;2;9.638554]

 

rnd:{[x;nd;m] string%[;s]((ceiling;floor;floor 0.5+)updn`nr?m)@:x*s:10 xexp nd}

Here the case structure is not a dictionary, just a mapping from the symbols to functions. The primitives all iterate implicitly: the Each Left \: is used only to support multiple rounding modes.

In the list of unary functions the third item floor 0.5+ is an implicit composition of two unaries: floor and the projection 0.5+.

 

Spoiler
rnd:{[x;nd;m] string%;s@:x*s:10 xexp nd}?

Stealing your solution here, except floor 0.5+ can be replaced with 7h$. It is slightly more efficient and will save about 10 characters, if that matters