using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime; static class Program { const int Repeats = 7; static void Main(string[] args) { string path = args[0]; int maxEditDistance = int.Parse(args[2]); int prefixLength = int.Parse(args[3]); int words = File.ReadLines(path).Count(l => l.Split(' ').Length == 2); long liveBefore = LiveBytes(); var sw = Stopwatch.StartNew(); var symSpell = new SymSpell(words, maxEditDistance, prefixLength); symSpell.LoadDictionary(path, 0, 1); double buildMs = sw.Elapsed.TotalMilliseconds; long liveBytes = LiveBytes() - liveBefore; ReadQueries(args[1], out var hits, out var typos, out var misses, out var phrases, out var phraseWords, out var texts); int Count(string[] set) { int n = 0; foreach (var q in set) { if (symSpell.Lookup(q, SymSpell.Verbosity.Top, maxEditDistance).Count > 0) { n++; } } return n; } var (hitNs, _) = Fastest(() => Count(hits)); var (typoNs, found) = Fastest(() => Count(typos)); var (missNs, _) = Fastest(() => Count(misses)); var (segmentNs, exact) = Fastest(() => { int n = 0; for (int i = 0; i < phrases.Length; i++) { if (symSpell.WordSegmentation(phrases[i], maxEditDistance).correctedString == string.Join(" ", phraseWords[i])) { n++; } } return n; }); var textParts = new List(); foreach (var (percent, typed, original) in texts) { var (ns, right) = Fastest(() => { int n = 0; for (int i = 0; i < typed.Length; i++) { var r = symSpell.Lookup(typed[i], SymSpell.Verbosity.Top, maxEditDistance); if ((r.Count > 0 ? r[0].term : typed[i]) == original[i]) n++; } return n; }); textParts.Add( $"text{percent}_us={ns / 1e3:F0} " + $"text{percent}_fixed={100.0 * right / typed.Length:F1}%" ); } Console.WriteLine( $"C# d={maxEditDistance} prefix={prefixLength}: " + $"words={words} deletes={symSpell.EntryCount} build_ms={buildMs:F0} " + $"heap_bytes={liveBytes} layout_bytes={LayoutBytes(symSpell)} " + $"hit_ns={hitNs / hits.Length:F0} typo_ns={typoNs / typos.Length:F0} " + $"miss_ns={missNs / misses.Length:F0} found={100.0 * found / typos.Length:F1}% " + $"segment_us={segmentNs / 1e3 / phrases.Length:F1} " + $"exact={100.0 * exact / phrases.Length:F1}% " + string.Join(" ", textParts) ); } static (double ns, int count) Fastest(Func pass) { double best = double.PositiveInfinity; int count = 0; for (int i = 0; i <= Repeats; i++) { var sw = Stopwatch.StartNew(); count = pass(); if (i > 0) { best = Math.Min(best, sw.Elapsed.TotalMilliseconds * 1e6); } } return (best, count); } static long LiveBytes() { GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); var info = GC.GetGCMemoryInfo(); return info.HeapSizeBytes - info.FragmentedBytes; } static long LayoutBytes(SymSpell symSpell) { var flags = BindingFlags.NonPublic | BindingFlags.Instance; var deletes = (Dictionary)typeof(SymSpell) .GetField("deletes", flags) .GetValue(symSpell); var words = (Dictionary)typeof(SymSpell) .GetField("words", flags) .GetValue(symSpell); long bytes = 48 + 28L * deletes.EnsureCapacity(0) + 48 + 28L * words.EnsureCapacity(0); foreach (var group in deletes.Values) bytes += 24 + 8L * group.Length; foreach (var word in words.Keys) bytes += (22 + 2L * word.Length + 7) / 8 * 8; return bytes; } static void ReadQueries( string path, out string[] hits, out string[] typos, out string[] misses, out string[] phrases, out string[][] phraseWords, out List<(int, string[], string[])> texts ) { var sets = new Dictionary> { ["HITS"] = new(), ["TYPOS"] = new(), ["MISSES"] = new() }; var phraseList = new List(); var phraseWordList = new List(); texts = new List<(int, string[], string[])>(); string section = null; foreach (var raw in File.ReadLines(path)) { var line = raw.TrimEnd('\r'); if (line.Length == 0 || line[0] == '#') continue; if (line[0] == '@') { section = line.Substring(1); continue; } var f = line.Split('\t'); switch (section) { case "PHRASES": phraseList.Add(f[0]); phraseWordList.Add(f[1].Split(' ')); break; case "TEXTS": texts.Add((int.Parse(f[0]), f[1].Split(' '), f[2].Split(' '))); break; default: sets[section].Add(line); break; } } hits = sets["HITS"].ToArray(); typos = sets["TYPOS"].ToArray(); misses = sets["MISSES"].ToArray(); phrases = phraseList.ToArray(); phraseWords = phraseWordList.ToArray(); } }