// signsNumber - not digits - places between them
        private static void Problem10598(string filename, int signsNumber, bool allowNegative = true)
        {
            var sb = new StringBuilder();

            for (var i = 1; i <= signsNumber + 1; i++)
            {
                sb.Append(i);
            }

            long totalExpr   = 0;
            long matchedExpr = 0;

            var map = new Dictionary <double, string>();

            var startTime = DateTime.Now;
            var fileInfo  = new FileInfo(filename);

            using (var sw = new StreamWriter(fileInfo.Open(FileMode.Create)))
            {
                sw.WriteLine("=============================");
                var bracesEnumerator = new BracesEnumerator(sb.ToString(), allowNegative);
                var trees            = bracesEnumerator.Braces(signsNumber, 0);
                foreach (var tree in trees)
                {
                    totalExpr++;

                    var res = tree.Evaluate();



                    if (Math.Abs(res - Math.Round(res)) > BracesEnumerator.Epsilon)
                    {
                        // ignore with fraction part
                        //continue;
                    }

                    if (Math.Abs(res) > 20_000)
                    {
                        // ignore too large
                        //continue;
                    }

                    if (Math.Abs(res) < BracesEnumerator.Epsilon)
                    {
                        // ignore numbers like 1.0E-117
                        // NOTE: zero is ignored too!
                        //continue;
                    }

                    if (!map.ContainsKey(res))
                    {
                        matchedExpr++;
                        map[res] = tree.ToString();
                    }



                    if (totalExpr % 1000000 == 0)
                    {
                        sw.Flush();
                        Console.WriteLine($"totalExpr = {totalExpr}");
                    }
                }

                var diffTime = DateTime.Now - startTime;

                sw.WriteLine(
                    $"total {totalExpr} in {diffTime.TotalMilliseconds} => {totalExpr / diffTime.TotalMilliseconds} records/ms");
                sw.WriteLine(
                    $"real {matchedExpr} in {diffTime.TotalMilliseconds} => {matchedExpr/ diffTime.TotalMilliseconds} records/ms");

                Console.WriteLine(
                    $"total {totalExpr} in {diffTime.TotalMilliseconds} => {totalExpr / diffTime.TotalMilliseconds} records/ms");
                Console.WriteLine(
                    $"real {matchedExpr} in {diffTime.TotalMilliseconds} => {matchedExpr / diffTime.TotalMilliseconds} records/ms");
                Console.WriteLine(
                    $"total/real {(double)totalExpr / matchedExpr}");


                // print keys in sorted order
                {
                    sw.WriteLine("-------- The same sorted --------");
                    var keys = map.Keys.ToArray();
                    Array.Sort(keys);

                    var prev   = 2.0;
                    var lastOk = 2.0;
                    foreach (var key in keys)
                    {
                        //Console.WriteLine($"{key} = {map[key]}");
                        sw.WriteLine($"{key} = {map[key]}");

                        if (key >= prev && Math.Abs(lastOk - 2.0) < BracesEnumerator.Epsilon)
                        {
                            if (Math.Abs(key - prev - 1.0) >= BracesEnumerator.Epsilon &&
                                Math.Abs(key - prev) >= BracesEnumerator.Epsilon)
                            {
                                // not OK
                                lastOk = prev;
                            }

                            prev = key;
                        }
                    }
                    sw.WriteLine($"lastOk: {lastOk}");
                }
            }
        }
        public static void BarnaulHappyTickets(int fromIndex, int toIndex, string filename, bool allowNegative = true)
        {
            const int signsNumber = 5; // not digits - places between them

            long totalExpr   = 0;
            long matchedExpr = 0;

            var startTime       = DateTime.Now;
            var fileInfo        = new FileInfo(filename);
            var workingFileMode = FileMode.Create;

            // fail over (for continue work after program stop)
            // if the last valuable line starts with number, continue from 1st number in the line
            var failoverFileInfo = new FileInfo($"lock-index-{fromIndex}-{toIndex}.txt");

            if (failoverFileInfo.Exists && failoverFileInfo.Length > 0)
            {
                using (var sr = new StreamReader(failoverFileInfo.Name))
                {
                    var line = sr.ReadLine();
                    if (!int.TryParse(line, out var lastIndex))
                    {
                        throw new Exception($"Cannot parse {line}");
                    }

                    fromIndex       = lastIndex + 1; // start from the next
                    workingFileMode = FileMode.Append;
                }
            }


            using (var sw = new StreamWriter(fileInfo.Open(workingFileMode)))
            {
                sw.WriteLine("=============================");
                for (int i = fromIndex; i <= toIndex; i++)
                {
                    var bracesEnumerator = new BracesEnumerator(UnknownTickets[i].ToString("000000"), allowNegative);
                    var trees            = bracesEnumerator.Braces(signsNumber, 0);
                    foreach (var tree in trees)
                    {
                        totalExpr++;

                        var res = tree.Evaluate();

                        if (Math.Abs(res - 100) < 1.0E-12)
                        {
                            matchedExpr++;
                            var treeStr = tree.ToString();
                            sw.WriteLine($"{UnknownTickets[i]} : {treeStr}");
                            break;
                        }

                        const int divider = 1000_000;
                        if (totalExpr % divider == 0)
                        {
                            sw.Flush();
                            Console.WriteLine($"totalExpr = {totalExpr / divider} M, thread {fromIndex} in {(DateTime.Now - startTime).TotalMilliseconds} ms");
                            Thread.Sleep(10); // allow other threads to do smth
                        }
                    }

                    using (var lockSw = new StreamWriter(failoverFileInfo.Name))
                    {
                        lockSw.WriteLine(i);
                    }
                }

                var diffTime = DateTime.Now - startTime;

                sw.WriteLine(
                    $"total {totalExpr} in {diffTime.TotalMilliseconds} => {totalExpr / diffTime.TotalMilliseconds} records/ms");
                sw.WriteLine(
                    $"real {matchedExpr} in {diffTime.TotalMilliseconds} => {matchedExpr/ diffTime.TotalMilliseconds} records/ms");

                Console.WriteLine(
                    $"total {totalExpr} in {diffTime.TotalMilliseconds} => {totalExpr / diffTime.TotalMilliseconds} records/ms");
                Console.WriteLine(
                    $"real {matchedExpr} in {diffTime.TotalMilliseconds} => {matchedExpr / diffTime.TotalMilliseconds} records/ms");
            }
        }