kdb+: use string as variable name - kdb

How can I use a string as a variable name?
I want my variable name to be constructed during runtime, but how can I use it as a left argument and assign a value to it?
Example:
[`$"test"] : 1 / 'assign error

You could use "set" but it will create a global:
q){(`$"test") set 1;test}[]
1
q)test
1
or (as noted by user2393012 in the comments):
#[`.;`test;:;1]
If you want to avoid globals you could use some sort of namespace/dictionary/mapping:
q){d:()!();d[`$"test"]:1;d`test}[]
1

Provided .data exists, Amend At does the job:
q)#[`.data;`test;:;1] / .data not defined
'type
[0] #[`.data;`test;:;1]
^
q).data.foo: 42 / defined .data
q)#[`.data;`$"test";:;1]
`.data
q).data.test
1

Related

SPSS/macro: split string into multiple variables

I am trying to split a string variable into multiple dummy coded variables. I used these sources to get an idea of how one would achieve this task in SPSS:
https://www.ibm.com/support/pages/making-multiple-string-variables-single-multiply-coded-field
https://www.spss-tutorials.com/spss-split-string-variable-into-separate-variables/
But when I try to adapt the first one to my needs or when I try to convert the second one to a macro, I fail.
In my dataset I have (multiple) variables that contain a comma seperated string that represents different combinations of selected items (as well as missing values). For each item of a specific variable I want to create a dummy variable. If the item was selected, it should be represented with a 1 in the new dummy variable. If it was not selected, that case should be represented with a 0.
Different input variables can contain different numbers of items.
For example:
ID
VAR1
VAR2
DMMY1_1
DMMY1_2
DMMY1_3
1
1, 2
8
1
1
0
2
1
1, 3
1
0
0
3
3, 1
2, 3, 1
1
0
1
4
2, 8
0
0
0
Here is what I came up with so far ...
* DEFINE DATA.
DATA LIST /ID 1 (F) VAR1 2-5 (A) VAR2 6-12 (A).
BEGIN DATA
11, 28
21 1, 3
33, 12, 3, 1
4 2, 8
END DATA.
* MACRO SYNTAX.
* DEFINE VARIABLES (in the long run these should/will be inside the macro function, but for now I will leave them outside).
NUMERIC v1 TO v3 (F1).
VECTOR v = v1 TO v3.
STRING #char (A1).
DEFINE split_var(vr = TOKENS(1)).
!DO !#pos=1 !TO char.length(!vr).
COMPUTE #char = char.substr(!vr, !#pos, 1).
!IF (!#char !NE "," !AND !#char !NE " ") !THEN
COMPUTE v(NUMBER(!#char, F1)) = 1.
!IFEND.
!DOEND.
!ENDDEFINE.
split_var vr=VAR1.
EXECUTE.
As I got more errors than I can count, it's hard to narrow down my problem. But I think the problem has something to do with the way I use the char.length() function (and I am a bit confused when to use the bang operator).
If anyone has some insights, I would really appreciate some help :)
There is a fundamental issue to understand about SPSS macro - the macro does not read or interact in any way with the data. All the macro does is manipulate text to write syntax. The syntax created will later work on the actual data when you run it.
So, for example, Your first error is using char.length(!vr) within the syntax. You are trying to get the macro to read the data, calculate the length and use, but that simply can't be done - the macro can only work with what you gave it.
Another example in your code: you calculate #char and then try to use it in the macro as !#char. So that obviously won't work. ! precedes only macro functions or arguments. #char, in your code, is neither, and it can't become one - can't read the data into the macro...
To give you a litte push forward: I understand you want the macro loop to run a different number of times for each variable, but you can't use char.length(!vr). I suggest instead have the macro loop as many times as necessary to be sure you can deal with the longest variable you'll need to work with.
And another general strategy hint - first, create syntax to deal with one specific variable and one specific delimiter. Once this works, start working on a macro, keeping in mind that the only purpose of the macro is to recreate the same working syntax, only changing the parameters of variable name and delimiter.
With my new understanding of the SPSS macro logic (thanks to #eli-k) the problem was quite easy to solve. Here is the working solution.
* DEFINE DATA.
DATA LIST /ID 1 (F) VAR1 2-5 (A) VAR2 6-12 (A).
BEGIN DATA
11, 28
21 1, 3
33, 12, 3, 1
4 2, 8
END DATA.
* DEFINE MACRO.
DEFINE #split_var(src_var = !TOKENS(1)
/dmmy_var_label = !DEFAULT(dmmy) !TOKENS(1)
/dmmy_var_lvls = !TOKENS(1))
NUMERIC !CONCAT(!dmmy_var_label,1) TO !CONCAT(!dmmy_var_label, !dmmy_var_lvls) (F1).
VECTOR #dmmy_vec = !CONCAT(!dmmy_var_label,1) TO !CONCAT(!dmmy_var_label, !dmmy_var_lvls).
STRING #char (A1).
LOOP #pos=1 TO char.length(!src_var).
COMPUTE #char = char.substr(!src_var, #pos, 1).
DO IF (#char NE "," AND #char NE " ").
COMPUTE #index = NUMBER(#char, F1).
COMPUTE #dmmy_vec(#index) = 1.
END IF.
END LOOP.
RECODE !CONCAT(!dmmy_var_label,1) TO !CONCAT(!dmmy_var_label, !dmmy_var_lvls) (SYSMIS=0) (ELSE=COPY).
EXECUTE.
!ENDDEFINE.
* CALL MACRO.
#split_var src_var=VAR2 dmmy_var_lvls=8.

how to add integer input in a set object

unable to add an item into a set taking it as an input from user
input>>j=set()
input>>j.add(int(input()))
4
TypeError: descriptor 'add' requires a 'set' object but received a 'int'
2 ways to do it:
j = set()
j.add(a) # a can be anything. If you want integer you can type cast it with int()
j = {''} # initializing a set in this way requires some default parameter otherwise python will make it a dictionary
j.add(1) or
j.add(a) # where a is an integer
j.remove('') # remove the initial string that we added
It because you have to call it to get a set
j = set()
j.add(int(input()))

Minimum arguments for variable parameters in freemarker macros

When you have variable parameters in a macro, for instance
<#macro m a b c...>
Do you have to pass a minimum of 3 arguments or 2 while calling the macro? Does the parameter c here have to have at least 1 value? Also is there any way to specify a parameter as null by default?
<#macro name param1 param2 ... paramN>
...
<#nested loopvar1, loopvar2, ..., loopvarN>
...
<#return>
...
</#macro>
Where:
name: name of macro variable. It's not an expression. It follows the
same syntax as like top-level variable references, like myMacro or
my-macro. However, it can also be written as a string literal, which
is useful if the macro name contains characters that can't be
specified in an identifier, for example <#macro "foo~bar">.... Note
that this string literal does not expand interpolations (as
"${foo}").
param1, param2, ...etc.: the name of the local variables store the
parameter values (not expression), optionally followed by = and the
default value (that's an expression). The default value can even be
another parameter, for example <#macro section title label=title>.
The parameter name uses the same syntax as like top-level variable
references, so the same features and restrictions apply.
paramN, the last parameter may optionally has 3 trailing dots (...),
which indicates that the macro takes a variable number of parameters
and the parameters that doesn't match any other parameters will be
collected in this last parameter (also called the catch-all
parameter). When the macro is called with named parameters, paramN
will be a hash containing all of the undeclared key/value pairs
passed to the macro. When the macro is called using positional
parameters, paramN will be the sequence of the extra parameter
values. (Inside the macro, to find out which was the case, you can
use myCatchAllParam?is_sequence.)
Therefore as you can see macro does not have any limitation to take N parameters.
This structure creates a macro variable (in the current namespace, if you know namespace feature). If you are new to macros and user-defined directives you should read the the tutorial about user-defined directives.
Macro variable stores a template fragment (called macro definition body) that can be used as user-defined directive. The variable also stores the name of allowed parameters to the user-defined directive. You must give value for all of those parameters when you use the variable as directive, except for parameters that has a default value. The default value will be used if and only if you don't give value for the parameter when you call the macro.
The variable will be created at the beginning of the template; it does not mater where the macro directive is placed in the template.
Example: Macro with parameters:
<#macro test foo bar baaz>
Test text, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<#-- call the macro: -->
<#test foo="a" bar="b" baaz=5*5-2/>
Output:
Test text, and the params: a, b, 23
Example: Macro with parameters and default parameter values:
<#macro test foo bar="Bar" baaz=-1>
Test text, and the params: ${foo}, ${bar}, ${baaz}
</#macro>
<#test foo="a" bar="b" baaz=5*5-2/>
<#test foo="a" bar="b"/>
<#test foo="a" baaz=5*5-2/>
<#test foo="a"/>
Output:
Test text, and the params: a, b, 23
Test text, and the params: a, b, -1
Test text, and the params: a, Bar, 23
Test text, and the params: a, Bar, -1
However, about last part of your question there is an explanation:
The null reference is by design an error in FreeMarker. Defining a custom null value - which is a string - is not a good idea for the reasons you mention. The following constructs should be used instead:
Macro and function parameters can have a default value, so the
callers can omit them
To check if a variable is null, you should use the ?? operator: <#if
(name??)>
When you use a variable that can be null, you should use the !
operator to specify a default value: name!"No name"
To check if a sequence (or a string) is empty, use the ?has_content
builtin: <#if (names?has_content)>
You can specify an empty sequence as default parameter value in a macro, and simply test whether it's empty.
When you have variable parameters in a macro, you don't have to pass a value for the last argument.
For example:
<#macro m a b c...>
a = ${a!}
b = ${b!}
<#list c?keys as attr>
${attr} = ${c[attr]}
</#list>
</#macro>
<#m a='A' b='B' />
<#m a='A' b='B' c='C' d='D'/>
Will output:
a = A
b = B
a = A
b = B
c = C
d = D

kdb+/q: Check if argument has been supplied to the function call

Say we have function fun with two arguments, second one is optional.
How to check within the function whether the second, optional argument has been supplied and act accordingly?
fun: {[x;optarg] $["optarg was supplied" like "optarg was supplied";"behavior 1"; "behavior 2"] }
fun[1;2] / behavior 1
fun[1] / behavior 2
```
I don't think this is possible. Supplying less than the specified number of arguments result in a projection.
A good alternative is to have your function accept one argument - a list. And then you can check for the existence of each element of the list.
f:{[l] $[1=count[l];
/ do something with first arg only;
/ do something with both args ]
}
Or you could have the function accept a dictionary (this allows you to set default values in the function).
q)f:{[dict] def:`a`b`c!10 20 30;
def:def upsert dict;
:def[`a] + def[`b] + def[`c] }
q)f[`a`b!5 10]
45
q)f[`a`c!5 10]
35
You can't check for number of arguments, kdb+ will report rank error when number of arguments is more than expected. But there is a workaround which will result in function which will accept any number of arguments:
q)func:('[{$[1=count x;"one";"more"]};enlist])
q)func[1]
"one"
q)func[1;2]
"more"
q)func[1;2;3]
"more"
Here is an example:
q)func:('[{$[1=count x;x[0];sum x]};enlist])
q)func[1]
1
q)func[1;2]
3
q)func[1;2;4]
7
q)func[1;2;4;7]
14
func:('[{
inputs:(`a_Required`b_Required`c_Optional`d_Optional);
optionalDefaults:`c_Optional`d_Optional!(0b;1b);
if[(count inputs)<count x;-1"Too Many input arguments";:()];
data:inputs xcols optionalDefaults, (!) . (numInputs:count x)#'(inputs;x);
show data;
data
};enlist]
)

