123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- /*
- * Copyright (c) 2023, Martin Janiczek <martin@janiczek.cz>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
- #pragma once
- #include <LibTest/Randomized/Chunk.h>
- #include <LibTest/Randomized/RandomRun.h>
- #include <AK/String.h>
- #include <AK/Variant.h>
- #include <AK/Vector.h>
- 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<ShrinkCommand> for_run(RandomRun const& run)
- {
- size_t run_size = run.size();
- Vector<ShrinkCommand> 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<String> 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<typename... Fs>
- auto visit(Fs&&... callbacks)
- {
- return m_command.visit(forward<Fs>(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<typename FN>
- static Vector<ShrinkCommand> chunk_commands(size_t run_size, AllowSizeOneChunks allow_chunks_size1, FN chunk_to_command)
- {
- Vector<u8> sizes = { 8, 4, 3, 2 };
- switch (allow_chunks_size1) {
- case AllowSizeOneChunks::Yes:
- sizes.append(1);
- break;
- case AllowSizeOneChunks::No:
- break;
- }
- Vector<ShrinkCommand> 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<ShrinkCommand> deletion_commands(size_t run_size)
- {
- return chunk_commands(
- run_size,
- AllowSizeOneChunks::Yes,
- [](Chunk c) { return ShrinkCommand(DeleteChunkAndMaybeDecPrevious { c }); });
- }
- static Vector<ShrinkCommand> minimize_commands(size_t run_size)
- {
- Vector<ShrinkCommand> commands;
- for (size_t i = 0; i < run_size; ++i) {
- ShrinkCommand command = ShrinkCommand(MinimizeChoice { i });
- commands.append(command);
- }
- return commands;
- }
- static Vector<ShrinkCommand> redistribute_commands(size_t run_size)
- {
- Vector<ShrinkCommand> 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<ShrinkCommand> 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<ShrinkCommand> 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<ShrinkCommand> 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<Test::Randomized::ShrinkCommand> : Formatter<StringView> {
- ErrorOr<void> format(FormatBuilder& builder, Test::Randomized::ShrinkCommand command)
- {
- return Formatter<StringView>::format(builder, TRY(command.to_string()));
- }
- };
|