commit
e3dc685891
12 changed files with 818 additions and 0 deletions
@ -0,0 +1,35 @@ |
|||
# Copilot Instructions |
|||
|
|||
## Communication Preferences |
|||
|
|||
- Ich bevorzuge die Kommunikation auf Deutsch |
|||
- Bitte antworte auf meine Fragen auf Deutsch |
|||
- Schreibe technische Erklärungen, Anleitungen und Hilfestellungen auf Deutsch |
|||
- Der generierte Code selbst soll jedoch auf Englisch sein |
|||
|
|||
## Language for Code |
|||
|
|||
- All code comments should be written in English |
|||
- All console output messages should be in English |
|||
- All documentation (README files, inline documentation, etc.) should be written in English |
|||
- Variable names and function names should follow English naming conventions |
|||
|
|||
## Code Style |
|||
|
|||
- Follow idiomatic Zig style |
|||
- Use clear and descriptive variable and function names |
|||
- Provide appropriate documentation for functions and complex logic |
|||
- Maintain the existing project structure and organization |
|||
|
|||
## Technical Requirements |
|||
|
|||
- The project uses Zig version 0.14.1 |
|||
- New features and syntax introduced in Zig 0.14.1 should be leveraged when appropriate |
|||
- Generated code must be compatible with Zig 0.14.1 compiler |
|||
- Build scripts should follow Zig 0.14.1 build system conventions |
|||
|
|||
## Features |
|||
|
|||
- The Pi-Finder library is focused on simplicity and performance |
|||
- JSON is the standard output format |
|||
- Any new features should maintain backward compatibility |
|||
@ -0,0 +1,42 @@ |
|||
# Zig build artifacts |
|||
zig-out/ |
|||
zig-cache/ |
|||
.zig-cache/ |
|||
|
|||
# Pi decimal file (may be large) |
|||
*.txt |
|||
|
|||
# IDE and editor files |
|||
.idea/ |
|||
.vscode/ |
|||
*.swp |
|||
*.swo |
|||
*~ |
|||
|
|||
# MacOS specific files |
|||
.DS_Store |
|||
.AppleDouble |
|||
.LSOverride |
|||
Icon? |
|||
._* |
|||
.Spotlight-V100 |
|||
.Trashes |
|||
|
|||
# Backup files |
|||
*.bak |
|||
*.tmp |
|||
*.backup |
|||
|
|||
# Local development environment settings |
|||
.env |
|||
.envrc |
|||
.direnv/ |
|||
|
|||
# Test artifacts |
|||
**/test-results/ |
|||
|
|||
# Dependency directories |
|||
/deps/ |
|||
|
|||
# Dynamic data directory |
|||
data/ |
|||
@ -0,0 +1,128 @@ |
|||
# Pi Finder Library |
|||
|
|||
A Zig library for searching numeric sequences in the decimal places of Pi. |
|||
|
|||
## Features |
|||
|
|||
- Efficient searching of numeric sequences in Pi decimal places |
|||
- JSON output as standard |
|||
- Optional plain text output format |
|||
- Configurable context display with delimiters |
|||
- Reusable module design |
|||
- Performance metrics |
|||
|
|||
## Installation |
|||
|
|||
### As a Dependency in a Zig Project |
|||
|
|||
Add this library as a dependency in your `build.zig.zon`: |
|||
|
|||
```zig |
|||
.dependencies = .{ |
|||
.@"pi-finder" = .{ |
|||
.path = "path/to/pi-finder-lib", |
|||
}, |
|||
}, |
|||
``` |
|||
|
|||
### In your build.zig |
|||
|
|||
```zig |
|||
const pi_finder_dep = b.dependency("pi-finder", .{ |
|||
.target = target, |
|||
.optimize = optimize, |
|||
}); |
|||
|
|||
exe.root_module.addImport("pi-finder", pi_finder_dep.module("pi-finder")); |
|||
``` |
|||
|
|||
## Usage |
|||
|
|||
### Basic Usage |
|||
|
|||
```zig |
|||
const std = @import("std"); |
|||
const PiFinder = @import("pi-finder"); |
|||
|
|||
pub fn main() !void { |
|||
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
|||
defer _ = gpa.deinit(); |
|||
const allocator = gpa.allocator(); |
|||
|
|||
// Initialize Pi Finder |
|||
var pi_finder = try PiFinder.PiFinder.init(allocator, "pi-data.txt"); |
|||
defer pi_finder.deinit(); |
|||
|
|||
// Configure context |
|||
const context_config = PiFinder.ContextConfig{ |
|||
.size = 50, |
|||
.position = 25, |
|||
.delimiter = "-", |
|||
.delimiter_interval = 2, |
|||
}; |
|||
|
|||
// Search for sequence |
|||
const result = try pi_finder.searchSequence("123456", context_config, allocator); |
|||
|
|||
// Output as JSON (default format) |
|||
const json_output = try PiFinder.formatSearchResultAsJson(allocator, result); |
|||
defer allocator.free(json_output); |
|||
|
|||
std.debug.print("{s}\n", .{json_output}); |
|||
} |
|||
``` |
|||
|
|||
### Using Plain Text Output Format |
|||
|
|||
```zig |
|||
// After getting the search result |
|||
const plain_text_output = try PiFinder.formatSearchResultAsPlainText(allocator, result); |
|||
defer allocator.free(plain_text_output); |
|||
|
|||
std.debug.print("{s}\n", .{plain_text_output}); |
|||
``` |
|||
|
|||
## API |
|||
|
|||
### PiFinder |
|||
|
|||
- `init(allocator, file_path)` - Initializes the Pi Finder |
|||
- `deinit()` - Frees allocated memory |
|||
- `findSequenceInPi(sequence)` - Searches for a sequence |
|||
- `getContext(position, config, sequence, allocator)` - Generates context |
|||
- `searchSequence(sequence, config, allocator)` - Comprehensive search with structured result |
|||
|
|||
### Output Format Functions |
|||
|
|||
- `formatSearchResultAsJson(allocator, result)` - Formats search result as JSON |
|||
- `formatSearchResultAsPlainText(allocator, result)` - Formats search result as plain text |
|||
|
|||
### ContextConfig |
|||
|
|||
Configuration for context display: |
|||
|
|||
- `size` - Size of the context |
|||
- `position` - Position of the sequence within the context |
|||
- `delimiter` - Delimiter character (optional) |
|||
- `delimiter_interval` - Interval for delimiter insertion |
|||
- `prefix` - Prefix for the sequence |
|||
- `suffix` - Suffix for the sequence |
|||
|
|||
### SearchResult |
|||
|
|||
Structured result containing: |
|||
|
|||
- `success` - Search success status |
|||
- `sequence` - Searched sequence |
|||
- `position` - Position in Pi (if found) |
|||
- `context` - Context around the position |
|||
- `error_message` - Error message (if any) |
|||
- `performance` - Performance metrics |
|||
- `context_config` - Used context configuration |
|||
|
|||
## Examples |
|||
|
|||
See the `examples/` directory for complete examples: |
|||
|
|||
- `birth-date-finder/` - Basic example for searching birth dates |
|||
- `birth-date-finder/src/plain_text_example.zig` - Example using plain text output format |
|||
@ -0,0 +1,33 @@ |
|||
const std = @import("std"); |
|||
|
|||
pub fn build(b: *std.Build) void { |
|||
const target = b.standardTargetOptions(.{}); |
|||
const optimize = b.standardOptimizeOption(.{}); |
|||
|
|||
// Create a module for the library |
|||
_ = b.addModule("pi-finder", .{ |
|||
.root_source_file = b.path("src/lib.zig"), |
|||
.target = target, |
|||
.optimize = optimize, |
|||
}); |
|||
|
|||
// Library |
|||
const lib = b.addStaticLibrary(.{ |
|||
.name = "pi-finder", |
|||
.root_source_file = b.path("src/lib.zig"), |
|||
.target = target, |
|||
.optimize = optimize, |
|||
}); |
|||
b.installArtifact(lib); |
|||
|
|||
// Tests |
|||
const lib_unit_tests = b.addTest(.{ |
|||
.root_source_file = b.path("src/lib.zig"), |
|||
.target = target, |
|||
.optimize = optimize, |
|||
}); |
|||
|
|||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); |
|||
const test_step = b.step("test", "Run unit tests"); |
|||
test_step.dependOn(&run_lib_unit_tests.step); |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
#!/bin/bash |
|||
|
|||
[ ! -d ./data ] && mkdir ./data |
|||
curl -o ./data/pi-decimal-10_000_000_000.txt https://einklich.net/etc/pi-decimal-10%5E9.txt |
|||
@ -0,0 +1,74 @@ |
|||
# Birth Date Finder Example |
|||
|
|||
This example demonstrates how the Pi Finder Library is used to search for a birth date (01.01.02) in the decimal places of Pi. |
|||
|
|||
## Execution |
|||
|
|||
```bash |
|||
cd birth-date-finder |
|||
zig build run |
|||
``` |
|||
|
|||
## Additional Examples |
|||
|
|||
This project includes two additional examples: |
|||
|
|||
### 1. Multiple Test Example |
|||
|
|||
```bash |
|||
zig build test-multiple |
|||
``` |
|||
|
|||
This example tests multiple different sequences and measures the performance. |
|||
|
|||
### 2. Plain Text Format Example |
|||
|
|||
```bash |
|||
zig build plain-text |
|||
``` |
|||
|
|||
This example demonstrates using the `formatSearchResultAsPlainText` function instead of the default JSON format. |
|||
|
|||
## Features |
|||
|
|||
- Searches for the sequence "010102" in Pi |
|||
- Uses '-' as delimiter every 2 digits (01-01-02 format) |
|||
- Outputs the result as JSON |
|||
- Shows performance metrics |
|||
|
|||
## Example Output |
|||
|
|||
### JSON Output (Default) |
|||
|
|||
```json |
|||
{ |
|||
"success": true, |
|||
"sequence": "010102", |
|||
"position": 12345, |
|||
"context": "...92[01-01-02]94...", |
|||
"performance": { |
|||
"file_load_time_ms": 1250.45, |
|||
"search_time_ms": 23.67, |
|||
"file_size_mb": 95.37, |
|||
"load_speed_mbs": 76.28 |
|||
}, |
|||
"context_config": { |
|||
"size": 50, |
|||
"position": 25, |
|||
"delimiter": "-", |
|||
"delimiter_interval": 2, |
|||
"prefix": "[", |
|||
"suffix": "]" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### Plain Text Output (Using formatSearchResultAsPlainText) |
|||
|
|||
```text |
|||
Search Result: |
|||
Success: true |
|||
Sequence: 010102 |
|||
Position: 12345 |
|||
Context: ...92[01-01-02]94... |
|||
``` |
|||
@ -0,0 +1,65 @@ |
|||
const std = @import("std"); |
|||
|
|||
pub fn build(b: *std.Build) void { |
|||
const target = b.standardTargetOptions(.{}); |
|||
const optimize = b.standardOptimizeOption(.{}); |
|||
|
|||
// Create the pi-finder module locally |
|||
const pi_finder_module = b.createModule(.{ |
|||
.root_source_file = b.path("../../src/lib.zig"), |
|||
}); |
|||
|
|||
const exe = b.addExecutable(.{ |
|||
.name = "birth-date-finder", |
|||
.root_source_file = b.path("src/main.zig"), |
|||
.target = target, |
|||
.optimize = optimize, |
|||
}); |
|||
|
|||
exe.root_module.addImport("pi-finder", pi_finder_module); |
|||
b.installArtifact(exe); |
|||
|
|||
const run_cmd = b.addRunArtifact(exe); |
|||
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); |
|||
|
|||
// Add test executable |
|||
const test_exe = b.addExecutable(.{ |
|||
.name = "test-multiple", |
|||
.root_source_file = b.path("src/test_multiple.zig"), |
|||
.target = target, |
|||
.optimize = optimize, |
|||
}); |
|||
|
|||
test_exe.root_module.addImport("pi-finder", pi_finder_module); |
|||
b.installArtifact(test_exe); |
|||
|
|||
const test_run_cmd = b.addRunArtifact(test_exe); |
|||
test_run_cmd.step.dependOn(b.getInstallStep()); |
|||
|
|||
const test_step = b.step("test-multiple", "Run multiple tests"); |
|||
test_step.dependOn(&test_run_cmd.step); |
|||
|
|||
// Add plain text example executable |
|||
const plain_text_exe = b.addExecutable(.{ |
|||
.name = "plain-text-example", |
|||
.root_source_file = b.path("src/plain_text_example.zig"), |
|||
.target = target, |
|||
.optimize = optimize, |
|||
}); |
|||
|
|||
plain_text_exe.root_module.addImport("pi-finder", pi_finder_module); |
|||
b.installArtifact(plain_text_exe); |
|||
|
|||
const plain_text_run_cmd = b.addRunArtifact(plain_text_exe); |
|||
plain_text_run_cmd.step.dependOn(b.getInstallStep()); |
|||
|
|||
const plain_text_step = b.step("plain-text", "Run the plain text format example"); |
|||
plain_text_step.dependOn(&plain_text_run_cmd.step); |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
const std = @import("std"); |
|||
const PiFinder = @import("pi-finder"); |
|||
|
|||
pub fn main() !void { |
|||
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
|||
defer _ = gpa.deinit(); |
|||
const allocator = gpa.allocator(); |
|||
|
|||
// Path to the Pi file (relative to the main project) |
|||
const pi_file_path = "../../data/pi-decimal-10_000_000_000.txt"; |
|||
|
|||
// Initialize Pi Finder (JSON output is standard) |
|||
var pi_finder = PiFinder.PiFinder.init(allocator, pi_file_path) catch |err| { |
|||
std.debug.print("Error initializing Pi finder: {}\n", .{err}); |
|||
return; |
|||
}; |
|||
defer pi_finder.deinit(); |
|||
|
|||
// Configure birth date |
|||
const birth_date = "010102"; // 01.01.02 |
|||
|
|||
// Context configuration with delimiter '-' |
|||
const context_config = PiFinder.ContextConfig{ |
|||
.size = 50, |
|||
.position = 25, |
|||
.delimiter = "-", |
|||
.delimiter_interval = 2, |
|||
.prefix = "[", |
|||
.suffix = "]", |
|||
}; |
|||
|
|||
// Perform search |
|||
const result = try pi_finder.searchSequence(birth_date, context_config, allocator); |
|||
|
|||
// Output result as JSON |
|||
const json_output = try PiFinder.formatSearchResultAsJson(allocator, result); |
|||
defer allocator.free(json_output); |
|||
|
|||
const stdout = std.io.getStdOut().writer(); |
|||
try stdout.print("{s}\n", .{json_output}); |
|||
|
|||
// Clean up if context was allocated |
|||
if (result.context) |context| { |
|||
if (context_config.delimiter != null) { |
|||
allocator.free(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
const std = @import("std"); |
|||
const PiFinder = @import("pi-finder"); |
|||
|
|||
/// This example demonstrates how to use the formatSearchResultAsPlainText function |
|||
/// to output search results in plain text format instead of JSON |
|||
pub fn main() !void { |
|||
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
|||
defer _ = gpa.deinit(); |
|||
const allocator = gpa.allocator(); |
|||
|
|||
// Path to the Pi file (relative to the main project) |
|||
const pi_file_path = "../../data/pi-decimal-10_000_000_000.txt"; |
|||
|
|||
// Initialize Pi Finder |
|||
var pi_finder = PiFinder.PiFinder.init(allocator, pi_file_path) catch |err| { |
|||
std.debug.print("Error initializing Pi finder: {}\n", .{err}); |
|||
return; |
|||
}; |
|||
defer pi_finder.deinit(); |
|||
|
|||
// Configure birth date |
|||
const birth_date = "010102"; // 01.01.02 |
|||
|
|||
// Context configuration with delimiter '-' |
|||
const context_config = PiFinder.ContextConfig{ |
|||
.size = 50, |
|||
.position = 25, |
|||
.delimiter = "-", |
|||
.delimiter_interval = 2, |
|||
.prefix = "[", |
|||
.suffix = "]", |
|||
}; |
|||
|
|||
// Perform search |
|||
const result = try pi_finder.searchSequence(birth_date, context_config, allocator); |
|||
|
|||
// Output result as plain text |
|||
const plain_text_output = try PiFinder.formatSearchResultAsPlainText(allocator, result); |
|||
defer allocator.free(plain_text_output); |
|||
|
|||
const stdout = std.io.getStdOut().writer(); |
|||
try stdout.print("{s}\n", .{plain_text_output}); |
|||
|
|||
// Clean up if context was allocated |
|||
if (result.context) |context| { |
|||
if (context_config.delimiter != null) { |
|||
allocator.free(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
const std = @import("std"); |
|||
const PiFinder = @import("pi-finder"); |
|||
|
|||
pub fn main() !void { |
|||
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; |
|||
defer _ = gpa.deinit(); |
|||
const allocator = gpa.allocator(); |
|||
|
|||
// Path to the Pi file (relative to the main project) |
|||
const pi_file_path = "../../data/pi-decimal-10_000_000_000.txt"; |
|||
|
|||
// Initialize Pi Finder (JSON output is standard) |
|||
var pi_finder = PiFinder.PiFinder.init(allocator, pi_file_path) catch |err| { |
|||
const result = PiFinder.SearchResult{ |
|||
.success = false, |
|||
.sequence = "N/A", |
|||
.position = null, |
|||
.context = null, |
|||
.error_message = @errorName(err), |
|||
}; |
|||
const json_output = try PiFinder.formatSearchResultAsJson(allocator, result); |
|||
defer allocator.free(json_output); |
|||
std.debug.print("{s}\n", .{json_output}); |
|||
return; |
|||
}; |
|||
defer pi_finder.deinit(); |
|||
|
|||
// Test with different birth dates |
|||
const test_dates = [_][]const u8{ "010102", "123456", "314159" }; |
|||
|
|||
for (test_dates) |birth_date| { |
|||
// Context configuration with delimiter '-' for date |
|||
const context_config = PiFinder.ContextConfig{ |
|||
.size = 40, |
|||
.position = 20, |
|||
.delimiter = "-", |
|||
.delimiter_interval = 2, |
|||
.prefix = "[", |
|||
.suffix = "]", |
|||
}; |
|||
|
|||
// Perform search |
|||
const result = try pi_finder.searchSequence(birth_date, context_config, allocator); |
|||
|
|||
// Output result as JSON |
|||
const json_output = try PiFinder.formatSearchResultAsJson(allocator, result); |
|||
defer allocator.free(json_output); |
|||
|
|||
const stdout = std.io.getStdOut().writer(); |
|||
try stdout.print("Searching for {s}:\n{s}\n\n", .{ birth_date, json_output }); |
|||
|
|||
// Clean up if context was allocated |
|||
if (result.context) |context| { |
|||
if (context_config.delimiter != null) { |
|||
allocator.free(context); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
const std = @import("std"); |
|||
|
|||
// Export all public types and functions |
|||
pub const PiFinder = @import("pi_finder.zig").PiFinder; |
|||
pub const ContextConfig = @import("pi_finder.zig").ContextConfig; |
|||
pub const SearchResult = @import("pi_finder.zig").SearchResult; |
|||
|
|||
// JSON utility functions for easier usage |
|||
pub fn formatSearchResultAsJson(allocator: std.mem.Allocator, result: SearchResult) ![]u8 { |
|||
var json_string = std.ArrayList(u8).init(allocator); |
|||
try std.json.stringify(result, .{}, json_string.writer()); |
|||
return json_string.toOwnedSlice(); |
|||
} |
|||
|
|||
// Plain text utility functions for easier usage |
|||
pub fn formatSearchResultAsPlainText(allocator: std.mem.Allocator, result: SearchResult) ![]u8 { |
|||
var plain_text = std.ArrayList(u8).init(allocator); |
|||
var writer = plain_text.writer(); |
|||
|
|||
try writer.writeAll("Success: "); |
|||
try writer.print("{}\n", .{result.success}); |
|||
try writer.writeAll("Sequence: "); |
|||
try writer.writeAll(result.sequence); |
|||
|
|||
if (result.position) |pos| { |
|||
try writer.writeAll("\nPosition: "); |
|||
try writer.print("{d}", .{pos}); |
|||
} else { |
|||
try writer.writeAll("\nPosition: N/A"); |
|||
} |
|||
|
|||
if (result.context) |context| { |
|||
try writer.writeAll("\nContext: "); |
|||
try writer.writeAll(context); |
|||
} else { |
|||
try writer.writeAll("\nContext: N/A"); |
|||
} |
|||
|
|||
if (result.error_message) |err_msg| { |
|||
try writer.writeAll("\nError Message: "); |
|||
try writer.writeAll(err_msg); |
|||
} |
|||
|
|||
return plain_text.toOwnedSlice(); |
|||
} |
|||
@ -0,0 +1,235 @@ |
|||
const std = @import("std"); |
|||
|
|||
/// Output format configuration - REMOVED |
|||
// The OutputFormat enum was removed as JSON is now the default and only output format |
|||
|
|||
/// Configuration for context around a found sequence |
|||
pub const ContextConfig = struct { |
|||
size: u64 = 25, |
|||
position: u64 = 10, |
|||
delimiter: ?[]const u8 = null, |
|||
delimiter_interval: u64 = 2, // Default: after every 2 digits (DD-MM-YY style) |
|||
prefix: []const u8 = "", |
|||
suffix: []const u8 = "", |
|||
}; |
|||
|
|||
/// Result structure for JSON output |
|||
pub const SearchResult = struct { |
|||
success: bool, |
|||
sequence: []const u8, |
|||
position: ?u64, |
|||
context: ?[]const u8, |
|||
error_message: ?[]const u8 = null, |
|||
performance: ?PerformanceMetrics = null, |
|||
context_config: ?ContextConfig = null, |
|||
|
|||
pub const PerformanceMetrics = struct { |
|||
file_load_time_ms: f64, |
|||
search_time_ms: f64, |
|||
file_size_mb: f64, |
|||
load_speed_mbs: f64, |
|||
}; |
|||
}; |
|||
|
|||
/// PiFinder structure with core functionality for searching and formatting sequences |
|||
pub const PiFinder = struct { |
|||
pi_decimals: []u8, |
|||
allocator: std.mem.Allocator, |
|||
load_time_ms: f64, |
|||
|
|||
const Self = @This(); |
|||
|
|||
/// Initializes the Pi finder and loads the Pi decimal places from file |
|||
pub fn init(allocator: std.mem.Allocator, file_path: []const u8) !Self { |
|||
const start_time = std.time.nanoTimestamp(); |
|||
|
|||
const file = std.fs.cwd().openFile(file_path, .{}) catch |err| { |
|||
return err; |
|||
}; |
|||
defer file.close(); |
|||
|
|||
const file_size = try file.getEndPos(); |
|||
const pi_data = try allocator.alloc(u8, file_size); |
|||
_ = try file.readAll(pi_data); |
|||
|
|||
// Calculate loading time |
|||
const end_time = std.time.nanoTimestamp(); |
|||
const load_time_ns = end_time - start_time; |
|||
const load_time_ms = @as(f64, @floatFromInt(load_time_ns)) / 1_000_000.0; |
|||
|
|||
return Self{ |
|||
.pi_decimals = pi_data, |
|||
.allocator = allocator, |
|||
.load_time_ms = load_time_ms, |
|||
}; |
|||
} |
|||
|
|||
/// Frees the memory |
|||
pub fn deinit(self: *Self) void { |
|||
self.allocator.free(self.pi_decimals); |
|||
} |
|||
|
|||
/// Searches for a number sequence in the Pi decimal places |
|||
pub fn findSequenceInPi(self: *const Self, sequence: []const u8) !?u64 { |
|||
// Input validation |
|||
if (sequence.len == 0) { |
|||
return null; |
|||
} |
|||
|
|||
if (sequence.len > self.pi_decimals.len) { |
|||
return null; |
|||
} |
|||
|
|||
// Check if all characters are digits |
|||
for (sequence) |char| { |
|||
if (char < '0' or char > '9') { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
// Search for the sequence in the Pi decimal places |
|||
var i: usize = 0; |
|||
outer_loop: while (i <= self.pi_decimals.len - sequence.len) : (i += 1) { |
|||
// Check each character of the sequence |
|||
for (sequence, 0..) |seq_char, k| { |
|||
if (self.pi_decimals[i + k] != seq_char) { |
|||
continue :outer_loop; |
|||
} |
|||
} |
|||
return @as(u64, i); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// Generate context around a found position with optional delimiter formatting |
|||
pub fn getContext(self: *const Self, position: u64, context_cfg: ContextConfig, sequence: []const u8, allocator: std.mem.Allocator) ![]const u8 { |
|||
// Ensure context position doesn't exceed context size |
|||
const actual_position = if (context_cfg.position >= context_cfg.size) context_cfg.size - 1 else context_cfg.position; |
|||
|
|||
// Calculate context boundaries |
|||
const context_start = if (position > actual_position) position - actual_position else 0; |
|||
const context_end_target = context_start + context_cfg.size; |
|||
const context_end = if (context_end_target <= self.pi_decimals.len) context_end_target else self.pi_decimals.len; |
|||
|
|||
const raw_context = self.pi_decimals[context_start..context_end]; |
|||
|
|||
// If no delimiter is specified, return raw context |
|||
if (context_cfg.delimiter == null) { |
|||
return raw_context; |
|||
} |
|||
|
|||
// Apply delimiter formatting to the found sequence within the context |
|||
const delimiter = context_cfg.delimiter.?; |
|||
const sequence_start_in_context = if (position > context_start) position - context_start else 0; |
|||
|
|||
// Create a copy of the context to modify |
|||
const max_delimiters = (sequence.len - 1) / context_cfg.delimiter_interval; |
|||
var formatted_context = try allocator.alloc(u8, raw_context.len + max_delimiters * delimiter.len + context_cfg.prefix.len + context_cfg.suffix.len); |
|||
var write_pos: usize = 0; |
|||
|
|||
// Copy context up to the sequence |
|||
for (raw_context[0..sequence_start_in_context]) |char| { |
|||
formatted_context[write_pos] = char; |
|||
write_pos += 1; |
|||
} |
|||
|
|||
// Add prefix |
|||
for (context_cfg.prefix) |char| { |
|||
formatted_context[write_pos] = char; |
|||
write_pos += 1; |
|||
} |
|||
|
|||
// Add the sequence with delimiters |
|||
if (sequence_start_in_context + sequence.len <= raw_context.len) { |
|||
for (sequence, 0..) |_, i| { |
|||
// Add delimiter based on interval (but not at the very beginning) |
|||
if (i > 0 and i % context_cfg.delimiter_interval == 0) { |
|||
// Add delimiter |
|||
for (delimiter) |d_char| { |
|||
formatted_context[write_pos] = d_char; |
|||
write_pos += 1; |
|||
} |
|||
} |
|||
// Add the character from the actual context (to ensure accuracy) |
|||
formatted_context[write_pos] = raw_context[sequence_start_in_context + i]; |
|||
write_pos += 1; |
|||
} |
|||
} |
|||
|
|||
// Add suffix |
|||
for (context_cfg.suffix) |char| { |
|||
formatted_context[write_pos] = char; |
|||
write_pos += 1; |
|||
} |
|||
|
|||
// Copy remaining context after the sequence |
|||
const remaining_start = sequence_start_in_context + sequence.len; |
|||
if (remaining_start < raw_context.len) { |
|||
for (raw_context[remaining_start..]) |char| { |
|||
formatted_context[write_pos] = char; |
|||
write_pos += 1; |
|||
} |
|||
} |
|||
|
|||
// Resize to actual used length |
|||
return formatted_context[0..write_pos]; |
|||
} |
|||
|
|||
/// Comprehensive search function that returns a structured result |
|||
pub fn searchSequence(self: *const Self, sequence: []const u8, context_cfg: ContextConfig, allocator: std.mem.Allocator) !SearchResult { |
|||
const start_search_time = std.time.nanoTimestamp(); |
|||
|
|||
const position = self.findSequenceInPi(sequence) catch |err| { |
|||
return SearchResult{ |
|||
.success = false, |
|||
.sequence = sequence, |
|||
.position = null, |
|||
.context = null, |
|||
.error_message = @errorName(err), |
|||
}; |
|||
}; |
|||
|
|||
const end_search_time = std.time.nanoTimestamp(); |
|||
const search_time_ns = end_search_time - start_search_time; |
|||
const search_time_ms = @as(f64, @floatFromInt(search_time_ns)) / 1_000_000.0; |
|||
|
|||
if (position) |pos| { |
|||
const context = self.getContext(pos, context_cfg, sequence, allocator) catch |err| { |
|||
return SearchResult{ |
|||
.success = false, |
|||
.sequence = sequence, |
|||
.position = pos, |
|||
.context = null, |
|||
.error_message = @errorName(err), |
|||
}; |
|||
}; |
|||
|
|||
// Calculate performance metrics |
|||
const file_size_mb = @as(f64, @floatFromInt(self.pi_decimals.len)) / (1024.0 * 1024.0); |
|||
const load_speed_mbs = file_size_mb / (self.load_time_ms / 1000.0); |
|||
|
|||
return SearchResult{ |
|||
.success = true, |
|||
.sequence = sequence, |
|||
.position = pos, |
|||
.context = context, |
|||
.performance = SearchResult.PerformanceMetrics{ |
|||
.file_load_time_ms = self.load_time_ms, |
|||
.search_time_ms = search_time_ms, |
|||
.file_size_mb = file_size_mb, |
|||
.load_speed_mbs = load_speed_mbs, |
|||
}, |
|||
.context_config = context_cfg, |
|||
}; |
|||
} else { |
|||
return SearchResult{ |
|||
.success = false, |
|||
.sequence = sequence, |
|||
.position = null, |
|||
.context = null, |
|||
.error_message = "Sequence not found in Pi", |
|||
}; |
|||
} |
|||
} |
|||
}; |
|||
Loading…
Reference in new issue