Esempio n. 1
0
    public static void Main()
    {
        var         s    = Console.ReadLine();
        var         n    = s.Length;
        RollingHash hash = new RollingHash(s);
        long        res  = 0;

        for (int i = n - 1; i >= 0; i--)
        {
            //|-n-|-i-|-k-|-k-|-e-|-i-|
            var len = s.Length - i;
            var s6  = hash.Slice(i, n - i);
            for (int j = 1; j < n - len * 2; j++)
            {
                if (s6 != hash.Slice(j, len))
                {
                    continue;
                }
                var middleLen = (n - j) - len * 2;
                var startInd  = j + len;
                for (int k = 1; k <= (middleLen - 1) / 2; k++)
                {
                    var first  = hash.Slice(startInd, k);
                    var second = hash.Slice(startInd + k, k);
                    if (first == second)
                    {
                        res++;
                    }
                }
            }
        }
        Console.WriteLine(res);
    }
Esempio n. 2
0
        public void CalcBaseHashTest2()
        {
            RollingHash rollingHash = new RollingHash(65536, 62851);

            Assert.AreEqual(37064u, rollingHash.CalcBaseHash("TM", 0, 1));
            Assert.AreEqual(31700u, rollingHash.CalcBaseHash("RS", 0, 1));
        }
Esempio n. 3
0
        public void StringSearchSliceTest(string t, string p, params int[] indices)
        {
            var tHash = new RollingHash(t);
            var pHash = new RollingHash(p);

            var results = new List <int>();
            var ph      = pHash[..];
Esempio n. 4
0
    static string Solve2(int n, RollingHash hash, RollingHash revHash)
    {
        var unmatchPoint = 0;

        for (int i = 1; i < n; i++)
        {
            if (hash.Slice(0, i) != revHash.Slice(0, i))
            {
                unmatchPoint = i - 1;
                break;
            }
        }
        var longest = 0;

        for (int i = 1; i <= n - unmatchPoint * 2; i++)
        {
            if (hash.Slice(unmatchPoint, i) == revHash.Slice(n - unmatchPoint - i, i))
            {
                longest = i;
            }
        }

        return(hash.Original.Substring(0, unmatchPoint)
               + hash.Original.Substring(unmatchPoint, longest)
               + hash.Original.Substring(n - unmatchPoint, unmatchPoint));
    }
Esempio n. 5
0
    static int rabin_karp(string word, string text)
    {
        if (word == "" || text == "")
        {
            return(-1);
        }
        if (word.Length > text.Length)
        {
            return(-1);
        }

        RollingHash rolling_hash = new RollingHash(text, word.Length);
        RollingHash word_hash    = new RollingHash(word, word.Length); // Won't call move_window on that

        for (int i = 0; i < (text.Length - word.Length + 1); i++)
        {
            //Console.WriteLine("Word Hash = " + word_hash.hash + ", Rolling Hash = " + rolling_hash.hash);
            if (rolling_hash.hash == word_hash.hash)
            {
                if (rolling_hash.window_text() == word)
                {
                    return(i);
                }
            }
            rolling_hash.move_window();
        }
        return(-1);
    }
Esempio n. 6
0
        public void Xdelta3ChecksumHash32BlockBenchmark_Test()
        {
            using var srcStream    = File.OpenRead($"patches{Path.DirectorySeparatorChar}a.test");
            using var targetStream = File.OpenRead($"patches{Path.DirectorySeparatorChar}b.test");
            using var md5          = MD5.Create();
            var originalHash = md5.ComputeHash(targetStream);

            targetStream.Position = 0;

            using var deltaStream  = new MemoryStream();
            using var outputStream = new MemoryStream();
            using var hasher       = new RollingHash(32);

            using VcEncoder coder = new VcEncoder(srcStream, targetStream, deltaStream, blockSize: 32, rollingHash: hasher);
            VCDiffResult result = coder.Encode(checksumFormat: ChecksumFormat.Xdelta3); //encodes with no checksum and not interleaved

            Assert.Equal(VCDiffResult.SUCCESS, result);

            srcStream.Position    = 0;
            targetStream.Position = 0;
            deltaStream.Position  = 0;

            using VcDecoder decoder = new VcDecoder(srcStream, deltaStream, outputStream);
            Assert.Equal(VCDiffResult.SUCCESS, decoder.Decode(out long bytesWritten));
            outputStream.Position = 0;
            deltaStream.Position  = 0;
            var outputHash = md5.ComputeHash(outputStream);

            Assert.Equal(originalHash, outputHash);
            File.WriteAllBytes("patch.xdelta", deltaStream.ToArray());
        }
Esempio n. 7
0
        public void CalcBaseHashTest1()
        {
            RollingHash rollingHash = new RollingHash(256, 101);

            Assert.AreEqual(65u, rollingHash.CalcBaseHash("hi", 0, 1));
            Assert.AreEqual(4u, rollingHash.CalcBaseHash("abr", 0, 2));
            Assert.AreEqual(30u, rollingHash.CalcBaseHash("bra", 0, 2));
        }
Esempio n. 8
0
    public static void Main()
    {
        var         s  = Console.ReadLine();
        var         t  = Console.ReadLine();
        RollingHash rh = new RollingHash(t);

        bool[] canOverlap = new bool[t.Length];
        for (int i = 0; i < canOverlap.Length; i++)
        {
            canOverlap[i] = rh[i..] == rh[..^ i];
Esempio n. 9
0
    static bool IsValid(RollingHash hash, int len)
    {
        HashSet <ulong> hashes = new HashSet <ulong>();

        for (int i = len; i <= hash.Length - len; i++)
        {
            hashes.Add(hash.Slice((i - len), len));
            if (hashes.Contains(hash.Slice(i, len)))
            {
                return(true);
            }
        }
        return(false);
    }
Esempio n. 10
0
    static string Solve1(int n, RollingHash hash, RollingHash revHash)
    {
        var max = 0;

        for (int i = 1; i < n; i++)
        {
            if (hash.Slice(0, i) == revHash.Slice(n - i, i))
            {
                max = i;
            }
        }
        return(hash.Original.Substring(0, max));
        //return max;
    }
Esempio n. 11
0
    public static void Main()
    {
        string      s  = Console.ReadLine();
        RollingHash rh = new RollingHash(s);

        if (s.Distinct().Count() == 1)
        {
            Console.WriteLine(s.Length);
            Console.WriteLine(1);
            return;
        }
        bool[] isBad = new bool[s.Length];
        for (int i = 1; i < s.Length; i++)
        {
            var hash = rh[0..i];
Esempio n. 12
0
    public static void Main()
    {
        var s     = Console.ReadLine();
        var t     = Console.ReadLine();
        var srh   = new RollingHash(s);
        var thash = new RollingHash(t).Slice(0, t.Length);

        for (int i = 0; i <= s.Length - t.Length; i++)
        {
            var shash = srh.Slice(i, t.Length);
            if (shash == thash)
            {
                Console.WriteLine(i);
            }
        }
    }
Esempio n. 13
0
    public void Solve()
    {
        var  S       = Reader.String();
        var  hash    = new RollingHash(S);
        var  hashrev = new RollingHash(new string(S.Reverse().ToArray()));
        long ans     = 0;

        for (int i = S.Length / 2 + 1; i < S.Length - 1; i++)
        {
            int remLen = S.Length - i;
            int L = 0, R = Math.Min(i - 2, remLen - 1) + 1;
            while (R - L > 1)
            {
                int mid = (L + R) / 2;
                if (hash[0, mid] == hash[i, i + mid])
                {
                    L = mid;
                }
                else
                {
                    R = mid;
                }
            }
            int A = L;
            L = 0; R = Math.Min(i - 2, remLen - 1) + 1;
            while (R - L > 1)
            {
                int mid = (L + R) / 2;
                if (hashrev[0, mid] == hashrev[remLen, remLen + mid])
                {
                    L = mid;
                }
                else
                {
                    R = mid;
                }
            }
            int C = L;
            if (A > 0 && C > 0 && A + C >= remLen)
            {
                ans += A - (remLen - C) + 1;
            }
        }

        Console.WriteLine(ans);
    }
Esempio n. 14
0
        public void Hash()
        {
            string s1 = "abc";

            Assert.AreEqual(90, RollingHash.GetHash(s1, Prime, NumCharactersInAlphabet));

            string s2 = "bcd";

            Assert.AreEqual(31, RollingHash.GetHash(s2, Prime, NumCharactersInAlphabet));

            /*Note that:  string s = "abcd"; // Hash(bcd) = Hash(abc) - hash (a) + hash(d) */

            int hashConstant = RollingHash.ComputeHashConstantForRollingHash(3, Prime, NumCharactersInAlphabet);

            // Assuming we are rolling the hash window over string s above.
            Assert.AreEqual(31, RollingHash.GenerateRollingHash(90, 'a', 'd', 3, hashConstant, Prime, NumCharactersInAlphabet));
        }
Esempio n. 15
0
        public void Simple()
        {
            const string        a2m = "abcdefghijklm";
            ReadOnlySpan <char> str = a2m + a2m;
            var rh = new RollingHash(str);

            var notMatchCount             = 0;
            var notMatchHashNotMatchCount = 0;

            for (int l1 = 0; l1 < str.Length; l1++)
            {
                for (int r1 = l1 + 1; r1 < str.Length; r1++)
                {
                    for (int l2 = 0; l2 < str.Length; l2++)
                    {
                        for (int r2 = l2 + 1; r2 < str.Length; r2++)
                        {
                            if (str[l1..r1].Equals(str[l2..r2], StringComparison.Ordinal))
Esempio n. 16
0
        /// <summary>
        /// Creates a tree from the given chunks. Children are grouped to increase the likelihood of node reuse.
        /// </summary>
        private static DedupNode CreateRollingHashTree(IReadOnlyList <DedupNode> chunks)
        {
            // If we do need to make a tree, then we'll want to use a rolling hash function to ensure
            // that we get consistent groupings of children nodes even with insertions/removals
            // of children (i.e. changes to the underlying file).
            var rolling = new RollingHash(
                windowLength: 4,
                bitMask: VariableChildCountBitMask,
                minCount: MinVariableChildCount);
            var thisLevel = new Queue <DedupNode>(chunks);

            while (thisLevel.Count > DedupNode.MaxDirectChildrenPerNode)
            {
                var nextLevel = new Queue <DedupNode>();
                while (thisLevel.Any())
                {
                    rolling.Reset();
                    var nodesForChild = new List <DedupNode>();
                    while (thisLevel.Any() && nodesForChild.Count < DedupNode.MaxDirectChildrenPerNode && !rolling.IsAtBoundary)
                    {
                        var node = thisLevel.Dequeue();

                        ulong nodeHash = 0;
                        nodeHash ^= BitConverter.ToUInt64(node.Hash, 0);
                        nodeHash ^= BitConverter.ToUInt64(node.Hash, 8);
                        nodeHash ^= BitConverter.ToUInt64(node.Hash, 16);
                        nodeHash ^= BitConverter.ToUInt64(node.Hash, 24);

                        rolling.Add(nodeHash);

                        nodesForChild.Add(node);
                    }

                    var newNode = new DedupNode(nodesForChild);
                    nextLevel.Enqueue(newNode);
                }

                thisLevel = nextLevel;
            }

            var root = new DedupNode(thisLevel.ToList());

            return(root);
        }
Esempio n. 17
0
        static void Method(string[] args)
        {
            int         n           = ReadInt();
            string      s           = Read();
            RollingHash rollingHash = new RollingHash(s);
            int         bottom      = 0;
            int         top         = n;

            while (bottom + 1 < top)
            {
                int  mid = (bottom + top) / 2;
                var  set = new HashSet <string>();
                var  sub = new Queue <string>();
                bool can = false;
                for (int i = 0; i + mid - 1 < n; i++)
                {
                    long[] hash    = rollingHash.GetRangeHash(i, i + mid - 1);
                    string hashStr = "";
                    for (int j = 0; j < hash.Length; j++)
                    {
                        hashStr += hash[j].ToString();
                    }
                    if (set.Contains(hashStr))
                    {
                        can = true;
                        break;
                    }
                    sub.Enqueue(hashStr);
                    if (sub.Count >= mid)
                    {
                        set.Add(sub.Dequeue());
                    }
                }
                if (can)
                {
                    bottom = mid;
                }
                else
                {
                    top = mid;
                }
            }
            WriteLine(bottom);
        }
Esempio n. 18
0
    public static void Main()
    {
        int n            = int.Parse(Console.ReadLine());
        var s            = Console.ReadLine();
        int curMaxLength = 0;
        var hash         = new RollingHash(s);

        for (int i = 0; i < s.Length; i++)
        {
            for (int j = i; j < s.Length; j++)
            {
                while (curMaxLength < j - i && j + curMaxLength < n && hash.Slice(i, curMaxLength + 1) == hash.Slice(j, curMaxLength + 1))
                {
                    curMaxLength++;
                }
            }
        }
        Console.WriteLine(curMaxLength);
    }
Esempio n. 19
0
    public static void Main()
    {
        Console.SetOut(new StreamWriter(Console.OpenStandardOutput())
        {
            AutoFlush = false
        });
        var hw = Console.ReadLine().Split().Select(int.Parse).ToArray();
        var h  = hw[0];
        var w  = hw[1];

        RollingHash[] rhs = new RollingHash[h];
        for (int i = 0; i < h; i++)
        {
            rhs[i] = new RollingHash(Console.ReadLine());
        }
        var rc = Console.ReadLine().Split().Select(int.Parse).ToArray();
        var r  = rc[0];
        var c  = rc[1];

        ulong[] hashes = new ulong[r];
        for (int i = 0; i < r; i++)
        {
            hashes[i] = new RollingHash(Console.ReadLine()).Slice(0, c);
        }
        for (int i = 0; i <= h - r; i++)
        {
            for (int j = 0; j <= w - c; j++)
            {
                for (int k = 0; k < r; k++)
                {
                    if (rhs[i + k].Slice(j, c) != hashes[k])
                    {
                        goto end;
                    }
                }
                Console.WriteLine($"{i} {j}");
                end :;
            }
        }
        Console.Out.Flush();
    }
Esempio n. 20
0
    public static void Main()
    {
        Console.SetOut(new StreamWriter(Console.OpenStandardOutput())
        {
            AutoFlush = false
        });
        var s     = Console.ReadLine();
        var t     = Console.ReadLine();
        var srh   = new RollingHash(s);
        var thash = new RollingHash(t).Slice(0, t.Length);

        for (int i = 0; i <= s.Length - t.Length; i++)
        {
            var shash = srh.Slice(i, t.Length);
            if (shash == thash)
            {
                Console.WriteLine(i);
            }
        }
        Console.Out.Flush();
    }
Esempio n. 21
0
    public static void Main()
    {
        Console.SetOut(new StreamWriter(Console.OpenStandardOutput())
        {
            AutoFlush = false
        });
        var nq = Console.ReadLine().Split().Select(int.Parse).ToArray();
        var n  = nq[0];
        var q  = nq[1];

#if DEBUG
        var s0 = "aa";
        var t  = string.Join("", Enumerable.Repeat('a', n));
        //var t = string.Join("", Enumerable.Range(0, n).Select(x => (char)('a' + x % 26)));
#else
        var s0 = Console.ReadLine();
        var t  = Console.ReadLine();
#endif

        var queries = Enumerable.Repeat(0, q).Select(_ => Console.ReadLine()).ToArray();

        int maxq = queries.Max(x => x.Split().Last().Length);

        List <string> s = new List <string>()
        {
            s0
        };
        for (int i = 0; i < t.Length; i++)
        {
            var news = s.Last() + t[i] + s.Last();
            s.Add(news);
            if (maxq <= news.Length)
            {
                break;
            }
        }

        var sas = s.Select(SuffixArray).ToArray();

        var rh = new RollingHash(s.Last());
        public static int Search(string text, string pattern)
        {
            int n = text.Length;
            int m = pattern.Length;

            int hashConstant = RollingHash.ComputeHashConstantForRollingHash(m, PrimeNumber, NumCharacters);
            int patternHash  = RollingHash.GetHash(pattern, PrimeNumber, NumCharacters); /* This hash is computed only once. Complexity : O(subString.Length)*/

            if (m > text.Length)
            {
                return(-1);
            }

            string patternInText     = text.Substring(0, m);
            int    patternInTextHash = RollingHash.GetHash(patternInText, PrimeNumber, NumCharacters);

            for (int i = 0; i < n - 1; i++) /* O(text.Length) */
            {
                if (patternHash == patternInTextHash)
                {
                    if (pattern == patternInText) /* This check is necessary as the hash function may have collisions.*/
                    {
                        return(i);
                    }
                }

                if (i < n - m)
                {
                    patternInText     = text.Substring(i + 1, m);                                                                                              /* a substring in text, size of pattern, starting at index i;*/
                    patternInTextHash = RollingHash.GenerateRollingHash(patternInTextHash, text[i], text[i + m], m, hashConstant, PrimeNumber, NumCharacters); /* O(1) with a rolling hash, otherwise: O(pattern.Length) */
                }
                else
                {
                    break;
                }
            }

            return(-1);
        }
Esempio n. 23
0
    public void Solve()
    {
        var  S       = Reader.String();
        var  hash    = new RollingHash(S);
        var  hashrev = new RollingHash(new string(S.Reverse().ToArray()));
        long ans     = 0;

        for (int i = S.Length / 2 + 1; i < S.Length - 1; i++)
        {
            int Rlen = S.Length - i;
            int A    = BinarySearchMax(0, Math.Min(i - 2, Rlen - 1),
                                       x => hash[0, x] == hash[i, i + x]);
            int C = BinarySearchMax(0, Math.Min(i - 2, Rlen - 1),
                                    x => hashrev[0, x] == hashrev[Rlen, Rlen + x]);
            if (A > 0 && C > 0 && A + C >= Rlen)
            {
                ans += A - (Rlen - C) + 1;
            }
        }

        Console.WriteLine(ans);
    }
Esempio n. 24
0
        public override IEnumerable <object> Solve(TextReader inputStream)
        {
            var n = inputStream.ReadInt();
            var s = new string[n];

            for (int i = 0; i < s.Length; i++)
            {
                s[i] = inputStream.ReadLine().Reverse().Join();
            }

            Array.Sort(s, (l, r) => l.Length - r.Length);

            long result        = 0;
            var  lastCharFlags = new Dictionary <(ulong, ulong), int>();

            foreach (var si in s)
            {
                var rollingHash = new RollingHash(si);
                int flag        = 0;
                for (int length = si.Length - 1; length >= 0; length--)
                {
                    flag |= 1 << (si[length] - 'a');
                    var hash = rollingHash[..length];
Esempio n. 25
0
    public static void Main()
    {
        int n = int.Parse(Console.ReadLine());
        var a = Console.ReadLine().Split().Select(uint.Parse).ToArray();
        var b = Console.ReadLine().Split().Select(uint.Parse).ToArray();

        b = b.Concat(b).ToArray();
        var         aDiffXor = a.Skip(n - 1).Concat(a).Zip(a, (x, y) => x ^ y).ToArray();
        var         bDiffXor = b.Skip(n - 1).Concat(b).Zip(b, (x, y) => x ^ y).ToArray();
        RollingHash aHash    = new RollingHash(aDiffXor);
        RollingHash bHash    = new RollingHash(bDiffXor);

        for (int i = n; i >= 1; i--)
        {
            var aHashVal = aHash.Slice(0, n);
            var bHashVal = bHash.Slice(i, n);
            if (aHashVal != bHashVal)
            {
                continue;
            }
            Console.WriteLine($"{n - i} {a[0] ^ b[i]}");
        }
    }
Esempio n. 26
0
    public static void Main()
    {
        int n    = int.Parse(Console.ReadLine());
        var s    = Console.ReadLine();
        var hash = new RollingHash(s);

        int valid = 0, invalid = n;

        while (invalid - valid > 1)
        {
            var mid = (valid + invalid) / 2;
            if (IsValid(hash, mid))
            {
                valid = mid;
            }
            else
            {
                invalid = mid;
            }
        }

        Console.WriteLine(valid);
    }
Esempio n. 27
0
    static void Solve()
    {
        var s       = Console.ReadLine();
        var revS    = string.Join("", s.Reverse());
        var hash    = new RollingHash(s);
        var revHash = new RollingHash(revS);

        if (s == revS)
        {
            Console.WriteLine(s);
            return;
        }
        var reses = new[]
        {
            Solve1(s.Length, hash, revHash),
            Solve1(s.Length, revHash, hash),
            Solve2(s.Length, hash, revHash),
            Solve2(s.Length, revHash, hash)
        };
        var maxLen = reses.Max(x => x.Length);
        var res    = reses.First(x => x.Length == maxLen);

        Console.WriteLine(res);
    }
Esempio n. 28
0
        public void CalcRollingHashTest2()
        {
            RollingHash rollingHash = new RollingHash(65536, 62851);

            Assert.AreEqual(18274u, rollingHash.CalcRollingHash(37064, 65536 % 62851, 'T', 'R'));
        }
Esempio n. 29
0
        static void Method(string[] args)
        {
            string        s  = Read();
            string        t  = Read();
            StringBuilder ss = new StringBuilder(s);

            while (ss.Length < s.Length + t.Length - 1)
            {
                ss.Append(s);
            }
            RollingHash rollingHash = new RollingHash(ss.ToString());

            long[] hash  = rollingHash.GetStringHash(t);
            int[]  graph = new int[s.Length];
            for (int i = 0; i < s.Length; i++)
            {
                graph[i] = -1;
            }
            for (int i = 0; i < s.Length; i++)
            {
                if (rollingHash.ValidateRange(i, t.Length, hash))
                {
                    graph[i] = (i + t.Length) % s.Length;
                }
            }
            int[] lengthes = new int[s.Length];
            for (int i = 0; i < s.Length; i++)
            {
                lengthes[i] = -1;
            }
            for (int i = 0; i < s.Length; i++)// from leaf
            {
                if (graph[i] >= 0)
                {
                    continue;
                }

                int cnt = 0;
                lengthes[i] = cnt;
                int now = (i - t.Length % s.Length + s.Length) % s.Length;
                while (graph[now] >= 0)
                {
                    cnt++;
                    lengthes[now] = cnt;
                    now           = (now - t.Length % s.Length + s.Length) % s.Length;
                }
            }

            int res = 0;

            for (int i = 0; i < s.Length; i++)
            {
                if (lengthes[i] == -1)
                {
                    WriteLine(-1);
                    return;
                }

                res = Max(res, lengthes[i]);
            }
            WriteLine(res);
        }
        public async override Task <ISaveGame> CreateSave(IIndelibleDirectory saveContents)
        {
            if (!this.ProfileRoot.ContainsDirectory("base"))
            {
                await this.CreateBaseSave(saveContents);
            }
            using var rollingHash = new RollingHash(32);

            // setup
            var newGuid          = Guid.NewGuid();
            var saveName         = $"{DateTimeOffset.UtcNow.ToString(DateFormat)}-{newGuid}";
            var saveDirectory    = this.ProfileRoot.OpenDirectory(saveName);
            var contentDirectory = saveDirectory.OpenDirectory("content");

            // diff is for anything that exists in the base directory
            var diffDir = contentDirectory.OpenDirectory("diff");

            // copy is for anything that does not and can not be diffed.
            var copyDir = contentDirectory.OpenDirectory("copy");

            // Traverse base directory in tandem with saveContents
            var baseDir = this.ProfileRoot.OpenDirectory("base/content").AsReadOnly();

            foreach (var f in saveContents.EnumerateFiles())
            {
                if (!baseDir.ContainsFile(f.Name))
                {
                    await copyDir.CopyFromAsync(f);

                    continue;
                }

                using var targetStream = f.OpenReadStream();
                using var baseStream   = baseDir.OpenFile(f.Name).OpenReadStream();

                using var outStream = diffDir.OpenFile(f.Name).OpenStream();
                using var decoder   = new VcEncoder(baseStream, targetStream, outStream,
                                                    rollingHash: rollingHash, blockSize: 32);
                VCDiffResult result = await decoder.EncodeAsync();

                if (result != VCDiffResult.SUCCESS)
                {
                    throw new IOException($"Failed to encode delta for {f.Name}");
                }
            }

            foreach (var d in saveContents.EnumerateDirectories().Where(d => !baseDir.ContainsDirectory(d.Name)))
            {
                // Copy all directories not in the base.
                await foreach (var _ in copyDir.OpenDirectory(d.Name).CopyFromDirectory(d))
                {
                }
                ;
            }

            var queuedDirs = (from targetDir in saveContents.EnumerateDirectories()
                              where baseDir.ContainsDirectory(targetDir.Name)
                              select(diffDir, baseDir.OpenDirectory(targetDir.Name), targetDir)).ToList();

            Queue <(IDirectory parentDir, IReadOnlyDirectory baseDir, IDirectory targetDir)> dirsToProcess =
                new Queue <(IDirectory, IReadOnlyDirectory, IDirectory)>(queuedDirs);

            while (dirsToProcess.Count > 0)
            {
                var(parent, src, diff) = dirsToProcess.Dequeue();
                var dst = parent.OpenDirectory(src.Name);
                foreach (var f in src.EnumerateFiles())
                {
                    if (!diff.ContainsFile(f.Name))
                    {
                        continue;
                    }
                    using var baseStream   = f.OpenReadStream();
                    using var targetStream = diff.OpenFile(f.Name).OpenReadStream();
                    using var outStream    = dst.OpenFile(f.Name).OpenStream();
                    using var decoder      = new VcEncoder(baseStream, targetStream, outStream,
                                                           rollingHash: rollingHash, blockSize: 32);
                    VCDiffResult result = await decoder.EncodeAsync();

                    if (result != VCDiffResult.SUCCESS)
                    {
                        throw new IOException($"Failed to decode delta for {f.Name}");
                    }
                }

                var children = from targetDir in diff.EnumerateDirectories()
                               where src.ContainsDirectory(targetDir.Name)
                               select(dst, src.OpenDirectory(targetDir.Name), targetDir);

                foreach (var childDirectory in children)
                {
                    dirsToProcess.Enqueue(childDirectory);
                }
            }

            this.ProfileRoot.OpenFile("latest").WriteAllText(saveName, Encoding.UTF8);
            return(this.GetSave(saveDirectory) !);
        }