/* * Copyright (c) 2023, Martin Janiczek * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include namespace Test { namespace Randomized { struct ZeroChunk { Chunk chunk; }; struct SortChunk { Chunk chunk; }; struct DeleteChunkAndMaybeDecPrevious { Chunk chunk; }; struct MinimizeChoice { size_t index; }; struct SwapChunkWithNeighbour { Chunk chunk; Chunk const neighbour() { return Chunk { chunk.size, chunk.index + chunk.size }; } }; struct RedistributeChoicesAndMaybeInc { size_t left_index; size_t right_index; }; using CommandVariant = Variant< ZeroChunk, SortChunk, DeleteChunkAndMaybeDecPrevious, MinimizeChoice, SwapChunkWithNeighbour, RedistributeChoicesAndMaybeInc>; enum class AllowSizeOneChunks { Yes, No, }; class ShrinkCommand { public: explicit ShrinkCommand(CommandVariant const& command) : m_command(command) { } static Vector for_run(RandomRun const& run) { size_t run_size = run.size(); Vector all; // Sorted roughly in the order of effectiveness. Deleting chunks is better // than minimizing them. all.extend(deletion_commands(run_size)); all.extend(zero_commands(run_size)); all.extend(sort_commands(run_size)); all.extend(swap_chunk_commands(run_size)); all.extend(minimize_commands(run_size)); all.extend(redistribute_commands(run_size)); return all; } bool has_a_chance(RandomRun const& run) const { return m_command.visit( [&](ZeroChunk c) { return run.contains_chunk(c.chunk); }, [&](SortChunk c) { return run.contains_chunk(c.chunk); }, [&](DeleteChunkAndMaybeDecPrevious c) { return run.contains_chunk(c.chunk); }, [&](MinimizeChoice c) { return run.size() > c.index; }, [&](RedistributeChoicesAndMaybeInc c) { return run.size() > c.right_index; }, [&](SwapChunkWithNeighbour c) { return run.contains_chunk(c.neighbour()); }); } ErrorOr to_string() { return m_command.visit( [](ZeroChunk c) { return String::formatted("ZeroChunk({})", c.chunk); }, [](SortChunk c) { return String::formatted("SortChunk({})", c.chunk); }, [](DeleteChunkAndMaybeDecPrevious c) { return String::formatted("DeleteChunkAndMaybeDecPrevious({})", c.chunk); }, [](MinimizeChoice c) { return String::formatted("MinimizeChoice(i={})", c.index); }, [](RedistributeChoicesAndMaybeInc c) { return String::formatted("RedistributeChoicesAndMaybeInc(left={},right={})", c.left_index, c.right_index); }, [](SwapChunkWithNeighbour c) { return String::formatted("SwapChunkWithNeighbour({})", c.chunk); }); } template auto visit(Fs&&... callbacks) { return m_command.visit(forward(callbacks)...); } private: CommandVariant m_command; // Will generate ShrinkCommands for all chunks of sizes 1,2,3,4,8 in bounds of the // given RandomRun size. // // They will be given in a reverse order (largest chunks first), to maximize our // chances of saving work (minimizing the RandomRun faster). // // chunkCommands(10, false, [](Chunk c){ return SortChunk(c); }) // --> // [ // Chunks of size 8 // SortChunk { chunk_size = 8, start_index = 0 }, // [XXXXXXXX..] // SortChunk { chunk_size = 8, start_index = 1 }, // [.XXXXXXXX.] // SortChunk { chunk_size = 8, start_index = 2 }, // [..XXXXXXXX] // // // Chunks of size 4 // SortChunk { chunk_size = 4, start_index = 0 }, // [XXXX......] // SortChunk { chunk_size = 4, start_index = 1 }, // [.XXXX.....] // // ... // SortChunk { chunk_size = 4, start_index = 5 }, // [.....XXXX.] // SortChunk { chunk_size = 4, start_index = 6 }, // [......XXXX] // // // Chunks of size 3 // SortChunk { chunk_size = 3, start_index = 0 }, // [XXX.......] // SortChunk { chunk_size = 3, start_index = 1 }, // [.XXX......] // // ... // SortChunk { chunk_size = 3, start_index = 6 }, // [......XXX.] // SortChunk { chunk_size = 3, start_index = 7 }, // [.......XXX] // // // Chunks of size 2 // SortChunk { chunk_size = 2, start_index = 0 }, // [XX........] // SortChunk { chunk_size = 2, start_index = 1 }, // [.XX.......] // // ... // SortChunk { chunk_size = 2, start_index = 7 }, // [.......XX.] // SortChunk { chunk_size = 2, start_index = 8 }, // [........XX] // ] template static Vector chunk_commands(size_t run_size, AllowSizeOneChunks allow_chunks_size1, FN chunk_to_command) { Vector sizes = { 8, 4, 3, 2 }; switch (allow_chunks_size1) { case AllowSizeOneChunks::Yes: sizes.append(1); break; case AllowSizeOneChunks::No: break; } Vector commands; for (u8 chunk_size : sizes) { if (chunk_size > run_size) continue; for (size_t i = 0; i < run_size - chunk_size + 1; ++i) { ShrinkCommand command = chunk_to_command(Chunk { chunk_size, i }); commands.append(command); } } return commands; } static Vector deletion_commands(size_t run_size) { return chunk_commands( run_size, AllowSizeOneChunks::Yes, [](Chunk c) { return ShrinkCommand(DeleteChunkAndMaybeDecPrevious { c }); }); } static Vector minimize_commands(size_t run_size) { Vector commands; for (size_t i = 0; i < run_size; ++i) { ShrinkCommand command = ShrinkCommand(MinimizeChoice { i }); commands.append(command); } return commands; } static Vector redistribute_commands(size_t run_size) { Vector commands; for (size_t offset = 3; offset > 0; --offset) { if (offset >= run_size) continue; for (size_t i = 0; i < run_size - offset; ++i) { ShrinkCommand command = ShrinkCommand(RedistributeChoicesAndMaybeInc { i, i + offset }); commands.append(command); } } return commands; } static Vector sort_commands(size_t run_size) { return chunk_commands( run_size, AllowSizeOneChunks::No, // doesn't make sense for sorting [](Chunk c) { return ShrinkCommand(SortChunk { c }); }); } static Vector zero_commands(size_t run_size) { return chunk_commands( run_size, AllowSizeOneChunks::No, // already happens in binary search [](Chunk c) { return ShrinkCommand(ZeroChunk { c }); }); } static Vector swap_chunk_commands(size_t run_size) { return chunk_commands( // TODO: This is not optimal as the chunks near the end of // the RandomRun will hit OOB. // // This is because SwapChunkWithNeighbour doesn't just // touch the Chunk but also its right neighbour: // [_,_,X,X,X,Y,Y,Y,_] // // If the chunk is too far to the right, it would go OOB: // [_,_,_,_,X,X,X,Y,Y]Y // // For now, this will work though, there will just be a // bit of unnecessary work calling .has_a_chance() on // these chunks that are too far to the right. run_size, AllowSizeOneChunks::No, // already happens in "redistribute choice" [](Chunk c) { return ShrinkCommand(SwapChunkWithNeighbour { c }); }); } }; } // namespace Randomized } // namespace Test template<> struct AK::Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Test::Randomized::ShrinkCommand command) { return Formatter::format(builder, TRY(command.to_string())); } };