How does fby work?

According to code.kx.com, fby is short for filter-by, and it is commonly used as the q equivalent to SQL's HAVING clause (though, like where, fby is a q function, and its use is not limited to where clauses).

What fby does is aggregate values from one list based on groups defined in another, parallel, list. For example, suppose we have one list of cities and another list with a few temperature samples for each. We can use fby to calculate the minimum temperature sample for each city, and then replicate those values at each position for each corresponding city:

q)city:`NY`NY`LA`SF`LA`SF`NY
q)temp:32 31 75 69 70 68 12
q)(min;temp) fby city
12 12 70 68 70 68 12
q)

Thus, NY's minimum temperature, 12, appears at every index in fby's output where `NY appears in city.

At the core of fby (like by) is group, which organizes the distinct values in a list into a dictionary mapping those values to their indices:

q)city:`NY`NY`LA`SF`LA`SF`NY
q)group city
NY| 0 1 6
LA| 2 4
SF| 3 5
q)

We can group the temperatures for each city together by indexing temp with the value of the grouped city dictionary:

q)grouped: value group city
q)temp[grouped]
32 31 12
75 70
69 68
q)

Note that the result of indexing temp with grouped is a nested list with the same shape as grouped. This is a general principle: the result of an indexing operation has the shape of the index.

Now we can apply an aggregation function to each of the temperature groups:

q)min each temp[grouped]
12 70 68
q)

We're almost there. The real trick of fby is placing each aggregation result into a new list so that each element has the correct value per the grouping list. We can use @ (functional amend) to get the job done (see also the functional apply/amend faq):

q)@[temp; grouped; :; min each temp[grouped] ]
12 12 70 68 70 68 12
q)

The real fby is just slightly more complicated to ensure that the first argument to @ has the correct type.

What does it mean for a table to be a flipped dictionary?

Short answer:

A table is a reference to a (column) dictionary.


The internal representation of a table is nearly identical to that of a dictionary. We can use the flip command to create a table from a dictionary:

q)t: ([] a: 1 2 3; b: 4 5 6; c: 7 8 9)
q)d: `a`b`c ! (1 2 3; 4 5 6; 7 8 9)
q)t ~ flip d
1b
q)

In fact, when a dictionary is flip'ed, the underlying core data structure remains untouched. The table itself is a simple, small object that refers to the original dictionary. Using .Q.w, we can measure how much more memory a table takes than the corresponding dictionary:

q).Q.w[]`used         // memory usage baseline
112992j
q)x:`a`b`c!3 3#til 9  // create a small dictionary
q).Q.w[]`used
113424j
q)x:flip x
q).Q.w[]`used         // memory usage delta is
113456j               // just 32 more bytes
q)

No matter how large the underlying dictionary is, creating a table is fast and still takes only 32 bytes:

q)x:`a`b`c!3 100000#til 10
q).Q.w[]`used
1686192j
q)x:flip x
q).Q.w[]`used
1686224j          // 32 = 1686224 - 1686192
q)


Now, Let's examine how a keyed table is related to a dictionary. We start by creating a simple keyed table:

q)t: ([] a: 1 2 3; b: 4 5 6; c: 7 8 9)
q)keyedTable: `a`b xkey t
q)keyedTable
a b| c
---| -
1 4| 7
2 5| 8
3 6| 9
q)keys keyedTable
`a`b
q)

Since keyedTable is a table, one might expect it to have the same type as t but instead, q presents the following surprise:

q)type t              
98h                  // type table as expected
q)type keyedTable
99h                  // *NOT* 98h
q)

Type 99h is the type number for dictionaries. If keyedTable really is a dictionary, we should be able to extract its key and value:

q)key keyedTable
a b
---
1 4
2 5
3 6
q)value keyedTable
c
-
7
8
9
q)

Indeed, keyedTable is a dictionary - one that holds unkeyed tables for both its key and its value:

q)type key keyedTable
98h
q)type value keyedTable
98h
q)

This suggests that we can create a keyed table by using the ! (dict) operator with two unkeyed tables:

q)(key keyedTable)!(value keyedTable)
a b| c
---| -
1 4| 7
2 5| 8
3 6| 9
q)

Lastly, joining the two flipped tables brings us back to the original dict.

q)(flip key keyedTable),(flip value keyedTable)
a| 1 2 3
b| 4 5 6
c| 7 8 9
q)

For more information, see Creating dictionaries and tables from C.

How do I execute a q function call with parameters over IPC?

Short answer:

q)phandle: hopen `:serverhost:5012
q)phandle (function; arg1; arg2 ..)
..
q)hclose phandle
q)


