diff --git a/src/shellpop.zig b/src/shellpop.zig index a3cebfb..3b9e97c 100644 --- a/src/shellpop.zig +++ b/src/shellpop.zig @@ -17,11 +17,10 @@ const ShellIndex = struct { // MaxShells is the maximum number of "popular" shells. const MaxShells = 63; +const MaxShellLen = 64; // ShellPopcon is a shell popularity contest: collect shells and return the // popular ones, sorted by score. score := len(shell) * number_of_shells. -// String values are copied, the returned slice of shells is allocated -// using an allocator. const ShellPopcon = struct { counts: std.StringHashMap(u32), allocator: Allocator, @@ -32,20 +31,50 @@ const ShellPopcon = struct { }; const ShellSections = struct { - index: []ShellIndex, - blob: []const u8, + index: BoundedArray(ShellIndex, MaxShells), + blob: BoundedArray(u8, MaxShells * MaxShellLen), offsets: StringHashMap(u10), - pub fn getOffset(self: *ShellSections, shell: []const u8) ?u10 { - return self.offsets.get(shell); + // initializes and populates shell sections. All strings are copied, nothing is owned. + pub fn init(allocator: Allocator, shells: BoundedArray([]const u8, MaxShells)) !ShellSections { + var self = ShellSections{ + .index = try BoundedArray(ShellIndex, MaxShells).init(shells.len), + .blob = try BoundedArray(u8, MaxShells * MaxShellLen).init(0), + .offsets = StringHashMap(u10).init(allocator), + }; + var offset: u10 = 0; + var idx: u10 = 0; + while (idx < shells.len) { + //const stderr = std.io.getStdErr().writer(); + //try stderr.print("\n", .{}); + + const len = @intCast(u6, shells.get(idx).len); + try self.blob.appendSlice(shells.get(idx)); + const ourShell = self.blob.constSlice()[offset .. offset + len]; + try self.offsets.put(ourShell, idx); + self.index.set(idx, ShellIndex{ + .offset = offset >> 2, // all shells are padded by 4 + .len = len, + }); + offset += len; + + // if offset is not multiple by 4, make it so, and append the offset. + const padding = (offset + 3) & ~@intCast(u10, 3); + offset += padding; + try self.blob.appendNTimes(0, padding); + idx += 1; + } + return self; } - // initializes ShellSections. All strings are copied, nothing is owned. - pub fn init(allocator: Allocator, shells: BoundedArray([]const u8, MaxShells)) ShellSections { - self.offsets = StringHashMap(u10).init(allocator); - _ = allocator; - _ = shells; + pub fn deinit(self: *ShellSections) void { + self.offsets.deinit(); + self.* = undefined; + } + + pub fn getOffset(self: *ShellSections, shell: []const u8) ?u10 { + return self.offsets.get(shell); } }; @@ -82,10 +111,10 @@ const ShellPopcon = struct { return std.math.order(a.score, b.score); } - pub fn getSections(self: *Self, limit: u32) ShellSections { - const stderr = std.io.getStdErr().writer(); - _ = stderr; - + // toOwnedSections returns the analyzed ShellSections. Resets the shell + // popularity contest. ShellSections memory is allocated by the ShellPopcon + // allocator, and must be deInit'ed by the caller. + pub fn toOwnedSections(self: *Self, limit: u10) !ShellSections { var deque = PriorityDequeue(KV, void, cmpShells).init(self.allocator, {}); defer deque.deinit(); @@ -99,55 +128,45 @@ const ShellPopcon = struct { } const total = std.math.min(deque.count(), limit); - var strSlice = self.allocator.alloc([]u8, total); - defer strSlice.deinit(); + var topShells = try BoundedArray([]const u8, MaxShells).init(total); var i: u32 = 0; while (i < total) { - strSlice[i] = deque.removeMax(); + const elem: []const u8 = deque.removeMax().shell; + topShells.set(i, elem); i += 1; } - return ShellSections.init(self.allocator, strSlice); + const result = ShellSections.init(self.allocator, topShells); + const allocator = self.allocator; + self.* = init(allocator); + return result; } }; -test "[]u8 comparison" { - var s1: []const u8 = "/bin/bash"; - var s2: []const u8 = "/bin/bash"; - try testing.expectEqual(s1, s2); -} - -test "basic shellpop" { +test "basic shellpopcon" { var popcon = ShellPopcon.init(testing.allocator); defer popcon.deinit(); - try popcon.put("/bin/bash"); - try popcon.put("/bin/bash"); - try popcon.put("/bin/bash"); - try popcon.put("/bin/zsh"); - try popcon.put("/bin/zsh"); - try popcon.put("/bin/zsh"); - try popcon.put("/bin/zsh"); - try popcon.put("/bin/nobody"); - try popcon.put("/bin/very-long-shell-name-ought-to-be-first"); - try popcon.put("/bin/very-long-shell-name-ought-to-be-first"); + const bash = "/bin/bash"; // 9 chars + const zsh = "/bin/zsh"; // 8 chars + const nobody = "/bin/nobody"; // only 1 instance, ought to ignore + const long = "/bin/very-long-shell-name-ought-to-be-first"; + const input = [_][]const u8{ + zsh, zsh, zsh, zsh, // zsh score 8*4=32 + bash, bash, bash, nobody, // bash score 3*9=27 + long, long, // long score 2*42=84 + }; - const stderr = std.io.getStdErr().writer(); + for (input) |shell| { + try popcon.put(shell); + } - var topshells = try popcon.top(2); - defer topshells.deinit(); - var shellStrings = topshells.keys(); - try testing.expectEqual(shellStrings.len, 2); + var sections = try popcon.toOwnedSections(MaxShells); + defer sections.deinit(); + try testing.expectEqual(sections.index.len, 3); // all but "nobody" qualify - try stderr.print("\n", .{}); - try stderr.print("0th type: {s}\n", .{@typeName(@TypeOf(shellStrings[0]))}); - try stderr.print("1st type: {s}\n", .{@typeName(@TypeOf(shellStrings[1]))}); - try stderr.print("0th: {s}, len: {d}\n", .{ shellStrings[0], shellStrings[0].len }); - try stderr.print("0ww: /bin/very-long-shell-name-ought-to-be-first\n", .{}); - try stderr.print("1st: {s}, len: {d}\n", .{ shellStrings[1], shellStrings[1].len }); - try stderr.print("1ww: /bin/zsh\n", .{}); - - try testing.expectEqual(shellStrings[0], "/bin/very-long-shell-name-ought-to-be-first"); - try testing.expectEqual(shellStrings[1], "/bin/zsh"); + try testing.expectEqual(sections.getOffset(long).?, 0); + try testing.expectEqual(sections.getOffset(zsh).?, 1); + try testing.expectEqual(sections.getOffset(bash).?, 2); }