Introduction

This is my attempt to solve AoC 2015 using shakti. It’s a work-in-progress. The goal is for me to learn k9 and to have idiomatic solutions, not necessarily to just golf the solutions into oblivion. Comments and criticism are encouraged.

With thanks to John Estrada for his k9 manual, and Eric Wastl for creating the Advent of Code itself.

Contents

Solutions

Day 1: Not Quite Lisp

last s:+\1 -1")"=*0:"input/01.txt"
1+s?-1

This is almost a 1:1 copy of the Q version, only difference is that loading a file from disk, 0:, takes a string rather than a symbol.

Day 2: I Was Told There Would Be No Math

t:+`w`h`l!+^'"i"$'"x"\'0:"input/02.txt"

first select +/(2*w*l)+(3*w*h)+2*h*l from t
first select +/(w*h*l)+(2*w+h) from t

Another near 1:1 copy with some subtle changes:

  • "i"$ replaces "I"$ casting and does not fully descend (hence the ')
  • ^ replaces asc
  • first select replaces exec

Day 3: Perfectly Spherical Houses in a Vacuum

#?+\d:(0 1;0 -1;-1 0;1 0)"^v<>"?*0:"input/03.txt"
#?,/+\2^d

So far, so straightforward. 2^ (2 cut) replaces 0N 2#.

Day 4: The Ideal Stocking Stuffer

There is no built-in MD5 in shakti, so this uses the ffi interface, 2:.

md5:*"../rs/target/release/libaoc.so"2:`md5!1
/md5:*"../c/aoc.so"2:`md5!1

f:{[x;y;z] y<256/3#md5 (x,$z),0x0}[*0:"input/04.txt";;]

(f[16];1+)/:0
(f[0];1+)/:0

See Helper Code for the ffi function implementations.

Day 5: Doesn’t He Have Intern-Elves For This?

sw:{[x;y] (y-1)_(-y)#',\x}                        / sliding window

v:{[x] 2<#"aeiou"#x}                              / 3 vowels
d:{[x] |/=':x}                                    / doubles
b:{[x] ~#(2^"abcdpqxy")#sw[;2] x}                 / no bad chars

p:{[x] $[2=c:#w:&1<+/s~\/s:sw[;2] x;~1=-/|w;2<c]} / non-overlapping pair
r:{[x] |/x=x(!#x)-2}                              / repeat aBa

+/{[x] v[x]&d[x]&b x}'i:0:"input/05.txt"
+/{[x] p[x]&r x}'i

Using # to filter the list in both the vowel and bad character checks was a nice simplification.

Day 6: Probably a Fire Hazard

s:{[x] +"ii"$","\'x $[5=#x:" "\x;2 4;1 3]} / sanitise
r:{[x;y] x+!1+y-x}                         / range

f:{[x;y]
  $["n"=y 6
      .[x;;:;1]
    "f"=y 6
      .[x;;:;0]
      .[x;;:;1 0 x . c]] c:r .'s y}
g:{[x;y]
  $["n"=y 6
      .[x;;+;1]
    "f"=y 6
      .[x;;:;0|-1+x . c]
      .[x;;+;2]] c:r .'s y}

+/,/(1000^1000000#0)f/:i:0:"input/06.txt"
+/,/(1000^1000000#0)g/:i

I struggled with the parser during this challenge - unlike Q, shakti treats newlines as an implicit semicolon, therefore using both will lead to problems!

Other notes:

  • The use of /: to scan over y given x, which was just / in Q.
  • We can cast multiple values with "ii"$
  • The solution takes ~800ms to run in K vs ~250ms in Q, not sure why.

Day 7: Some Assembly Required

g:{[x] $["0123456789"'*x;. x;(!W)'`$x;W`$x;()]} / get
b:{[x] ((32-#b)#0),b:2\x}                       / binary

w:{[x;y] $[#(),v:g@x;W[`$y]:v;()]}              / wire
n:{[x;y] $[#(),v:g@x;W[`$y]:2/16 _1 0 b v;()]}  / not
a:{[x;y;z] $[2=#,/v:g'(x;y);W[`$z]:2/&/b'v;()]} / and
o:{[x;y;z] $[2=#,/v:g'(x;y);W[`$z]:2/|/b'v;()]} / or
l:{[x;y;z] $[2=#,/v:g'(x;y);W[`$z]:{[x;y] 2/16 _ b[x],y#0}. v;()]} / lshift
r:{[x;y;z] $[2=#,/v:g'(x;y);W[`$z]:{[x;y] 2/(-y) _ b x}. v;()]}    / rshift

f:{[x]
  $[(!W)'`$last x                               / already solved
      ()
    3=#x                                        / wire
      w . x 0 2
    4=#x                                        / not
      n . x 1 3
      ((a;o;l;r)"AOLR"?x[1;0]). x 0 2 4]}

{[x;y] f'x;W}[i:" "\'0:"input/07.txt"]/:W:`!0N;
W`a

i:@[i;&(,"b")~/last'i;:;,($W`a;"->";,"b")]
{[x;y] f'x;W}[i]/:W:`!0N;
W`a

Some challenges lend themselves to Q/K. Some do not. This is one that falls into the latter camp. There were also a number of teething problems when re-writing this in shakti:

Pain points here:

  • Cannot count an atom, hence #(),v
  • No 0b vs so have to pad 2\ to perform binary operations

Day 8: Matchsticks

d:{[x]
    *(0;" ";0){[x;y]           / (count;previous;skip)
    c:x 0
    $[s:x 2
        (c;y;s-1)
      "\\"=x 1                 / previous was an escape char
        (1+x 0;y;$[y="x";3;1])
        (1+x 0;y;0)]}/:x}

+/{[x] (2+#x)-d x}'i:0:"input/08.txt"
+/{[x] 2++/"\"\\"'x}'i

Lack of ssr meant I relied on my go approach for Part 1.

Also it’s interesting to note that if you place an int to the left of +/ it does not get treated as the starting value as it does in Q:

1+/1 2 3  / 1+ is applied to each-right
2 3 4
1++/1 2 3 / 1+ sum
7

Versus Q:

q)\
  1+/1 2 3 / 1 is treated as the start of the accumulator
7

Day 9: All in a Single Night

k:{[x](,/@\)/,\(,<<)'x=/x:!x} / from: https://groups.google.com/d/msg/shaktidb/OTsNFbAa7dI/sRmpNwYPAwAJ
sw:{[x;y] (y-1)_(-y)#',\x}    / sliding window

r:,/{[x] (`$,/'2#''x(0 2;2 0))!2#`i$x 4}'" "\'0:"input/09.txt" / routes
c:{[x] x@k@#x}@`$?2^,/$!r                                      / combos

min t:{[x] +/r`$,/'$sw[x;2]}'c
max t

Tables don’t appear to be ready yet. Names can only be 8 characters long… So not my best work.

Day 10: Elves Look, Elves Say

#@[;40] r:(50;{[x] ,/(1_-':i,#x),'x@i:&~~':x})\:.'*0:"input/10.txt"
#last r

A simple translation. To iterate over a function n times, the syntax is (n;function)\: - thanks to chrispsn for the tip!

Day 11: Corporate Policy

a:"abcdefghijklmnopqrstuvwxyz"

n:{[x]                              / generate next pass
  $[0=#x
      "a"
    "z">last x
      (-1_ x),a@1+a?last x
      (n@-1_x),"a"]}

s:{[x] (1_-':&1=-':a?x)'1}          / straight
b:{[x] ~|/"iol"'x}                  / no bad chars
p:{[x] 1<+/{[x] (1_-':&x)'1}'a=\x}  / pairs

c:{[x]
  $[~s x                            / no straight
      n x
    ~b x                            / bad chars
      n x
    ~p x                            / no pairs
      n x
    x]}

r:c/:*0:"input/11.txt";r
c/:n r

I was unable to do n:{[x] a@26\1+26/a?x} as this overflowed the integer type.

 26/21 25 1 23 10 6 7 1    / overflows K
319820783

q)\
  26/:21 25 1 23 10 6 7 1  / fine in Q
176413479919

The rest is pretty straightforward.

Day 12: JSAbacusFramework.io

f:{[x;y]
  $[`A`L'@x                          / table or list
      ,/f[;y]'x
    `NA`NI`NL'@x                     / nested something
      $[y&|/"red"~/x[];0;,/f[;y]'x[]]
    `I`i'@x                          / int list or atom
      x
      0]}

+/f[;0b] j:`json?*0:"input/12.txt"
+/f[j;1b]

Took a bit of time to work out the various types. Haven’t figured out the equivalent of 0N! either so this was very trial-and error.

Day 13: Knights of the Dinner Table

sw:{[x;y] x@(-y-1)_(!#x)+\!y} / sliding window

c:{[x] (,/'(-1+#x;{[x;y] y,/'(y:,/(),y)_\x }[x;])/:first x),'first x} / combos
f:{[x;y] +/y`$s,|'s:,/'$sw[x;2]} / solve

max f[;h]'c p:`$,'?,/$!h:,/{[x] (`$,/1#'x 0 10)!$["l"=*x 2;-;:] `i$x 3}'" "\'0:"input/13.txt"
max f[;h]'c p,`m

Fairly simple translation, but uses a dictionary for the lookup rather than a table.

Day 14: Reindeer Olympics

o:++\'{[x] {[x;y;z] 2503#(y#x),z#0}."i"$x 3 6 13}'" "\'0:"input/14.txt"

max last o
|/+/o=|/+o

I was having an absolute mare with the select. My Q solution is pretty straightforward, so I dropped the tables and went back to basics…

This was as far as I got with the tables (first star):

o:+`n`s`t`r!+{[x] "niii"$x 0 3 6 13}'" "\'0:"input/14.txt"

{[x] |/*+x[]} select (|/+\2503#,/(t#'s),r#'0) by n from o

Day 15: Science for Hungry People

c:{[x;y;z] $[z=#x;,x,y;,/c[;;z]'[x,/t;y-t:!1+y]]} / combo generator

i:{[x] "i"$@[;2 4 6 8 10]@" "\",:"_x}'0:"input/15.txt"
s:{[x;y] (t*500=a 4;t:*/0|4#a:+/x*y)}[i;]'c[!0;100;3]

max s[;1]
max s[;0]

I gave up with using tables for this one too and went back to basics. I had real difficulty trying to index into a keyed table as I had done with my Q solution to this challenge.

Day 16: Aunt Sue

s:{[x] (!)."ni"$+2^@[" "\",:"_ x;2 3 4 5 6 7]}'0:"input/16.txt"
t:(+`sue!1+!#s),'{[x;y] (x!(#x)#-1),y }[c:?,/!'s;]'s

first first select sue from select from (select from (select from (select from (select from (select from (select from (select from (select from (select from t where children in -1 3) where cats in -1 7) where samoyeds in -1 2) where pomeranians in -1 3) where akitas in -1 0) where vizslas in -1 0) where goldfish in -1 5) where trees in -1 3) where cars in -1 2) where perfumes in -1 1
first first select sue from select from (select from (select from (select from (select from (select from (select from (select from (select from (select from t where children in -1 3) where (cats > 7)|cats=-1) where samoyeds in -1 2) where (pomeranians <3)|pomeranians=-1) where akitas in -1 0) where vizslas in -1 0) where (goldfish < 5)|goldfish = -1) where (trees>3)|trees=-1) where cars in -1 2) where perfumes in -1 1

Day 16 presented a number of challenges, but I stuck with tables:

  • Using a dictionary of defaults gets overwritten even when using ', e.g.
 d:{a:9;b:8;c:7} / define defaults
 d
`a|9
`b|8
`c|7

 {[x;y] x,y}[d;]'({a:5};{b:3};{c:1}) / fill with defaults
a b c
- - -
5 3 1
5 3 1
5 3 1

 d / d is overwritten!
`a|5
`b|3
`c|1

The workaround seems to be to define the defaults inside the lambda:

 {[x] {a:1;b:2;c:3},x}'({a:5};{b:3};{c:1})
a b c
- - -
5 2 3
1 3 3
1 2 1
  • Cannot use multiple clauses in the where statement.
 select from t where goldfish < 5, trees < 3
select from t where goldfish < 5, trees < 3
                                 ^
!value

This results in the monstrosity that is the nested select from .. statements.

  • Nulls are different to Q, and you can’t use them with in hence default value being -1.

Other than that… it wasn’t too bad.

Day 17: No Such Thing as Too Much

a:"abcdefghijklmnopqrstuvwxyz"

d:{[x;y] x y_#x} / drop

b:{[x] (`$'a@!#x)!x}@|^`i$0:"input/17.txt"
f:{[x;y;z] $[x=s:+/b@y;,y;,/f[x;;]'[y,/z w;z d/w:&(z>last y)&~x<b[z]+s]]}

#r:f[150;`;!b]
-1+&/#'r

Relatively straightforward, a few pain points:

  • Could not do f'[a;l;l], had to do f[a;;]'[l;l]
  • x_/:y is gone (atom drop), equivalent functionality is x{[x;y] x y_#x}/y
  • Cannot index into char dict with "", () turns into "", !0 cannot be indexed into etc
  • >= does not exist hence ~x<y

Day 18: Like a GIF For Your Yard

g:"#"=0:"input/18.txt" / grid
c:{[x] ,/x,\/x}@!#g    / coords

f:{[x;y] s:+/(`b=@)#.[x;]'y+/(0 1;1 1;1 0;1 -1;0 -1;-1 -1;-1 0;-1 1)
    $[.[x;y]
      2 3's
      3=s]}
s:{[x] .[x;2#,0,-1+#x;:;1b]} / set corners

+/,/(100;{[x]  (#x)^f[x]'c})/:g
+/,/(100;{[x] s(#x)^f[x]'c})/:s g

Only notable pain point here is the `!length error when indexing outside the grid, hence (`b=@)# to filter only boolean types.

Day 19: Medicine for Rudolph

sw:{[x;y] (y-1)_(-y)#',\x}
ss:{[x;y] $[1=#(),y;&x=*y;*{[x;y] $[0=#q:*|x;x;0=#p:*x;(1#q;1_q);(y+*|p)>*q;(p;1_q);(p,*q;1_q)]}[;#y]/:(();&y~/sw[x;#y])]}
ssr:{[x;y;z] ,/@[x;i;:;(#i:1+2*!_.5*#x:(,/0,(0,#y,"")+/ss[x;y])^x)#,z]}

r:{[x] (" "\x)0 2}'-2_i:0:"input/19.txt"
s:{[x] `$(&"ABCDEFGHIJKLMNOPQRSTUVWXYZ"'x)^x}@m:*|i

#?,/'$,/{[x;y;z] @[x;;:;z]'&x=y}[s].'`$/r

r:|'r@>#'r[;1] / arrange largest transformations first

f:{[x] / (molecule;transformations)
  m:*p:*x
  t:*|p
  $[m~1#"e" / reached the end!
    ,p
    (+(ssr[m;;].'r@w;t+c w:&0<c:#'ss[m;]'*'r)),1_x]}

last first f/:(,(m;0))

Part 1 was straightforward. I wrote ss (after a glimpse of John’s example) to help with Part 2 (the ssr is a simplified version from Q).

I had started with a recursive approach for Part 2 but haven’t worked out a way to stop recursing without using a global variable as a flag, so stuck with iteration.

Recursive approach with global variable:

f2:{[x;y;z] $[D[`FOUND]|x~1#"e";(D[`FOUND]:1b;z);,/f2[;y;]'[ssr[x;;].'y@w;z+c w:&0<c:#'ss[x;]'*'y]]}
D:{FOUND:0b}; *1_f2[m;r;0] / how to set a global within a function?

I came back to this later and wrote ss in both C and Rust. It is significantly (100x) faster than my K implementation.

// continuation of aoc.rs ...

#[no_mangle]
pub extern "C" fn ss(h: *const c_char, n: *const c_char) -> *mut u32 {
    let p;
    unsafe {
        let hs = CStr::from_ptr(h).to_str().expect("fail");
        let ns = CStr::from_ptr(n).to_str().expect("fail");
        let v: Vec<_> = hs.match_indices(ns).collect();
        p = k('I' as c_char, v.len() as u64);
        for i in 0..v.len() {
            ptr::write(p.add(i), v[i].0 as u32);
        }
    }
    return p;
}

Timing comparison:

\t:1000 ss[m]'r[;0] / this is the k code
2788

\t:1000 ss[m]'r[;0] / this is the c code
25

Day 20: Infinite Elves and Infinite Houses

d:{[x] ?`i f,x%(f:r@&d=_d:x%r:1+!_sqrt x)} / distinct factors
f:{[x;y] +/r@&~(x%y)>r:d x}

({[x;y] x>+/d y}[.1*i:`i$*0:"input/20.txt"];1+)/:0
({[x;y;z] x>f[z;y]}[i%11;50];1+)/:0

Found out that mod and div do not support y being a list:

1 2 3 4 5 mod 20
^
!type

…which made the initial solution incredibly slow as I had to do div'x and mod'x… So rewrote to use % and the solution runs almost twice as fast as the original Q version:

$ time k 20.k </dev/null
831600
884520

real	0m6.352s
user	0m6.347s
sys	0m0.004s

$ time q 20.q </dev/null
2020.07.03D08:13:16.207996000	LDN	:/home/mark/github/perch/code/kdb/lib/q/q.q

831600
884520

real	0m10.561s
user	0m10.549s
sys	0m0.011s

Day 21: RPG Simulator 20XX

W:`Dagger`Shortsword`Warhammer`Longsword`Greataxe
A:`Leather`Chainmail`Splintmail`Bandedmail`Platemail`NoArmour
R:`Damage1`Damage2`Damage3`Armor1`Armor2`Armor3`NoRing`NoRing2
I:+`Name`Cost`Damage`Armor!(W,A,R;8 10 25 40 74,13 31 53 75 102 0,25 50 100 20 40 80 0 0;4 5 6 7 8, 0 0 0 0 0 0, 1 2 3 0 0 0 0 0;0 0 0 0 0, 1 2 3 4 5 0, 0 0 0 1 2 3 0 0)

Rc:R@?^'{[x] ,/{[x;y] y,'((),y)_x}[x;]'x}@!#R / unique ring combinations

cross:{[x;y] ,/x,\/y}
c:cross[cross[W;A];Rc] / all item combinations

A:()!()
ct:{[x]
  A[`L]:x / global var hack
  s:select from I where Name in A[`L]
  select sum Cost, sum Armor, sum Damage from s}'c
ct:ct@<*+`Cost#ct / sort by ascending cost

e:"i"$last'" "\'0:"input/21.txt" / enemy (hp;damage;armor)

t:{[x] d:last x;$[|/0>*'x;x;(d-(1|a[1]-d 2;0;0);a:*x)]} / take turn
g:{[x;y] ~2 mod#t\:(x;y)}[;e]'100,'.'`Damage`Armor#ct / player has 100 hp

*`Cost#ct@*&g     / cheapest win
*`Cost#ct@last&~g / priciest loss

That is a huge amount of setup. Two issues I faced with tables:

  1. Cannot reference local variables in select statement, hence assignment to global A dict)
  2. Could not do select sum Cost .. with where clause, hence assignment to s: and selecting sums from that.
 select sum Cost from I where Name in `Dagger`Leather`Armor1`Armor2
select sum Cost from I where Name in `Dagger`Leather`Armor1`Armor2
^
!class
/ two-step works
s:select from I where Name in `Dagger`Leather`Armor1`Armor2
select sum Cost from s
{Cost:81}

Day 22: Wizard Simulator 20XX

Spells:+`Name`Cost`Turns`Damage`Heal`Mana!(`MagicMissile`Drain`Shield`Poison`Recharge
    53 73 113 173 229 / Cost
    1 1 6 6 5         / Turns
    4 2 0 3 0         / Damage
    0 2 0 0 0         / Heal
    0 0 0 0 101)      / Mana Boost

D:()!() // hack

turn:{[P;E;A;S;H]
  / hard mode
  $[H;P[`hp]-:1;()]
  d:1>P[`hp]

  / === PLAYER TURN ===
  / cast new spell
  D[`spell]:S
  A,:s:select from Spells where Name=D`spell
  P[`mana]-:first first select Cost from s
  / apply active spells
  a:select from A where Turns > 0
  E[`hp]-:first select sum Damage from a
  P[`hp]+:first select sum Heal from a
  P[`mana]+:first select sum Mana from a
  / reduce turns
  A:select Name, Cost, Turns-1, Damage, Heal, Mana from A

  / === ENEMY TURN ===
  / apply active spells
  a:select from A where Turns > 0
  E[`hp]-:first select sum Damage from a
  P[`mana]+:first select sum Mana from a
  / active shield?
  P[`hp]-:1|E[`damage] - $[#select from a where Name=`Shield;7;0]
  / reduce turns
  A:select Name, Cost, Turns-1, Damage, Heal, Mana from A
  / active spells
  D[`active]:*+`Name#select from A where Turns > 0
  D[`mana]:P[`mana]
  / available spells
  as:select from Spells where ~Name in D`active
  as:*+`Name#select from as where ~Cost > D`mana

  / return; 1=victory, 0=loss, -1=continue
  $[(~d)&1>E`hp
      (1;*+select sum Cost from A)
    d|1>P`hp
      (0;0)
      (-1;(P;E;A),/as)]}

f:{[x;y]
  b:first x  / best cost
  q:last x   / remaining queue
  i:first q  / pop
  a:i 2      / active spells

  $[0=#q
      (b;q)
    b<*+select sum Cost from a
      (b;1 _ q)
    1=first res:turn . i,y
      (b&res 1;1 _ q)
    0=first res
      (b;1 _ q)
      (b;res[1],1 _ q)]}

e:`hp`damage!"i"$last'" "\'0:"input/22.txt"

first f[;0b]/:(0w;s:({hp:50;mana:500};e;0#Spells),/*+`Name#Spells)
first f[;1b]/:(0w;s)

Diabolical. Definitely my least favourite challenge of AoC 2015. So much code and most of it terrible. I have already rewritten it once.

K9-wise the pain points are mostly around tables:

  • Cannot do update .. from
  • Cannot do select x .. where ..
  • Cannot reference local variable in where clause

Other pain points:

  • No if statement, so weird $[cond;true;no-op]
  • No early-return from functions
  • No [] in condition, leading to funky statements

Day 23: Opening the Turing Lock

f:{[x;y]
  a:y 0;b:y 1;i:x pc:y 2;
  $["hlf"~i 0
      $["a"=*i 1;.5 1 1;1 .5 1]*(a;b;pc+1)
    "tpl"~i 0
      $["a"=*i 1;3 1 1;1 3 1]*(a;b;pc+1)
    "inc"~i 0
      $["a"=*i 1;1 0 1;0 1 1]+(a;b;pc)
    "jmp"~i 0
      (a;b;pc+`i$i 1)
    "jie"~i 0
      (a;b;pc+$[0=2 mod _y@"ab"?*i 1;`i$i 2;1])
    "jio"~i 0
      (a;b;pc+$[1=y@"ab"?*i 1;`i$i 2;1])
      (a;b;pc)]}

@[;1]@ f[p:{[x] " "\",+"_x}'0:"input/23.txt"]/:0 0 0
@[;1]@ f[p]/:1 0 0

Fairly straightforward translation here, couple of pain points:

  • ,0b is treated as true rather than 'type which threw me slightly when I was debugging.
  • `i$"+123" comes out as 0 rather than 123, not a huge deal but different to Q.

Day 24: It Hangs in the Balance

d:{[x;y] x y_#x} / drop

f:{[x;y;z]
    $[x=s:+/y
      ,y
      ,/f[x;;]'[y,/z w;z d/w:&(z<*|y)&~x<z+s]]}
m:{[x] &/*/'x@&c=&/c:#'x}

m f[3 div +/i;1#i;] 1_i:|^`i$0:"input/24.txt"
m f[4 div +/i;1#i;] 1_i

Near carbon copy, similar to Day 17 with the f[]'[].

Day 25: Let It Snow

i:{[x] `i$(" "\",."_x) 16 18}@*0:"input/25.txt"
n:{[x;y;z] x++/y+1+!z-1}[1++/!*i;;] . i

(n-1;{[x] 33554393 mod 252533*x})/:20151125

Nothing special here, uses the new iteration format, otherwise just a copy of Q.

Solution Comparison

for f in *.q; do /usr/bin/time --format "%C,%e,%M" q $f -s 4 </dev/null >/dev/null; done
for f in *.k; do /usr/bin/time --format "%C,%e,%M" k $f      </dev/null >/dev/null; done

This is not by any means a scientific comparison; no comparisons should be drawn:

  • The solutions are different!
  • 4 threads were available to Q

It’s purely for me to determine whether I should look for a more efficient algorithm to solve the challenge.

Helper Code

aoc.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <openssl/md5.h>

#include "k.h"

// gcc aoc.c -shared -lcrypto -fPIC -o aoc.so -std=c99

// md5C:*"../src/aoc.so"2:`md5!1
// md5:{[x] `c md5C x,0x0}
K md5(const S x)
{
  unsigned char digest[16];
  MD5((const unsigned char*)x, strlen(x), digest);
  K d = k((S)'I', 16);
  for (U i = 0; i < 16; i++)
  {
    ((I*)d)[i] = digest[i];
  }
  return d;
}

//ssC:*"../src/aoc.so"2:`ss!2
//ss:{[x;y] (),ssC[x,0x0;y,0x0]}
K ss(const S x, const S y)
{
  I lx = strlen(x);
  I ly = strlen(y);

  if (lx == 0 || ly == 0)
  {
    return k((S)'I', 0);
  }

  const char *ptr = x;
  const char *p_idx = ptr;

  I *tmp = (I*) malloc((lx / ly) * sizeof(I));
  I fnd = 0;

  while ((ptr = strstr(ptr, y)) != NULL)
  {
    ((I*)tmp)[fnd++] = ptr - p_idx;
    ptr = ptr + ly * sizeof(char);
  }
  K r = k((S)'I', fnd);
  for (int i = 0; i < fnd; i++)
  {
    ((I*)r)[i] = tmp[i];
  }
  free(tmp);

  return r;
}

aoc.rs

use std::ffi::CStr;
use std::os::raw::c_char;
use std::ptr;

extern "C" {
    fn k(t: c_char, l: u64) -> *mut u32;
}

// md5C:*"../rs/target/libaoc.so"2:`md5!1
// md5:{[x] `c md5C x,0x0}
#[no_mangle]
pub extern "C" fn md5(s: *const c_char) -> *mut u32 {
    let p;
    unsafe {
        let digest = md5::compute(CStr::from_ptr(s).to_bytes());
        p = k('I' as c_char, 16);
        for i in 0..16 {
            ptr::write(p.add(i), digest[i].into())
        }
    }
    return p
}

// ssC:*"../rs/target/release/libaoc.so"2:`ss!2
// ss:{[x;y] (),ssC[x,0x0;y,0x0]}
#[no_mangle]
pub extern "C" fn ss(h: *const c_char, n: *const c_char) -> *mut u32 {
    let p;
    unsafe {
        let hs = CStr::from_ptr(h).to_str().expect("fail");
        let ns = CStr::from_ptr(n).to_str().expect("fail");
        let v: Vec<_> = hs.match_indices(ns).collect();
        p = k('I' as c_char, v.len() as u64);
        for i in 0..v.len() {
            ptr::write(p.add(i), v[i].0 as u32)
        }
    }
    return p
}