Bastian (BaM) 7 months ago
commit
e3dc685891
  1. 35
      .github/copilot-instructions.md
  2. 42
      .gitignore
  3. 128
      README.md
  4. 33
      build.zig
  5. 4
      download-pi-data.sh
  6. 74
      examples/birth-date-finder/README.md
  7. 65
      examples/birth-date-finder/build.zig
  8. 48
      examples/birth-date-finder/src/main.zig
  9. 50
      examples/birth-date-finder/src/plain_text_example.zig
  10. 59
      examples/birth-date-finder/src/test_multiple.zig
  11. 45
      src/lib.zig
  12. 235
      src/pi_finder.zig

35
.github/copilot-instructions.md

@ -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

42
.gitignore

@ -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/

128
README.md

@ -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

33
build.zig

@ -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);
}

4
download-pi-data.sh

@ -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

74
examples/birth-date-finder/README.md

@ -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...
```

65
examples/birth-date-finder/build.zig

@ -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);
}

48
examples/birth-date-finder/src/main.zig

@ -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);
}
}
}

50
examples/birth-date-finder/src/plain_text_example.zig

@ -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);
}
}
}

59
examples/birth-date-finder/src/test_multiple.zig

@ -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);
}
}
}
}

45
src/lib.zig

@ -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();
}

235
src/pi_finder.zig

@ -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…
Cancel
Save