To invoke a function in another q process (i.e., a function that already exists in that q process), you need the following:

  1. a process handle to the other q process,
  2. the name of the pre-existing function in the other process, and
  3. the parameters you want to pass.

Then all you need to do is to apply the process handle to a list whose first element is the function name (as a symbol) and whose remaining elements are the arguments.

For example, let's start one q process, which will be our server, listening on TCP port 5012. In our server, we'll define a function square (we'll make the background color different for the server to make it easier to distinguish from the client):

$ q -p 5012             // server
..
q)\p                    // display the listening port
5012
q)square: {x * x}
q)square 3
9
q)

Next, we'll start up another q process (the client), create a process handle connected to the first q process (the server), and request the first process to compute square 5:

$ q                     // client
..
q)phandle: hopen `:localhost:5012
q)phandle (`square; 5)  // remote execution
25
q)

To call a function with more parameters, simply add them to the end of the list. We'll demonstrate by defining a 2-argument function on the server that calculates the length of a right triangle's hypotenuse:

q)hypotenuse: {sqrt sum square x,y}
q)hypotenuse[3;4]
5f
q)
q)phandle (`hypotenuse; 5; 12)
13f
q)

What if the function you're calling doesn't take any parameters? For example, we'll define a function in the server called serverTime that returns the local time according to the server:

q)serverTime: {[] .z.T}
q)serverTime[]
11:51:34.762
q)

You can't pass zero parameters over IPC:

q)phandle enlist `serverTime  // a list with just
'length                       // the function name
q)                            // is not allowed

However, you can pass whatever you like, and it won't matter:

q)phandle `serverTime`ignored
11:52:28.537
q)phandle (`serverTime; ())    // () is often used
11:52:40.923
q)phandle (`serverTime; ::)    // so is ::
11:52:47.338
q)

See :: (generic null).

So far, all of our examples involved a client invoking a predefined function on the server. You can also pass a function defined on the client to be executed on the server. To see this, let's define a global variable on the server:

q)SERVERGLOBAL: 47
q)

Now, on the client, we'll define a function called getSERVERGLOBAL to retrieve the value of SERVERGLOBAL on the server. Instead of passing the name of the function (i.e., `getSERVERGLOBAL), we pass the function's value:

q)getSERVERGLOBAL: {[] SERVERGLOBAL}
q)getSERVERGLOBAL[]
'SERVERGLOBAL                   // not defined here
q)phandle (getSERVERGLOBAL; ::) // note the missing `
47
q)

Notice that this operation does not cause getSERVERGLOBAL to become defined on the server:

q)getSERVERGLOBAL
'getSERVERGLOBAL
q)

This technique can be used to run built-in functions and anonymous functions (aka lambdas) on the server as well:

q)phandle (+; 2; 4)
6
q)phandle ({til SERVERGLOBAL - x}; 42)
0 1 2 3 4
q)

There is one more way to convey code to the server to run: you can pass the code in a string.

q)phandle "SERVERGLOBAL + 4"
51
q)

We prefer passing a function over a string, because - especially as the expression to be passed gets more complex - it's easier to read.

How do I use the functional forms of apply and amend?

Typical q code operates on all of the elements of a container at once:

q)container: 1 2 3
q)100 * container
100 200 300
q)

Sometimes we are interested in only a subset of elements from a container:

q)container: til 10
q)container
0 1 2 3 4 5 6 7 8 9
q)container[where 0 = container mod 2]
0 2 4 6 8
q)                                                                                                         

