Skip to content

Commit 67f3c48

Browse files
author
rw
committed
first commit
0 parents  commit 67f3c48

File tree

5 files changed

+179
-0
lines changed

5 files changed

+179
-0
lines changed

.envrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
use nix

.github/workflows/test.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: "Test"
2+
on:
3+
pull_request:
4+
push:
5+
jobs:
6+
tests:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v3
10+
- uses: cachix/install-nix-action@v22
11+
with:
12+
nix_path: nixpkgs=channel:nixos-unstable
13+
- run: nix develop -c 'nix-instantiate --eval leetcode.nix | jq -r "fromjson | ."'

leetcode.nix

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# nix-instantiate --eval leetcode.nix | jq -r 'fromjson | .'
2+
3+
let
4+
fac = self: acc: n: if n == 0 then acc else self (n * acc) (n - 1);
5+
pow2 = n: builtins.bitAnd n (n - 1) == 0;
6+
remainder = a: b:
7+
if a < b then a
8+
else remainder (a - b) b;
9+
sumDivisors = n: builtins.foldl'
10+
(runningsum: current:
11+
if remainder n current == 0
12+
then current + runningsum
13+
else runningsum)
14+
0
15+
(if n == 1 then [] else
16+
builtins.genList (x: x+1) (n - 2));
17+
# https://leetcode.com/problems/perfect-number/
18+
perfectNumber = n:
19+
if n < 6 then false
20+
else sumDivisors n == n;
21+
fib = n:
22+
if n == 0 then n else
23+
if n == 1 then n else
24+
fib(n - 1)+fib(n - 2);
25+
fibTCO = self: acc: n:
26+
if n == 0 then acc else
27+
if n == 1 then acc else
28+
self (acc + n) (n - 1);
29+
tco = import ./tco.nix;
30+
facTCO = n: tco fac 1 n;
31+
ftco = n: tco fibTCO 1 n;
32+
# https://leetcode.com/problems/take-gifts-from-the-richest-pile/
33+
sqrt = number: iterations:
34+
let
35+
nextGuess = guess: (guess + number / guess) / 2;
36+
improve = guess:
37+
if iterations == 0 then guess
38+
else improve (nextGuess guess) (iterations - 1);
39+
in improve 1 iterations;
40+
41+
# pickGifts = gifts: k:
42+
in
43+
# using builtins.toJSON to eagerly evaluate the tests
44+
# jq then pretty prints the result
45+
builtins.toJSON {
46+
tests =[
47+
(assert remainder 10 3 == 1; "remainder works")
48+
(assert perfectNumber 6; "six is perfect")
49+
(assert ! perfectNumber 7; "seven is not perfect")
50+
(assert pow2 33554432; "2^25 is a power of 2")
51+
(assert fib 1 == 1; "fib 1 is 1")
52+
(assert fib 2 == 1; "fib 2 is 1")
53+
(assert fib 3 == 2; "fib 3 is 2")
54+
(assert fib 4 == 3; "fib 4 is 3")
55+
(assert fib 5 == 5; "fib 5 is 5")
56+
(assert fib 6 == 8; "fib 6 is 8")
57+
(assert facTCO 15 == 1307674368000; "tail call optimized factorial works")
58+
(assert ftco 100 == 5050; "tail call optimized fibonacci works")
59+
];
60+
}

shell.nix

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{ pkgs ? import <nixpkgs> {} }:
2+
pkgs.mkShell {
3+
packages = [
4+
pkgs.git
5+
pkgs.nixfmt
6+
pkgs.jq
7+
];
8+
}

tco.nix

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# https://discourse.nixos.org/t/tail-call-optimization-in-nix-today/17763
2+
f:
3+
let
4+
lib = import <nixpkgs/lib>;
5+
# we'll use a fixed point and tail recursion
6+
fac = self: acc: n: if n == 0 then acc else self (n * acc) (n - 1);
7+
apply = f: args: builtins.foldl' (f: x: f x) f args;
8+
9+
unapply =
10+
let
11+
unapply' = acc: n: f: x:
12+
if n == 1
13+
then f (acc ++ [ x ])
14+
else unapply' (acc ++ [ x ]) (n - 1) f;
15+
in
16+
unapply' [ ];
17+
argCount = f:
18+
let
19+
# N.B. since we are only interested if the result of calling is a function
20+
# as opposed to a normal value or evaluation failure, we never need to
21+
# check success, as value will be false (i.e. not a function) in the
22+
# failure case.
23+
called = builtins.tryEval (
24+
f (builtins.throw "You should never see this error message")
25+
);
26+
in
27+
if !(builtins.isFunction f || builtins.isFunction (f.__functor or null))
28+
then 0
29+
else 1 + argCount called.value;
30+
tailCallOpt = f:
31+
let
32+
argc = argCount (lib.fix f);
33+
34+
# This function simulates being f for f's self reference. Instead of
35+
# recursing, it will just return the arguments received as a specially
36+
# tagged set, so the recursion step can be performed later.
37+
fakef = unapply argc (args: {
38+
__tailCall = true;
39+
inherit args;
40+
});
41+
# Pass fakef to f so that it'll be called instead of recursing, ensuring
42+
# only one recursion step is performed at a time.
43+
encodedf = f fakef;
44+
45+
# This is the main function, implementing the “optimized” recursion
46+
opt = args:
47+
let
48+
steps = builtins.genericClosure {
49+
# This is how we encode a (tail) call: A set with final == false
50+
# and the list of arguments to pass to be found in args.
51+
startSet = [
52+
{
53+
key = "0";
54+
id = 0;
55+
final = false;
56+
inherit args;
57+
}
58+
];
59+
60+
operator =
61+
{ id, final, ... }@state:
62+
let
63+
# Generate a new, unique key to make genericClosure happy
64+
newIds = {
65+
key = toString (id + 1);
66+
id = id + 1;
67+
};
68+
69+
# Perform recursion step
70+
call = apply encodedf state.args;
71+
72+
# If call encodes a new call, return the new encoded call,
73+
# otherwise signal that we're done.
74+
newState =
75+
if builtins.isAttrs call && call.__tailCall or false
76+
then newIds // {
77+
final = false;
78+
inherit (call) args;
79+
} else newIds // {
80+
final = true;
81+
value = call;
82+
};
83+
in
84+
85+
if final
86+
then [ ] # end condition for genericClosure
87+
else [ newState ];
88+
};
89+
in
90+
# The returned list contains intermediate steps we need to ignore
91+
(builtins.head (builtins.filter (x: x.final) steps)).value;
92+
in
93+
# make it look like a normal function again
94+
unapply argc opt;
95+
in
96+
tailCallOpt f
97+
# => 1307674368000

0 commit comments

Comments
 (0)