Tiny cleanup

This commit is contained in:
Marek Lemańczyk committed 2025-09-19 00:43:52 +02:00
1 parent e0a5bd68c4
commit 2fc42e57c9
1 file changed
+343 -341
+343 -341
View File
@@ -6,404 +6,406 @@ namespace EvenPerfectBitScanner.ResultsParser;
internal static class Program internal static class Program
{ {
private static readonly HashSet<string> HelpOptions = new(StringComparer.OrdinalIgnoreCase) private static readonly HashSet<string> HelpOptions = new(StringComparer.OrdinalIgnoreCase)
{ {
"--?", "--?",
"-?", "-?",
"-help", "-help",
"--help", "--help",
"--h", "--h",
"-h", "-h",
"/?", "/?",
"/h", "/h",
"/help" "/help"
}; };
private const string DefaultHeader = "p,searchedMersenne,detailedCheck,isPerfect"; private const string DefaultHeader = "p,searchedMersenne,detailedCheck,isPerfect";
private static int Main(string[] args) private static int Main(string[] args)
{ {
if (args.Length == 0) if (args.Length == 0)
{ {
PrintHelp(); PrintHelp();
return 1; return 1;
} }
if (args.Length == 1 && IsHelpOption(args[0])) if (args.Length == 1 && IsHelpOption(args[0]))
{ {
PrintHelp(); PrintHelp();
return 0; return 0;
} }
if (args.Length != 1) if (args.Length != 1)
{ {
Console.Error.WriteLine("Only a single CSV file path is supported."); Console.Error.WriteLine("Only a single CSV file path is supported.");
PrintHelp(); PrintHelp();
return 1; return 1;
} }
string inputPath = args[0]; string inputPath = args[0];
if (!File.Exists(inputPath)) if (!File.Exists(inputPath))
{ {
Console.Error.WriteLine($"Input file '{inputPath}' was not found."); Console.Error.WriteLine($"Input file '{inputPath}' was not found.");
return 1; return 1;
} }
try try
{ {
ProcessFile(inputPath); ProcessFile(inputPath);
return 0; return 0;
} }
catch (Exception exception) catch (Exception exception)
{ {
Console.Error.WriteLine($"Processing failed: {exception.Message}"); Console.Error.WriteLine($"Processing failed: {exception.Message}");
return 1; return 1;
} }
} }
private static bool IsHelpOption(string value) private static bool IsHelpOption(string value)
{ {
return HelpOptions.Contains(value); return HelpOptions.Contains(value);
} }
private static void PrintHelp() private static void PrintHelp()
{ {
Console.WriteLine("EvenPerfectBitScanner.ResultsParser"); Console.WriteLine("EvenPerfectBitScanner.ResultsParser");
Console.WriteLine("Parses CSV results produced by the candidate scanner."); Console.WriteLine("Parses CSV results produced by the candidate scanner.");
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Usage:"); Console.WriteLine("Usage:");
Console.WriteLine(" EvenPerfectBitScanner.ResultsParser <results.csv>"); Console.WriteLine(" EvenPerfectBitScanner.ResultsParser <results.csv>");
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("The program accepts the following help switches:"); Console.WriteLine("The program accepts the following help switches:");
Console.WriteLine(" --?, -?, /?, -help, --help, --h, -h, /h, /help"); Console.WriteLine(" --?, -?, /?, -help, --help, --h, -h, /h, /help");
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Outputs prime results into four files placed next to the source file:"); Console.WriteLine("Outputs prime results into four files placed next to the source file:");
Console.WriteLine(" raw-primes-<name> - prime entries in the original order"); Console.WriteLine(" raw-primes-<name> - prime entries in the original order");
Console.WriteLine(" sorted-primes-<name> - prime entries sorted by p"); Console.WriteLine(" sorted-primes-<name> - prime entries sorted by p");
Console.WriteLine(" sorted-primes-passed-<name> - sorted prime entries with isPerfect = true"); Console.WriteLine(" sorted-primes-passed-<name> - sorted prime entries with isPerfect = true");
Console.WriteLine(" sorted-primes-rejected-<name> - sorted prime entries with isPerfect = false"); Console.WriteLine(" sorted-primes-rejected-<name> - sorted prime entries with isPerfect = false");
Console.WriteLine(); Console.WriteLine();
Console.WriteLine("Example:"); Console.WriteLine("Example:");
Console.WriteLine(" EvenPerfectBitScanner.ResultsParser results.csv"); Console.WriteLine(" EvenPerfectBitScanner.ResultsParser results.csv");
} }
private static void ProcessFile(string inputPath) private static void ProcessFile(string inputPath)
{ {
using IEnumerator<string> enumerator = File.ReadLines(inputPath).GetEnumerator(); using IEnumerator<string> enumerator = File.ReadLines(inputPath).GetEnumerator();
if (!enumerator.MoveNext()) if (!enumerator.MoveNext())
{ {
Console.WriteLine("Input file did not contain any data. Created empty outputs using the default header."); Console.WriteLine("Input file did not contain any data. Created empty outputs using the default header.");
string fallbackHeader = DefaultHeader; string fallbackHeader = DefaultHeader;
string rawFallbackPath = BuildOutputPath(inputPath, "raw-primes-"); string rawFallbackPath = BuildOutputPath(inputPath, "raw-primes-");
WriteCsv(rawFallbackPath, fallbackHeader, Array.Empty<CandidateResult>()); WriteCsv(rawFallbackPath, fallbackHeader, Array.Empty<CandidateResult>());
CreateEmptyOutputs(inputPath, fallbackHeader); CreateEmptyOutputs(inputPath, fallbackHeader);
return; return;
} }
string headerLine = enumerator.Current; string headerLine = enumerator.Current;
string header = string.IsNullOrWhiteSpace(headerLine) ? DefaultHeader : headerLine.Trim(); string header = string.IsNullOrWhiteSpace(headerLine) ? DefaultHeader : headerLine.Trim();
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator(); using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
if (!primeEnumerator.MoveNext()) if (!primeEnumerator.MoveNext())
{ {
throw new InvalidOperationException("Prime generator did not provide any values."); throw new InvalidOperationException("Prime generator did not provide any values.");
} }
ulong currentPrime = primeEnumerator.Current; ulong currentPrime = primeEnumerator.Current;
List<CandidateResult> pendingResults = new(); List<CandidateResult> pendingResults = new();
List<CandidateResult> primeResults = new(); List<CandidateResult> primeResults = new();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
string currentLine = enumerator.Current; string currentLine = enumerator.Current;
if (string.IsNullOrWhiteSpace(currentLine)) if (string.IsNullOrWhiteSpace(currentLine))
{ {
continue; continue;
} }
CandidateResult result = ParseLine(currentLine); CandidateResult result = ParseLine(currentLine);
InsertPendingResult(pendingResults, result); InsertPendingResult(pendingResults, result);
DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, false); DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, false);
} }
DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, true); DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, true);
string rawOutputPath = BuildOutputPath(inputPath, "raw-primes-"); string rawOutputPath = BuildOutputPath(inputPath, "raw-primes-");
WriteCsv(rawOutputPath, header, primeResults); WriteCsv(rawOutputPath, header, primeResults);
if (primeResults.Count == 0) if (primeResults.Count == 0)
{ {
Console.WriteLine("No prime entries were found in the input file."); Console.WriteLine("No prime entries were found in the input file.");
Console.WriteLine($"Created an empty prime snapshot at: {rawOutputPath}"); Console.WriteLine($"Created an empty prime snapshot at: {rawOutputPath}");
CreateEmptyOutputs(inputPath, header); CreateEmptyOutputs(inputPath, header);
return; return;
} }
List<CandidateResult> sortedResults = new(primeResults); List<CandidateResult> sortedResults = new(primeResults);
BubbleSort(sortedResults); BubbleSort(sortedResults);
List<CandidateResult> passedResults = new(); List<CandidateResult> passedResults = new();
List<CandidateResult> rejectedResults = new(); List<CandidateResult> rejectedResults = new();
for (int i = 0; i < sortedResults.Count; i++) for (int i = 0; i < sortedResults.Count; i++)
{ {
CandidateResult candidate = sortedResults[i]; CandidateResult candidate = sortedResults[i];
if (candidate.IsPerfect) if (candidate.IsPerfect)
{ {
passedResults.Add(candidate); passedResults.Add(candidate);
} }
else else
{ {
rejectedResults.Add(candidate); rejectedResults.Add(candidate);
} }
} }
string sortedOutputPath = BuildOutputPath(inputPath, "sorted-primes-"); string sortedOutputPath = BuildOutputPath(inputPath, "sorted-primes-");
string passedOutputPath = BuildOutputPath(inputPath, "sorted-primes-passed-"); string passedOutputPath = BuildOutputPath(inputPath, "sorted-primes-passed-");
string rejectedOutputPath = BuildOutputPath(inputPath, "sorted-primes-rejected-"); string rejectedOutputPath = BuildOutputPath(inputPath, "sorted-primes-rejected-");
WriteCsv(sortedOutputPath, header, sortedResults); WriteCsv(sortedOutputPath, header, sortedResults);
WriteCsv(passedOutputPath, header, passedResults); WriteCsv(passedOutputPath, header, passedResults);
WriteCsv(rejectedOutputPath, header, rejectedResults); WriteCsv(rejectedOutputPath, header, rejectedResults);
Console.WriteLine($"Processed {primeResults.Count} prime entr" + Console.WriteLine($"Processed {primeResults.Count} prime entr" +
$"{(primeResults.Count == 1 ? "y" : "ies")}."); $"{(primeResults.Count == 1 ? "y" : "ies")}.");
Console.WriteLine($"Prime results (raw order): {rawOutputPath}"); Console.WriteLine($"Prime results (raw order): {rawOutputPath}");
Console.WriteLine($"Prime results (sorted): {sortedOutputPath}"); Console.WriteLine($"Prime results (sorted): {sortedOutputPath}");
Console.WriteLine($"Prime results (passed): {passedOutputPath}"); Console.WriteLine($"Prime results (passed): {passedOutputPath}");
Console.WriteLine($"Prime results (rejected): {rejectedOutputPath}"); Console.WriteLine($"Prime results (rejected): {rejectedOutputPath}");
} }
private static void CreateEmptyOutputs(string inputPath, string header) private static void CreateEmptyOutputs(string inputPath, string header)
{ {
string sortedOutputPath = BuildOutputPath(inputPath, "sorted-primes-"); string sortedOutputPath = BuildOutputPath(inputPath, "sorted-primes-");
string passedOutputPath = BuildOutputPath(inputPath, "sorted-primes-passed-"); string passedOutputPath = BuildOutputPath(inputPath, "sorted-primes-passed-");
string rejectedOutputPath = BuildOutputPath(inputPath, "sorted-primes-rejected-"); string rejectedOutputPath = BuildOutputPath(inputPath, "sorted-primes-rejected-");
WriteCsv(sortedOutputPath, header, Array.Empty<CandidateResult>()); WriteCsv(sortedOutputPath, header, Array.Empty<CandidateResult>());
WriteCsv(passedOutputPath, header, Array.Empty<CandidateResult>()); WriteCsv(passedOutputPath, header, Array.Empty<CandidateResult>());
WriteCsv(rejectedOutputPath, header, Array.Empty<CandidateResult>()); WriteCsv(rejectedOutputPath, header, Array.Empty<CandidateResult>());
} }
private static CandidateResult ParseLine(string line) private static CandidateResult ParseLine(string line)
{ {
string trimmedLine = line.Trim(); string trimmedLine = line.Trim();
ReadOnlySpan<char> span = trimmedLine.AsSpan(); ReadOnlySpan<char> span = trimmedLine.AsSpan();
int firstCommaIndex = span.IndexOf(','); int firstCommaIndex = span.IndexOf(',');
int lastCommaIndex = span.LastIndexOf(','); int lastCommaIndex = span.LastIndexOf(',');
if (firstCommaIndex < 0 || lastCommaIndex <= firstCommaIndex) if (firstCommaIndex < 0 || lastCommaIndex <= firstCommaIndex)
{ {
throw new FormatException("Input line does not contain expected comma separators."); throw new FormatException("Input line does not contain expected comma separators.");
} }
ReadOnlySpan<char> pSpan = span[..firstCommaIndex]; ReadOnlySpan<char> pSpan = span[..firstCommaIndex];
ReadOnlySpan<char> isPerfectSpan = span[(lastCommaIndex + 1)..]; ReadOnlySpan<char> isPerfectSpan = span[(lastCommaIndex + 1)..];
ulong p = ulong.Parse(pSpan, NumberStyles.Integer, CultureInfo.InvariantCulture); ulong p = ulong.Parse(pSpan, NumberStyles.Integer, CultureInfo.InvariantCulture);
bool isPerfect = bool.Parse(isPerfectSpan); bool isPerfect = bool.Parse(isPerfectSpan);
return new CandidateResult(p, isPerfect, trimmedLine); return new CandidateResult(p, isPerfect, trimmedLine);
} }
private static string BuildOutputPath(string inputPath, string prefix) private static string BuildOutputPath(string inputPath, string prefix)
{ {
string fileName = Path.GetFileName(inputPath); string fileName = Path.GetFileName(inputPath);
string prefixedName = prefix + fileName; string prefixedName = prefix + fileName;
string? directory = Path.GetDirectoryName(inputPath); string? directory = Path.GetDirectoryName(inputPath);
if (string.IsNullOrEmpty(directory)) if (string.IsNullOrEmpty(directory))
{ {
return prefixedName; return prefixedName;
} }
return Path.Combine(directory, prefixedName); return Path.Combine(directory, prefixedName);
} }
private static void WriteCsv(string path, string header, IEnumerable<CandidateResult> entries) private static void WriteCsv(string path, string header, IEnumerable<CandidateResult> entries)
{ {
using StreamWriter writer = new(path, false, Encoding.UTF8); using StreamWriter writer = new(path, false, Encoding.UTF8);
writer.WriteLine(header); writer.WriteLine(header);
foreach (CandidateResult entry in entries) foreach (CandidateResult entry in entries)
{ {
writer.WriteLine(entry.ToCsv()); writer.WriteLine(entry.ToCsv());
} }
} }
private static void BubbleSort(List<CandidateResult> results) private static void BubbleSort(List<CandidateResult> results)
{ {
if (results.Count < 2) if (results.Count < 2)
{ {
return; return;
} }
int length = results.Count; int length = results.Count;
bool swapped; bool swapped;
do do
{ {
swapped = false; swapped = false;
for (int i = 1; i < length; i++) for (int i = 1; i < length; i++)
{ {
if (results[i - 1].P <= results[i].P) if (results[i - 1].P <= results[i].P)
{ {
continue; continue;
} }
(results[i - 1], results[i]) = (results[i], results[i - 1]); (results[i - 1], results[i]) = (results[i], results[i - 1]);
swapped = true; swapped = true;
} }
length--; length--;
} }
while (swapped); while (swapped);
} }
private static void DrainPrimeMatches( private static void DrainPrimeMatches(
List<CandidateResult> pendingResults, List<CandidateResult> pendingResults,
List<CandidateResult> primeResults, List<CandidateResult> primeResults,
ref ulong currentPrime, ref ulong currentPrime,
IEnumerator<ulong> primeEnumerator, IEnumerator<ulong> primeEnumerator,
bool inputCompleted) bool inputCompleted)
{ {
if (pendingResults.Count == 0) if (pendingResults.Count == 0)
{ {
return; return;
} }
RemoveEntriesBelowPrime(pendingResults, currentPrime); RemoveEntriesBelowPrime(pendingResults, currentPrime);
while (pendingResults.Count > 0) while (pendingResults.Count > 0)
{ {
bool extractedPrime = ExtractPrimeEntries(pendingResults, primeResults, currentPrime); bool extractedPrime = ExtractPrimeEntries(pendingResults, primeResults, currentPrime);
if (extractedPrime) if (extractedPrime)
{ {
if (!TryAdvancePrime(primeEnumerator, ref currentPrime, pendingResults)) if (!TryAdvancePrime(primeEnumerator, ref currentPrime, pendingResults))
{ {
pendingResults.Clear(); pendingResults.Clear();
return; return;
} }
RemoveEntriesBelowPrime(pendingResults, currentPrime); RemoveEntriesBelowPrime(pendingResults, currentPrime);
continue; continue;
} }
if (!inputCompleted) if (!inputCompleted)
{ {
return; return;
} }
if (!TryAdvancePrime(primeEnumerator, ref currentPrime, pendingResults)) if (!TryAdvancePrime(primeEnumerator, ref currentPrime, pendingResults))
{ {
pendingResults.Clear(); pendingResults.Clear();
return; return;
} }
RemoveEntriesBelowPrime(pendingResults, currentPrime); RemoveEntriesBelowPrime(pendingResults, currentPrime);
} }
} }
private static bool ExtractPrimeEntries( private static bool ExtractPrimeEntries(
List<CandidateResult> pendingResults, List<CandidateResult> pendingResults,
List<CandidateResult> primeResults, List<CandidateResult> primeResults,
ulong prime) ulong prime)
{ {
if (pendingResults.Count == 0) if (pendingResults.Count == 0)
{ {
return false; return false;
} }
int startIndex = FindFirstIndexAtLeast(pendingResults, prime); int startIndex = FindFirstIndexAtLeast(pendingResults, prime);
if (startIndex >= pendingResults.Count) if (startIndex >= pendingResults.Count)
{ {
return false; return false;
} }
CandidateResult firstCandidate = pendingResults[startIndex]; CandidateResult firstCandidate = pendingResults[startIndex];
if (firstCandidate.P != prime) if (firstCandidate.P != prime)
{ {
return false; return false;
} }
int endIndex = startIndex + 1; int endIndex = startIndex + 1;
while (endIndex < pendingResults.Count && pendingResults[endIndex].P == prime) while (endIndex < pendingResults.Count && pendingResults[endIndex].P == prime)
{ {
endIndex++; endIndex++;
} }
for (int i = startIndex; i < endIndex; i++) for (int i = startIndex; i < endIndex; i++)
{ {
primeResults.Add(pendingResults[i]); primeResults.Add(pendingResults[i]);
} }
pendingResults.RemoveRange(startIndex, endIndex - startIndex); pendingResults.RemoveRange(startIndex, endIndex - startIndex);
return true; return true;
} }
private static void RemoveEntriesBelowPrime(List<CandidateResult> pendingResults, ulong currentPrime) private static void RemoveEntriesBelowPrime(List<CandidateResult> pendingResults, ulong currentPrime)
{ {
if (pendingResults.Count == 0) if (pendingResults.Count == 0)
{ {
return; return;
} }
int removeCount = FindFirstIndexAtLeast(pendingResults, currentPrime); int removeCount = FindFirstIndexAtLeast(pendingResults, currentPrime);
if (removeCount == 0) if (removeCount == 0)
{ {
return; return;
} }
pendingResults.RemoveRange(0, removeCount); pendingResults.RemoveRange(0, removeCount);
} }
private static bool TryAdvancePrime( private static bool TryAdvancePrime(
IEnumerator<ulong> primeEnumerator, IEnumerator<ulong> primeEnumerator,
ref ulong currentPrime, ref ulong currentPrime,
List<CandidateResult> pendingResults) List<CandidateResult> pendingResults)
{ {
ulong minimumPending = pendingResults.Count > 0 ? pendingResults[0].P : 0UL; ulong minimumPending = pendingResults.Count > 0 ? pendingResults[0].P : 0UL;
do do
{ {
if (!primeEnumerator.MoveNext()) if (!primeEnumerator.MoveNext())
{ {
currentPrime = ulong.MaxValue; currentPrime = ulong.MaxValue;
return false; return false;
} }
currentPrime = primeEnumerator.Current; currentPrime = primeEnumerator.Current;
} }
while (pendingResults.Count > 0 && currentPrime < minimumPending); while (pendingResults.Count > 0 && currentPrime < minimumPending);
return true; return true;
} }
private static void InsertPendingResult(List<CandidateResult> pendingResults, CandidateResult result) private static void InsertPendingResult(List<CandidateResult> pendingResults, CandidateResult result)
{ {
int insertIndex = FindFirstIndexAtLeast(pendingResults, result.P); int insertIndex = FindFirstIndexAtLeast(pendingResults, result.P);
pendingResults.Insert(insertIndex, result); pendingResults.Insert(insertIndex, result);
} }
private static int FindFirstIndexAtLeast(List<CandidateResult> pendingResults, ulong value) private static int FindFirstIndexAtLeast(List<CandidateResult> pendingResults, ulong value)
{ {
int low = 0; int low = 0;
int high = pendingResults.Count - 1; int high = pendingResults.Count - 1;
while (low <= high) while (low <= high)
{ {
int mid = low + ((high - low) >> 1); int mid = low + ((high - low) >> 1);
ulong midValue = pendingResults[mid].P; ulong midValue = pendingResults[mid].P;
if (midValue < value) if (midValue < value)
{ {
low = mid + 1; low = mid + 1;
continue; continue;
} }
high = mid - 1; high = mid - 1;
} }
return low; return low;
} }
private readonly record struct CandidateResult(ulong P, bool IsPerfect, string Csv) private readonly record struct CandidateResult(ulong P, bool IsPerfect, string Csv)
{ {
public string ToCsv() public string ToCsv()
{ {
return Csv; return Csv;
} }
}
}
} }