Sometimes, however, you need to update particular elements of a structure while leaving the remaining elements unchanged. That's what functional apply and amend are for; they transform specific elements of a container without touching the others. The variations are distinguished by 3 choices:

1. @ or .

Which operator is used, @ or ., determines the interpretation of the indices used to select the elements to transform.

2. container or name

The first argument is either the value of a container or the name of a global variable referring to a container. In the former case, a new object is returned; in the latter, the global variable is modified and its name is returned.

3. monadic or dyadic function

If the transformation requires additional information beyond that contained in each element itself, that is accomplished by using a dyadic function and supplying the additional information in a fourth argument to the operator.

For a detailed discussion, please see the following faqs:

Functional @ (apply)
Functional . (apply)

Lastly, note that there is another pair of overloads for @ and . - each with three arguments - called protected execution, which are invoked when the first argument to @ or . is a function or projection; protected execution is discussed in another faq.

How do I use the functional form of @ (at) apply?

Short answer:

1. @[container; indices; function]
2. @[container; indices; function; second_args]

(Note that there is another overload for @ with three arguments, called protected execution, which is invoked when the first argument to @ is a function or projection; protected execution is discussed in another faq.)


In the 3-argument case, q applies function to those elements of container specified by indices, leaving the rest of container alone. In other words, the behavior of 3-argument @ (apply) resembles that of the following function:

Although this model breaks down when the first argument is a global variable name, it is very helpful in understanding what is going on even in that case. Let's look at a couple of simple examples to clarify this:

q)list: 1 + til 8
q)list
1 2 3 4 5 6 7 8
q)@[list; 0; neg]         / negate element 0
-1 2 3 4 5 6 7 8
q)list                    / list remains unmodified
1 2 3 4 5 6 7 8
q)@[list; 1 3 4; {x * x}] / square elements 1, 3, and 4
1 4 3 16 25 6 7 8
q)

The diagram below illustrates the last example above:


Because the new container has the same type and shape as the original, function must return the same type as its argument, unless the container is a mixed list (i.e., type 0):

q)@[list; 4 5 6 7; {x div 2}] / list is an int list
1 2 3 4 2 3 3 4
q)@[list; 4 5 6 7; {x % 2}]   / but % returns float
'type
q)mixed_list: 1 2, `3`4
q)type mixed_list
0h
q)@[mixed_list; 0 3; string] / anything goes with type 0
,"1"
2
`3
,"4"
q)

Next, we'll consider the second case listed at the top of this faq. When function is dyadic and a fourth argument is supplied, @ (apply) behaves like the following function:

The basic idea is still the same, i.e., to transform selected elements while leaving the rest of the container intact. The difference is that, instead of function being modified by each, it is modified by '(each-both) (also see this faq on each-both and multivalent each), so that the selected elements of container are paired up with the corresponding elements from second_args:

q)@[list; 1 3 5 7; +; 10 20 30 40]
1 12 3 24 5 36 7 48
q)@[list; (1 3 5 7; 0 2 4 6); *; 2 -2]
-2 4 -6 8 -10 12 -14 16
q)

One function often used with 4-argument @ (apply), is : (amend). We can replace selected elements with a certain value (or values) as follows:

q)list: 1 + til 8
q)@[list; 1 3 5 7; :; 47]
1 47 3 47 5 47 7 47
q)@[list; 1 3 5 7; :; 10 20 30 40]
1 10 3 20 5 30 7 40
q)

It might appear that the above example exposes a flaw in our model, apply_dyadic, of @ (apply). However, recall that, when modified by an adverb, : (amend) does not modify its first argument:

q):'[list 1 2 3; 20 30 40]
20 30 40                     / :' returns its 2nd argument
q)list                       / without modifying its 1st
1 2 3 4 5 6 7 8
q)

Where our models do break down is when, as alluded to earlier, the first argument is the name of global variable referring to a container, rather than the value of a container. In that case, the mechanics of the operation are the same, but original container is modified, and the return value is the name:

q)list: 1 + til 8
q)@[`list; 0; neg]
`list
q)list
-1 2 3 4 5 6 7 8
q)

This behavior is handy for writing functions of your own that work in both scenarios - either creating a new value or modifying one in place - like the following:


We can also use @ to apply a function to selected entries in a dictionary:

q)dictionary: `a`b`c`d ! `1`2, 3 4
q)dictionary
a| `1
b| `2
c| 3
d| 4
q)@[dictionary; `a`b; {"I"$ string x}]
a| 1
b| 2
c| 3
d| 4
q)

Tables can be transformed as well. Consider the following table:

q)t: ([] col1: `foo`bar`baz; col2: 5 10 15f)
q)t
col1 col2
---------
foo  5   
bar  10  
baz  15  
q)

