22
\$\begingroup\$

K is a programming language in the APL family designed by Arthur Whitney. While the official interpreter is closed-source and commercial, a trial version with a workspace limit of 32 bits of addressing space (which shouldn't pose problems for code golf) can be found at the Kx Systems website. This version bundled as part of the kdb+ database is colloquially known as "K4". There are also open-source K implementations available, including Kona, which is based on K3, and my own interpreter called oK, which is based on K5 and has a browser based REPL.

Kx Systems has a wiki with K4/kdb+/Q information, and the Kona GitHub page also has an excellent collection of reference materials. I have begun writing a manual for oK/k5 which may be a useful reference.

Like J and APL, K is a very terse and powerful language, and can often make a good showing in code golf. Please share tips, tricks and idioms you discover, and if you haven't tried K before consider giving it a spin! Post one tip per answer, please!

\$\endgroup\$
0

11 Answers 11

8
\$\begingroup\$

Calling a Dyad

Assuming you had a dyadic (2-argument) function f:

f: {x+2*y}

You would ordinarily call it like so:

f[3;47]

You can save a character by instead currying in the first argument and then applying the resulting partial function to the second argument by juxtaposition:

f[3]47

The same naturally works for array indexing:

  z: (12 17 98;90 91 92)
(12 17 98
 90 91 92)

  z[1;2]
92

  z[1]2
92
\$\endgroup\$
6
\$\begingroup\$

Ranges

Normally if you want to create a vector of sequential numbers you use !:

  !5
0 1 2 3 4

If you want to create a range that starts at a number other than zero, you'd then add an offset to the resulting vector:

  10+!5
10 11 12 13 14

There are a few unusual approaches which could work better for a particular situation. For example, if your base and offset are already members of a list, you could use "where" twice:

  &10 5
0 0 0 0 0 0 0 0 0 0 1 1 1 1 1
  &&10 5
10 11 12 13 14

For slower-growing sequences, consider combining "where" with "take":

  5#2
2 2 2 2 2
  &5#2
0 0 1 1 2 2 3 3 4 4

If you want to create a range of multiples, you could multiply the result of ! or you could scan (\) a list of copies of the step size:

  2*!5
0 2 4 6 8
  +\5#2
2 4 6 8 10

If you're trying to avoid parentheses, the former is better if the length of the sequence is variable and the step size is fixed, while the latter is better if the step size is what tends to vary. Picking the right variation can save 1 or 2 characters. The off-by-one difference could also work in your favor.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Another clever trick from @ngn with situational usefulness is to use e.g. 2|^x instead of (#x)#2. \$\endgroup\$
    – coltim
    Dec 3, 2020 at 20:48
6
\$\begingroup\$

Printing newlines

If your output must have a newline, you may be tempted to do this:

`0:whatever,"\n"

Don't. K2 (and likely other versions) has a neat feature where you can print a list of lines:

  `0:("abc";"def")
abc
def

So, if you need to append a newline to the output, just do:

`0:,whatever
\$\endgroup\$
6
\$\begingroup\$

Trainify (x;f x) and x f g x

Works in ngn/k

You can use "n-times-scan" 1 f\x for this. It is slightly (likely just 1 byte) longer but more reliable than converge scan f\x because it is guaranteed to return the two-item list (x;f x) even if x~f x or ~x~f f x.

For example, if you want to extract the first and last items of a list:

{(*x;*|x)}   /10 bytes
{*'(x;|x)}
{*'1|:\x}    /9 bytes
*'1|:\       /6 bytes (8 if you need enclosing parens)

Note that *'|:\ doesn't always work because the reverse might be identical to itself, in which case a 1-item list is returned instead of two.

This trick extends to the case x f g x, which can be translated to f/(x;g x) and then f/1 g\x, though it actually saves bytes only if g does not require (...) in g\.

For sorting a list of lists by the result of -/ of each row:

{<x!-/'x}   /9 bytes, or 10 with `@`
<!/1-/'\    /8 bytes, or 10 with `(...)`

Both are real examples from this answer.


Sometimes f\ works unconditionally in place of 1 f\.

/absolute value
{x|-x}
|/-:\

x f x is shorter to trainify as f/2#, than f/1::\.

/equality table
{x=/:x}
=/:/2#,

(f x)g x is only one byte longer than x g (f x) as a train: g/|1 f\.

/extract first interval based on the indices computed by f
{*(f x)_x}
{*f[x]_x}
*_/|1 f\
\$\endgroup\$
3
\$\begingroup\$

Casts from strings are expensive. Just use eval. This:

0.0$a

can become just this:

. a

In K5, it's a byte shorter:

.a
\$\endgroup\$
3
\$\begingroup\$

Each Right

Occasionally you may find yourself writing (or arriving at by simplification) a parenthesized expression applied via each-monad:

  (2#)'3 4 5
(3 3
 4 4
 5 5)

It is one character shorter to convert this pattern into an application of each-right:

  2#/:3 4 5
(3 3
 4 4
 5 5)
\$\endgroup\$
1
  • 2
    \$\begingroup\$ On the flip side, if the left-hand side expression requires parenthesis, it can save a byte to convert an each-right to an each by fixing one of the args. E.g. (x,y)in/:z to in[x,y]'z. \$\endgroup\$
    – coltim
    Oct 23, 2020 at 20:38
3
\$\begingroup\$

Cyclic Permutations

Dyadic ! in K2/K3 is "rotate":

  2!"abcd"
"cdab"
  -1!"abcd"
"dabc"

When "scan" (\) is provided with a monadic verb, it acts as a fixed-point operator. In K, fixed point operators repeatedly apply their verb to a value until the initial value is revisited or the value stops changing. Combining rotate with fixed-point scan provides a very convenient way of computing a set of cyclic permutations of a list:

  ![1]\1 2 4 8
(1 2 4 8
 2 4 8 1
 4 8 1 2
 8 1 2 4)

You can either curry ! via brackets or parenthesize to create the verb train (1!):

![1]\
(1!)\

(Note that 1!\ has an entirely different behavior!) Each of these is equivalent in length but the former may be more desirable if the rotation stride is something other than 1; in this case the brackets delimit a parenthesized subexpression "for free".

As an example, here is a short program which tests via brute force whether a string x contains the substring y (cyclically!):

{|/(y~(#y)#)'![1]\x}

K5 users beware! K5 has changed the meaning of dyadic !, so this technique isn't so easy. It will work as expected in Kona.

\$\endgroup\$
3
\$\begingroup\$

Lists of Strings

Some challenges call for a hardcoded list of strings, e.g.:

("qwertyuiop";"asdfghjkl";"zxcvbnm")

Sometimes, a few bytes can be saved using an alternative form, i.e.:

";"\:"qwertyuiop;asdfghjkl;zxcvbnm"

k4 (and several other variants) can take advantage of the symbol datatype, so long as the hardcoded strings lack spaces. For example:

$`qwertyuiop`asdfghjkl`zxcvbnm
\$\endgroup\$
0
2
\$\begingroup\$

Avoid Conditionals

K has a conditional construct (:[) which is equivalent to a Lisp-style cond:

:[cond1;result1; cond2;result2; cond3;result3; default]

You can have as many conditions as you like, and if none match the default value is returned.

Sometimes (as in recursive programs or programs that otherwise rely on a sequence of side effects), there's no getting around using one of these. However, in situations where you can afford to do a bit of extra work, you can often replace a "cond" with list indexing.

Consider the infamous fizzbuzz program. Written in a conventional imperative programming style, we might go with:

{:[~x!15;"FizzBuzz";~x!3;"Fizz";~x!5;"Buzz";x]}'1+!100

There's quite a bit of repetition here in the divisibility tests. A different approach recognizes that there are 4 cases (a number, divisibility by only 3, divisibility by only 5, divisibility by 3 and 5) and attempts to directly compute an index which chooses one of these cases from a list:

{(x;"Fizz";"Buzz";"FizzBuzz")@+/1 2*~x!/:3 5}'1+!100

Two characters shorter, and a better use of the language. Knowing that list literals are evaluated right to left, we also gain some additional golfing opportunities for combining reused subexpressions. We couldn't have easily done this in the cond-based version, since the string cases aren't evaluated at all if they aren't selected:

{(x;4#t;4_ t;t:"FizzBuzz")@+/1 2*~x!/:3 5}'1+!100

Now we've saved 5 characters overall. Incidentally, this particular example works out even nicer in k5, since we have the "pack" overload for / to handle the step of multiplying by a vector of coefficients and summing:

{(x;4_t;4#t;t:"FizzBuzz")@2 2/~3 5!\:x}'1+!100

Also note that the behavior of "find" (?), which produces an index past the end of the key list if the item is not found, is specifically designed to support handling a "default" case in this kind of indexing. Consider this fragment to convert vowels to uppercase:

{("AEIOU",x)"aeiou"?x}'

Versus one of:

{t:"aeiou"?x;:[t<5;"AEIOU"t;x]}'
{:[~4<t:"aeiou"?x;"AEIOU"t;x]}'

(I know which I'd rather read, too!)

\$\endgroup\$
2
\$\begingroup\$

Handling unicode characters (ngn/k only)

Originally from Bubbler.

Putting unicode characters inside a string turns them into a byte sequence:

 "⍺⍴⍵"
0xe28dbae28db4e28db5

this often hinders the ability to use them as individual characters. However, you can use symbols instead:

 `⍺`⍴`⍵
`0xe28dba`0xe28db4`0xe28db5

Which will group them into appropriate characters. You can then retrieve the byte sequences with $, and concatenate them into the correct string.

\$\endgroup\$
1
\$\begingroup\$

Uses of $

This tip is written with ngn/k in mind, parts might apply to other versions

  • $x string applied on a string converts each character into its own string, making $x one byte shorter than ,'x or ++x.

This transformation can be useful when replacing some characters with spaces in a string: Given a string x and a boolean vector y of the same length, ($x)@'y inserts spaces at the indices of 1's in the boolean vector.

  ($"Hello")@'0 1 1 0 1
"H  l "
  • i$C truncates strings if i is less than the length of string. On strings that case is identical to i#C, but when given lists of strings, $ always applies to individual strings, potentially saving some 's.
  3$("Hello, ";"World")
("Hel";"Wor")
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.