diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..068d5c4e813512312ae8f3f1892d82d04172a970 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/zig-* +/deps.zig +/.zigmod diff --git a/build.zig b/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..2c49a791732be2224d28af6394bd11f7285958eb --- /dev/null +++ b/build.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const deps = @import("./deps.zig"); + +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("zig-leven", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + deps.addAllTo(exe); + exe.install(); + + const run_cmd = exe.run(); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); +} diff --git a/src/lib.zig b/src/lib.zig new file mode 100644 index 0000000000000000000000000000000000000000..e45925436de7d27dfa14f0c65c8b19f1c9b6d3fa --- /dev/null +++ b/src/lib.zig @@ -0,0 +1,85 @@ +const std = @import("std"); +const range = @import("range").range; + +// // the naïve recursive implementation +// pub fn leven(comptime T: type, a: []const T, b: []const T) usize { +// if (b.len == 0) return a.len; +// if (a.len == 0) return b.len; + +// if (a[0] == b[0]) return leven(T, a[1..], b[1..]); + +// return 1 + std.math.min3( +// leven(T, a[1..], b), +// leven(T, a, b[1..]), +// leven(T, a[1..], b[1..]), +// ); +// } + +pub fn leven(comptime T: type, alloc: *std.mem.Allocator, a: []const T, b: []const T) !usize { + if (std.mem.eql(T, a, b)) return 0; + + var left = a; + var right = b; + + if (left.len > right.len) { + left = b; + right = a; + } + + var ll = left.len; + var rl = right.len; + + { + const sl = suffixLen(T, a, b); + ll -= sl; + rl -= sl; + } + + const start = prefixLen(T, a, b); + ll -= start; + rl -= start; + + if (ll == 0) return rl; + + var result: usize = 0; + + const charCodeCache = try alloc.alloc(T, ll); + defer alloc.free(charCodeCache); + + const array = try alloc.alloc(usize, ll); + defer alloc.free(array); + + for (range(ll)) |_, i| { + charCodeCache[i] = left[start + i]; + array[i] = i + 1; + } + + for (range(rl)) |_, j| { + const bCharCode = right[start + j]; + var temp = j; + result = j + 1; + + for (range(ll)) |_, i| { + const temp2 = if (bCharCode == charCodeCache[i]) temp else temp + 1; + temp = array[i]; + array[i] = if (temp > result) (if (temp2 > result) result + 1 else temp2) else (if (temp2 > temp) temp + 1 else temp2); + result = array[i]; + } + } + + return result; +} + +fn prefixLen(comptime T: type, a: []const T, b: []const T) usize { + if (a.len == 0 or b.len == 0) return 0; + var i: usize = 0; + while (a[i] == b[i]) : (i += 1) {} + return i; +} + +fn suffixLen(comptime T: type, a: []const T, b: []const T) usize { + if (a.len == 0 or b.len == 0) return 0; + var i: usize = 0; + while (a[a.len - 1 - i] == b[b.len - 1 - i]) : (i += 1) {} + return i; +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..536bcb3e5c2b9af15b120f4e9beb6f6c21376032 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const leven = @import("leven").leven; + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer _ = gpa.deinit(); + const alloc = &gpa.allocator; + + try std.testing.expectEqual(@as(usize, 1), try leven(u8, alloc, "a", "b")); + try std.testing.expectEqual(@as(usize, 1), try leven(u8, alloc, "ab", "ac")); + try std.testing.expectEqual(@as(usize, 1), try leven(u8, alloc, "ac", "bc")); + try std.testing.expectEqual(@as(usize, 1), try leven(u8, alloc, "abc", "axc")); + try std.testing.expectEqual(@as(usize, 3), try leven(u8, alloc, "kitten", "sitting")); + try std.testing.expectEqual(@as(usize, 6), try leven(u8, alloc, "xabxcdxxefxgx", "1ab2cd34ef5g6")); + try std.testing.expectEqual(@as(usize, 2), try leven(u8, alloc, "cat", "cow")); + try std.testing.expectEqual(@as(usize, 6), try leven(u8, alloc, "xabxcdxxefxgx", "abcdefg")); + try std.testing.expectEqual(@as(usize, 7), try leven(u8, alloc, "javawasneat", "scalaisgreat")); + try std.testing.expectEqual(@as(usize, 3), try leven(u8, alloc, "example", "samples")); + try std.testing.expectEqual(@as(usize, 6), try leven(u8, alloc, "sturgeon", "urgently")); + try std.testing.expectEqual(@as(usize, 6), try leven(u8, alloc, "levenshtein", "frankenstein")); + try std.testing.expectEqual(@as(usize, 5), try leven(u8, alloc, "distance", "difference")); + try std.testing.expectEqual(@as(usize, 2), try leven(u8, alloc, "因為我是中國人所以我會說中文", "因為我是英國人所以我會說英文")); +} diff --git a/zig.mod b/zig.mod new file mode 100644 index 0000000000000000000000000000000000000000..dfcb46cdfcce6d9e9133f9c5424cf89514976d70 --- /dev/null +++ b/zig.mod @@ -0,0 +1,7 @@ +id: 96h80ezrvj7iqti7oar3lj30v0tg4cjii7yggcr0m9dhynfu +name: leven +main: src/lib.zig +license: MIT +description: Measure the difference between two slices using the Levenshtein distance algorithm +dependencies: + - src: git https://github.com/nektro/zig-range diff --git a/zigmod.lock b/zigmod.lock new file mode 100644 index 0000000000000000000000000000000000000000..4abcde2af0172615d652f2b48a13934c6821abf0 --- /dev/null +++ b/zigmod.lock @@ -0,0 +1,2 @@ +2 +git https://github.com/nektro/zig-range commit-890ca308fe09b3d5c866d5cfb3b3d7a95dbf939f diff --git a/zigmod.sum b/zigmod.sum new file mode 100644 index 0000000000000000000000000000000000000000..bdf9bd96a631d0f7e218d0fe63dcc7b18a49ea3c --- /dev/null +++ b/zigmod.sum @@ -0,0 +1 @@ +blake3-09698753782139ab4877d08f33235170836f68b73e482b65cdee5637a6addf86 git/github.com/nektro/zig-range