KX Community

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

Home Forums kdb+ Joy of q: Let it snow

  • Joy of q: Let it snow

    Posted by sjt on April 7, 2022 at 12:00 am

    In a post on the Array Thinking blog I explored an array-oriented approach to a simple problem: representing snowflakes falling through the air.

    The problem is a classic for an object-oriented approach: define a Snowflake class, set wind speed as a global, define a Fall method for Snowflake with a small random element, make a collection of Snowflake instances in a property of a Sky object that iterates their Fall methods and plots them on a display. Almost writes itself.

    Wouldnt you know? Theres a solution in one line of qs ancestor language APL. It illuminates how array programmers approach problems: thinking more about gross data structures than breaking the problem into small pieces.

     

    Well start here by replicating the APL solution in q, then improving it a bit. Then well cut to a different approach entirely to add more features, and we shall all give silent thanks to the languages brevity.

    The APL solution exploits its IDE, whose editor instantly reflects changes to a variable. Well use a browser to call q. Heres snow.q.

     

    FRAME:30 80 
    generate:{"@**......... " x#prd[x]?100} 
    pic:generate FRAME 
    .z.ph:{.h.hp pic::advance pic} 
    advance:{ lt:generate each FRAME-0 1; lt[0],'enlist[lt 1], -1 _ -1 _'x } 
    PORT:5000+sum`long$"snow" system "p ",string PORT -1 "Listening on ",string PORT;

     

    Not the brutal elegance of the APL line, but straightforward enough. A generate function returns a character array with snowflakes: dots for distant flakes, larger glyphs for nearer flakes. An advance function shifts the frame down and right and generates some more flakes lt for the left and top. The HTTP GET callback .z.ph advances the state and sends it to the browser as an HTML pre block.

     

    We can do better. Snowflakes dont fall in straight lines, not even diagonal ones. They jiggle about a bit with random gusts. And if the sun is out, some of them might twinkle.

     

    .z.ph:{.h.hp pic::advance jiggle twinkle pic} 
    twinkle:{ v:raze x; v:@[v;where v="+";:;"."]; /dim i:where v="."; FRAME#@[v;floor[.1*count i]?i;:;"+"] } 
    jiggle:{ f:v i:where not null v:raze x; j:(prd[FRAME]-1)& 0|i + count[i]?-2 0 2 where 1 8 1; v[i]:" "; v[j]:f; FRAME#v }

     

    This is better a little less rigid.

     

    But the big missing is that the near flakes should be moving faster than the far flakes.

    We could do that on the character array we have already jiggled the flakes  but well now shift to a different model. Well tabulate the flakes as vectors (of row, column and depth positions) and project them onto a character array. (Thank goodness for terse languages.) Amend At is perfect for that and notice how elegantly the distance positions get converted to glyphs. Notice also the use of the frame size as an arithmetic base: FRAME sv x`r`c converts coordinates to index positions.

     

    FRAME:2#RCD:30 80 10 / rows; columns; depth 
    BOUNDS:`r`c`d!0,'RCD-1 / stay within 
    Flakes:([]r:0#0.;c:0#0.;d:0#0.) / row, col; depth 
    rnd:floor .5+ 
    disp:{FRAME#@[prd[FRAME]#" ";FRAME sv x`r`c;:;"#**......."@x`d]} rnd@

     

    Well move distant flakes less than near flakes, so we need a distance scale, and positions will be floats. The Flakes table start empty; global FALL specifies how many new flakes in each cycle and WIND the horizontal wind speed. With a distance scale TRIG we are ready to start.

     

    FALL:9 / flakes per cycle 
    PORT:5000+sum`long$"snow" / apparent movement diminishes with distance 
    TRIG:2*atan .5%1+til RCD 2 /https://elvers.us/perception/visualAngle/ 
    WIND:0.3 advance:{[f] dwd:TRIG rnd f`d; /diminish with distance gust:-.5+first 1?1f; f:update r:r+dwd, c:c+(WIND+gust)*dwd from f; f:update r:r+dwd*(count[f]?2.)-1, c:c+dwd*(count[f]?2.)-1 from f; /jiggle f:delete from f where any each not f within':BOUNDS; /fallen f upsert flip 0 1 1f*FALL?'RCD } /new flakes / callback 
    .z.ph:{.h.hp disp Flakes::advance Flakes} system "p ",string PORT -1 "Listening on ",string PORT;

     

    In the last line of advance new flakes as float vectors get appended to the table. We begin with an empty sky. Now we see the near flakes moving faster.

     

    snow2.q

     

    / constants 
    FRAME:2#RCD:30 80 10 / rows; columns; depth 
    BOUNDS:`r`c`d!0,'RCD-1 / stay within 
    FALL:9 / flakes per cycle 
    PORT:5000+sum`long$"snow" / apparent movement diminishes with distance 
    TRIG:2*atan .5%1+til RCD 2 / https://elvers.us/perception/visualAngle/ 
    WIND:0.3 / globals 
    Flakes:([]r:0#0.;c:0#0.;d:0#0.) / row, col; depth 
    / functions 
    rnd:floor .5+ disp:{FRAME#@[prd[FRAME]#" ";FRAME sv x`r`c;:;"#**......."@x`d]} rnd@ 
    advance:{[f] dwd:TRIG rnd f`d; / diminish with distance gust:-.5+first 1?1f; f:update r:r+dwd, c:c+(WIND+gust)*dwd from f; f:update r:r+dwd*(count[f]?2.)-1, c:c+dwd*(count[f]?2.)-1 from f; / jiggle f:delete from f where any each not f within':BOUNDS; f upsert flip 0 1 1f*FALL?'RCD } 
    / callback 
    .z.ph:{.h.hp disp Flakes::advance Flakes} system "p ",string PORT -1 "Listening on ",string PORT;

     

    To do

    • Make flakes sparkle at random in the sunlight.
    • Wind gusts could be stronger eddies in the air. And vertical as well as horizontal.
    • Instead of using .h.hp, compose the HTML document returned with a meta element in the head to autorefresh.

    Over to you.

    sjt replied 1 month, 2 weeks ago 2 Members · 2 Replies
  • 2 Replies
  • jbetz34

    Member
    July 4, 2022 at 12:00 am

    Very cool. A possible improvement might be to asynchronously push on a timer from the q server once connection has been established. That way you don’t have to refresh every time to see the snow fall. Can’t wait to try for myself.

  • jbetz34

    Member
    December 4, 2022 at 12:00 am

    A bit more than just Ajax… Joy of q: It’s snowing again

Log in to reply.