Recall that each row of a table is a dictionary:

q)t 0
col1| `foo
col2| 5f
q)

Thus, we can use @ (apply) to accomplish an update in the following manner:

q)update col2 - 4 from t where i > 0
col1 col2
---------
foo  5   
bar  6  
baz  11  
q)@[t; 1 2; {[row] row[`col2] -: 4; row}]
col1 col2
---------
foo  5   
bar  6  
baz  11  
q)

We could also obtain the previous result using nested calls to @ (apply):

q)@[t; 1 2; @[; `col2; -[; 4]]]
col1 col2 
---------
foo  5 
bar  6
baz  11
q)

Since we can index tables using column names, we can double all of the entries in col2 as follows:

q)@[t; `col2; 2*]
col1 col2
---------
foo  10  
bar  20  
baz  30  
q)

How do I use the functional form of . (dot) apply?

Short answer:

1. .[container; indices; function]
2. .[container; indices; function; second_args]

(Note that there is another overload for . with three arguments, called protected execution, which is invoked when the first argument to . is a function or projection; protected execution is discussed in another faq.)


In the 3-argument case, q applies function to those elements of container specified by indices, leaving the rest of container alone.

The behavior of . (apply) is very similar to that of @ (apply) (which is described in another faq). The only difference between @ (apply) and . (apply) is that the indices, in the case of . (apply), are applied at depth along the dimensions of the container. The following code behaves like . (apply) for a two-dimensional container:

To explore the different treatment of the indices argument between @ (apply) and . (apply), we'll consider the simplest, multi-dimensional container: a two-dimensional list, aka a matrix.

q)matrix: 0N 4 # til 16
q)matrix
0  1  2  3 
4  5  6  7 
8  9  10 11
12 13 14 15
q)

We can use simple indexing of matrix with both @ (index) and . (index) to illustrate the difference:

q)@[matrix; 0]           // fetch row 0
0 1 2 3
q)@[matrix; 0 0]         // fetch matrix[0;0] fails.  why?
0 1 2 3                  // @ cannot index more
0 1 2 3                  // than one dimension
q)
q).[matrix; 0 0]         // dot indexes "at depth"
0

Now let's apply a function to selected elements from matrix using . (apply):

q).[matrix; (0; ::); 100+]
100 101 102 103
4   5   6   7  
8   9   10  11 
12  13  14  15 
q).[matrix; 0 0; 100+]
100 1  2  3 
4   5  6  7 
8   9  10 11
12  13 14 15
q).[matrix; (::; 0); 100+]
100 1  2  3 
104 5  6  7 
108 9  10 11
112 13 14 15
q)

Notice that we used :: in order to elide a dimension from the indices.

We can also use . (apply) with dictionaries of lists:

