Search code examples
kdb

KDB+/Q:Input agnostic function for single and multi row tables


I have tried using the following function to derive a table consisting of 3 columns with one column data holding a list of an arbitrary schema.

fn:{
   flip `time`data`id!(x`b;(x`a`b`c`d`e);x`a)
 };

which works well on input with multiple rows i.e.:

q)x:flip `a`b`c`d`e!(5#enlist 5?10)
q)fn[`time`data`id!(x`b;(x`a`b`c`d`e);x`a)]

time data      id
-----------------
8    8 5 2 8 6 8
5    8 5 2 8 6 5
2    8 5 2 8 6 2
8    8 5 2 8 6 8
6    8 5 2 8 6 6

However fails when using input with a single row i.e.

q)x:`a`b`c`d`e!5?10
q)fn[`time`data`id!(x`b;(x`a`b`c`d`e);x`a)]

time data id
------------
8    7    7
8    8    7
8    4    7
8    4    7
8    6    7

which is obviously incorrect. One might fix this by using enlist i.e.

q)x:enlist `a`b`c`d`e!5?10
q)fn[`time`data`id!(x`b;(x`a`b`c`d`e);x`a)]

time| 8
data| 7 8 4 4 6
id  | 7

Which is correct, however if one were to apply this in the function i.e.

fn:{
   flip enlist `time`data`id!(x`b;(x`a`b`c`d`e);x`a)
 };

...

 time| 2 5 8 7 9
    data| 2 5 8 7 9 2 5 8 7 9 2 5 8 7 9 2 5 8 7 9 2 5 8 7 9
    id  | 2 5 8 7 9

Which has the wrong format of data values. My question here is how might one avert this conversion issue and derive the same field values whether the argument is a multi row or single row table. Or otherwise what is the canonical implementation of this in kdb+/q Thanks

Edit: To clarify: my problem isn't necessarily with the data input as one could just apply enlist if it is only one row. My question pertains to how one might use enlist in the fn function to make single row input conform to the logic seen when using multi row tables. i.e. how to replace fn enlist input with fn data (how to make the function input agnostic) Thanks


Solution

  • Are you meaning to flip the data perpendicular to the rest of the table? Your 5 row example works because there are 5 rows and 5 columns. The single row doesn't work due to 1 row to 5 columns.

    Correct me if I'm wrong but I think this is what you want:

    fn:{([]time:x`b;data:flip x`a`b`c`d`e;id:x`a)};
    --------------------------------------------------
    t1:flip `a`b`c`d`e!(5#enlist til 5);
    a b c d e
    ---------
    0 0 0 0 0
    1 1 1 1 1
    2 2 2 2 2
    3 3 3 3 3
    4 4 4 4 4
    
    fn[t1]
    time data      id
    -----------------
    0    0 0 0 0 0 0
    1    1 1 1 1 1 1
    2    2 2 2 2 2 2
    3    3 3 3 3 3 3
    4    4 4 4 4 4 4
    --------------------------------------------------
    t2:enlist `a`b`c`d`e!til 5;
    
    a b c d e
    ---------
    0 1 2 3 4
    
    fn[t2]
    time data      id
    -----------------
    1    0 1 2 3 4 0
    
    

    Note without the flip you get this:

    ([]time:t1`b;data:t1`a`b`c`d`e;id:t1`a)
    
    time data      id
    -----------------
    0    0 1 2 3 4 0
    1    0 1 2 3 4 1
    2    0 1 2 3 4 2
    3    0 1 2 3 4 3
    4    0 1 2 3 4 4
    

    In this case the time is no longer in line with the data but it works because of 5 row and cols.

    Edit - I can't think of a better way to convert a dictionary to a table when needed other than using count first in a conditional. Note if the first key is a nested list this wouldn't work

    { $[1 = count first x;enlist x;x] } `a`b`c`d`e!til 5
    

    Note, your provided function doesn't work with this:

    {
       flip `time`data`id!(x`b;(x`a`b`c`d`e);x`a)
     }{$[1 = count first x;enlist x;x]} `a`b`c`d`e!til 5