Strings Expansion is changing order or the string

I'm trying to so some normal variable expansion in a string and, when it's in a function, it comes out out-of-order.
function MakeMessage99($startValue, $endValue) { "Ranges from $startValue to $endValue" }
MakeMessage99(1, 100)
This returns Ranges from 1 100 to then it should return Ranges from 1 to 100
Functions in powershell shouldn't use parenthesis to enclose parameters. Instead:
PS C:\> MakeMessage99 1 100
Ranges from 1 to 100
Where MakeMessage is your function, "1" is a parameter in the first position, and "100" is a parameter in the second position. According to about_Functions_Advanced_Parameters:
By default, all function parameters are positional. Windows PowerShell assigns position numbers to parameters in the order in which the parameters are declared in the function.
Powershell has several ways to check input going in. You could cast the input as a numeric type. There are also baked-in validation methods for parameters that may prevent this sort of error in the future. If you really want an integer, a simple cast would cause an array to be invalid input. For example:
function MakeMessage99 {
Param
(
[int]$startValue,
[int]$endValue
)
"Ranges from $startValue to $endValue"
}
You could also explore range validation (such as [ValidateRange(0,100)]), pattern validation (such as [ValidatePattern("[0-9][0-9][0-9][0-9]")] to validate a four-digit number) or other validation attributes listed in the link above.
This is a common pitfall in PowerShell. When you invoke...
MakeMessage99(1, 100)
...you're actually passing an array containing the values 1 and 100 as the first parameter. To pass 1 as the first parameter and 100 as the second parameter, use...
MakeMessage99 1 100