q)dict: `a`b ! (1 2 3; 4 5 6)
q).[dict; (`a; 0); 50*]
a| 50 2 3
b| 4  5 6
q).[dict; (::; 1); 50*]
a| 1 100 3
b| 4 250 6
q)

The general case of applying a function at depth via functional . (apply) is not implemented for tables:

q)t: ([] a: `foo`bar; b: 5 10f)
q).[t; (0; `b); %[; 5]]
'nyi
q).[t; (`b; 0); %[; 5]]
'nyi
q)

The . form of apply has another trick up its sleeve: the empty list index. When the second parameter to . (apply) is (), the entire container is passed to function in a single invocation, as the following example demonstrates:

q).[matrix; (); 0N!]
(0 1 2 3;4 25 36 7;8 81 100 11;12 13 14 15)
0  1  2   3 
4  25 36  7 
8  81 100 11
12 13 14  15
q)

When using . (apply) in this manner, we can return anything from function; the type and shape do not matter:

q).[matrix; (); {"hello"}]
"hello"
q)


Returning to the second case, when function is dyadic, . (apply) takes a fourth argument (named second_args in our example), and each indexed element of container is paired with the corresponding element of second_args. This means that second_args must conform to indices (or be an atom). The following function expresses this behavior:

(Also see this faq on each-both and multivalent each.)

We can demonstrate how this works by altering the center square of matrix:

q).[matrix; (1 2; 1 2); *; (10 20; 100 200)]
0  1   2    3 
4  50  120  7 
8  900 2000 11
12 13  14   15
q)

How does each-both (aka multivalent each) work?

Short answer: It takes a function of N operands and creates a new function that applies the original to each set of corresponding elements from N same-length vectors.


We can categorize the ways in which q handles vectors into the following three groups:

1) Treat the vector as a single entity.
2) Perform an operation on each element of a vector.
3) Perform an operation on each set of corresponding elements from multiple vectors of the same length.

We use ' (each-both) to ask q to apply a function in the third manner.

Let's review a few examples to distinguish the three forms of vector handling described above. Consider the , (join) operator, which takes two arguments and concatenates them. When used by itself, , (join) treats a vector as a single entity:

q)1 , 2
1 2
q)1 2 3 , 10
1 2 3 10
q)1 , 10 20 30
1 10 20 30
q)1 2 , 10 20 30
1 2 10 20 30
q)

Notice that the lengths of the two vectors to be joined doesn't matter; in fact, they don't have to be vectors.

Next, as an example of applying the same operation to each element of a vector, we can use \: (each-left) to append the letter "e" to each element in a vector of strings:

q)("car"; "far"; "mar") ,\: "e"
"care"
"fare"
"mare"
q)

Lastly, we'll use ' (each-both) to join corresponding elements from two vectors of the same length (like zip from Python or Haskell):

q)1 2 3 ,' 10 20 30
1 10
2 20
3 30
q)

As an aside, the use of ' (each-both) is not needed for many built-in functions in q, because those functions (sometimes referred to as atomic functions in kx documentation) automatically assume that this is the desired behavior when presented with same-length vectors as arguments. The + operator is a good example:

q)1 2 3 + 10 20 30
11 22 33
q)

Even a user defined function, if that function's body is exclusively made up of applications of atomic functions, does not require the use of ' (each-both) to display this behavior:

q)atomic: {[x; y] (x * x) + y * y}
q)atomic'[1 2 3; 10 20 30]
101 404 909
q)atomic[1 2 3; 10 20 30]  // ' not required
101 404 909
q){[x; y] atomic[x; y]}[1 2 3; 10 20 30]
101 404 909
q)

The last example shows that such user defined atomic functions are truly atomic in the eyes of q.

The following code simulates the behavior of ' (each-both) - on functions of two arguments - by creating a new function (that acts on vectors) from the function passed as an argument:

However, ' (each-both) doesn't stop at two lists, which is why it is sometimes called multivalent each. Suppose we need to generate a bunch of html hyperlinks for a web page we are creating on-the-fly (perhaps in a custom http POST handler). Each link needs to have its own styling, so we want to add a distinct class attribute to each anchor tag.

The .h namespace includes numerous functions for generating HTML. The one we need is the .h.htac (html tag with attributes and closing tag) function:

q).h.htac[`a; `class`href ! ("myclass"; "kdbfaq.com"); "kdbfaq"]
"<a class=\"myclass\" href=\"kdbfaq.com\">kdbfaq</a>"
q)

Let's wrap .h.htac (html tag with attributes and closing tag) up in a function to create a styled link:

We'll store the information we need to generate the links in a table:

q)links
name        url                    class
----------------------------------------
"kx"        "http://kx.com"        "c1" 
"Wikipedia" "http://wikipedia.com" "c2" 
"kdbfaq"    "http://kdbfaq.com"    "c3"
q)

Now we can use ' (multivalent each) to generate all the links easily:

q)styled_link'[links `name; links `url; links `class]
"<a class=\"c1\" href=\"http://kx.com\">kx</a>"
"<a class=\"c2\" href=\"http://wikipedia.com\">Wikipedia</a>"
"<a class=\"c3\" href=\"http://kdbfaq.com\">kdbfaq</a>"
q)

Or, alternately, we can combine ' (multivalent each) with . (apply) for a shorter, more general approach to applying a function to each row in a table:

q)styled_link .' flip value flip links
"<a class=\"c1\" href=\"http://kx.com\">kx</a>"
"<a class=\"c2\" href=\"http://wikipedia.com\">Wikipedia</a>"
"<a class=\"c3\" href=\"http://kdbfaq.com\">kdbfaq</a>"
q)           

On more wrinkle: sometimes ' (each) is used where \: (each-left) or /: (each-right) would be more explicit. For instance, our earlier example in which we appended the letter "e" to each element of a list of strings could have been written as follows:

q)("car"; "far"; "mar") ,' "e"
"care"
"fare"
"mare"
q)

We find the original, more explicit form, quicker and easier to read.

How can I capture STDOUT, STDERR and the exit status of a system command invoked from q?

Short answer: 2>&1 (i.e., redirect STDERR to STDOUT), echo $?, and add parentheses.

Note: This applies to bash, and has not been tested in csh, ksh, etc.


We can invoke arbitrary external programs from q using the system command. For example:

q)system "echo Hello, world!"
"Hello, world!"
q)

External programs often signal errors by returning a non-zero exit code, which is stored in the shell variable ?:

$ ls nonexistent_file
ls: nonexistent_file: No such file or directory
$ echo $?
1
$ foo
-bash: foo: command not found
$ echo $?
127

When an external program called from system in q runs into a situation like this, q detects the non-zero exit code from the child process and raises a signal, 'os:

q)system "ls nonexistent_file"
ls: nonexistent_file: No such file or directory
'os
q)

When trying out ideas at the console as in the above example, the full details of the error are available on the screen. Getting those error details programmatically is a bit trickier, however. Consider the following function, in which we use protected execution to invoke an error handler when the 'os signal is raised:

use_protected_execution: {[]
    : @[system;
        "ls nonexistent_file";
        {[error_info] -1 "error is ", error_info;}];
    }

The error handler in use_protected_execution knows only that an operating system error occurred. None of the error details are available within the code:

q)use_protected_execution[]
ls: nonexistent_file: No such file or directory  // invisible
error is os
q)

We can improve the situation somewhat by appending "; echo $?" to the command:

q)result: system "ls nonexistent_file; echo $?"
q)result
,"1"    // actually a list of strings
q)

This enables us to distinguish error codes as follows:

distinguish_error_codes: {[]
    result: system "ls nonexistent_file; echo $?";
    exit_code: "I" $ last result;
    $[0   = exit_code;
        ; // everything's alright
      1   = exit_code;
        ; // minor problem
      2   = exit_code;
        ; // major problem
      127 = exit_code;
        ; // command not found
    / else
        // unhandled exit code
      ]}

What we really want, though, is to capture the output sent by the failed command to STDERR. You might expect the usual shell redirection syntax to do the job:

q)result: system "ls nonexistent_file 2>&1; echo $?"
ls: nonexistent_file: No such file or directory
q)result
,"1"
q)

No such luck. The trick is to put parentheses around the whole thing:

q)result: system "(ls nonexistent_file 2>&1; echo $?)"
q)result
"ls: nonexistent_file: No such file or directory"
,"1"
q)

We can wrap all this up in a handy function that, instead of raising os, raises the descriptive message output to STDERR by the child process:

call_external: {[command]
    result: system "(", command, " 2>&1; echo $?)";
    exit_code: "I" $ last result;
    if [0 < exit_code;
        ' raze -1 _ result];
    -1 _ result}

Why can't I index some elements of a dictionary?

Short answer: Check that the keys of your dictionary are uniformly atoms or lists, not both.


To experiment, let's construct two dictionaries, one with atomic keys and one with list keys.

q)dict1: `keya`keyb`keyc ! 10 20 30
q)dict1                // dictionary with atomic keys
keya| 10
keyb| 20
keyc| 30
q)dict2: (`keya1`keya2; `keyb1`keyb2; `keyc1`keyc2) ! 1 2 3
q)dict2                // dictionary with list keys
keya1 keya2| 1
keyb1 keyb2| 2
keyc1 keyc2| 3
q)dict1[`keya]         // index by atom key
10
q)dict2[`keya1`keya2]  // index by list key
1
q)

Note how dict1 contains a keys made of exclusively of atoms, and dict2 contains keys that are lists.

Now let's consider a dictionary whose keys are both atoms and lists:

q)dict3: (`keya1; `keyb1`keyb2; `keyc1`keyc2) ! 1 2 3
q)key dict3
`keya1                // The first key is an atomic symbol,
`keyb1`keyb2          // while the rest of the keys are
`keyc1`keyc2          // lists of symbols.
q)dict3[`keya1]       // The first key indexes fine,
1
q)dict3[`keyb1`keyb2] // but the remaining keys are broken;
0N 0N
q)dict3[`keyc1`keyc2] // they are treated as multiple keys.
0N 0N
q)

Even though the keys are present they cannot be found by a dictionary lookup. Reverse lookup does work:

q)dict3
`keya1      | 1    // ok
`keyb1`keyb2| 2    // broken
`keyc1`keyc2| 3    // broken
q)dict3 ? 2
`keyb1`keyb2
q)dict3 ? 3
`keyc1`keyc2
q)

Analogous behavior can be observed when searching a list that contains both atoms and lists:

q)list: key dict3
q)list ? `keya1
0
q)list ? `keyb1`keyb2
3 3
q)

Always remember that a dictionary lookup is essentially the following:

q)lookup: {[dict; thekey] value[dict] @ key[dict] ? thekey}
q)

Sometimes types in q behave differently depending on the data contained within. Take a look at this related faq to read more.

Why does the type of null returned by a failed dictionary key lookup vary?

Short answer: A failed dictionary search returns the null corresponding to the first dictionary entry's value.


Let's test with the following two dictionaries, sfdict and fsdict:

q)sfdict: `symbol`float ! (`abc; 123.4)  // type sym is first
q)fsdict: `float`symbol ! (123.4; `abc)  // type float is first
q)sfdict
symbol| `abc
float | 123.4
q)fsdict
float | 123.4
symbol| `abc
q)

Note that while both dictionaries contain the same key-value pairs, because dictionaries are an ordered list of key value pairs, the above two dictionaries do not match.

q)sfdict = fsdict
symbol| 1
float | 1
q)sfdict ~ fsdict
0b
q)

A dictionary search returns a null value when the key sought is not found:

q)null sfdict[`badkey]
1b
q)null fsdict[`badkey]
1b
q)

Getting a null back from a failed lookup is expected. On the other hand, many are surprised to find that the two nulls returned for the above two lookups differ:

q)sfdict[`badkey] ~ fsdict[`badkey]
0b
q)sfdict[`badkey]
`                    // null symbol
q)fsdict[`badkey]
0n                   // null float
q)

The types of the returned nulls are different according to the types of the first values in sfdict and fsdict. This is the same behavior observed when indexing a mixed list with an out-of-range index:

q)(`a; `b; 1f; 2f)[5]
`
q)(1f; 2f; `a; `b)[5]
0n
q)

There's a similar "first element wins" behavior with the atomicity of dictionary keys. see this faq on dictionary indexing to learn more.