ShrinkCommand.h 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /*
  2. * Copyright (c) 2023, Martin Janiczek <martin@janiczek.cz>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #pragma once
  7. #include <LibTest/Randomized/Chunk.h>
  8. #include <LibTest/Randomized/RandomRun.h>
  9. #include <AK/String.h>
  10. #include <AK/Variant.h>
  11. #include <AK/Vector.h>
  12. namespace Test {
  13. namespace Randomized {
  14. struct ZeroChunk {
  15. Chunk chunk;
  16. };
  17. struct SortChunk {
  18. Chunk chunk;
  19. };
  20. struct DeleteChunkAndMaybeDecPrevious {
  21. Chunk chunk;
  22. };
  23. struct MinimizeChoice {
  24. size_t index;
  25. };
  26. struct SwapChunkWithNeighbour {
  27. Chunk chunk;
  28. Chunk const neighbour()
  29. {
  30. return Chunk { chunk.size, chunk.index + chunk.size };
  31. }
  32. };
  33. struct RedistributeChoicesAndMaybeInc {
  34. size_t left_index;
  35. size_t right_index;
  36. };
  37. using CommandVariant = Variant<
  38. ZeroChunk,
  39. SortChunk,
  40. DeleteChunkAndMaybeDecPrevious,
  41. MinimizeChoice,
  42. SwapChunkWithNeighbour,
  43. RedistributeChoicesAndMaybeInc>;
  44. enum class AllowSizeOneChunks {
  45. Yes,
  46. No,
  47. };
  48. class ShrinkCommand {
  49. public:
  50. explicit ShrinkCommand(CommandVariant const& command)
  51. : m_command(command)
  52. {
  53. }
  54. static Vector<ShrinkCommand> for_run(RandomRun const& run)
  55. {
  56. size_t run_size = run.size();
  57. Vector<ShrinkCommand> all;
  58. // Sorted roughly in the order of effectiveness. Deleting chunks is better
  59. // than minimizing them.
  60. all.extend(deletion_commands(run_size));
  61. all.extend(zero_commands(run_size));
  62. all.extend(sort_commands(run_size));
  63. all.extend(swap_chunk_commands(run_size));
  64. all.extend(minimize_commands(run_size));
  65. all.extend(redistribute_commands(run_size));
  66. return all;
  67. }
  68. bool has_a_chance(RandomRun const& run) const
  69. {
  70. return m_command.visit(
  71. [&](ZeroChunk c) { return run.contains_chunk(c.chunk); },
  72. [&](SortChunk c) { return run.contains_chunk(c.chunk); },
  73. [&](DeleteChunkAndMaybeDecPrevious c) { return run.contains_chunk(c.chunk); },
  74. [&](MinimizeChoice c) { return run.size() > c.index; },
  75. [&](RedistributeChoicesAndMaybeInc c) { return run.size() > c.right_index; },
  76. [&](SwapChunkWithNeighbour c) { return run.contains_chunk(c.neighbour()); });
  77. }
  78. ErrorOr<String> to_string()
  79. {
  80. return m_command.visit(
  81. [](ZeroChunk c) { return String::formatted("ZeroChunk({})", c.chunk); },
  82. [](SortChunk c) { return String::formatted("SortChunk({})", c.chunk); },
  83. [](DeleteChunkAndMaybeDecPrevious c) { return String::formatted("DeleteChunkAndMaybeDecPrevious({})", c.chunk); },
  84. [](MinimizeChoice c) { return String::formatted("MinimizeChoice(i={})", c.index); },
  85. [](RedistributeChoicesAndMaybeInc c) { return String::formatted("RedistributeChoicesAndMaybeInc(left={},right={})", c.left_index, c.right_index); },
  86. [](SwapChunkWithNeighbour c) { return String::formatted("SwapChunkWithNeighbour({})", c.chunk); });
  87. }
  88. template<typename... Fs>
  89. auto visit(Fs&&... callbacks)
  90. {
  91. return m_command.visit(forward<Fs>(callbacks)...);
  92. }
  93. private:
  94. CommandVariant m_command;
  95. // Will generate ShrinkCommands for all chunks of sizes 1,2,3,4,8 in bounds of the
  96. // given RandomRun size.
  97. //
  98. // They will be given in a reverse order (largest chunks first), to maximize our
  99. // chances of saving work (minimizing the RandomRun faster).
  100. //
  101. // chunkCommands(10, false, [](Chunk c){ return SortChunk(c); })
  102. // -->
  103. // [ // Chunks of size 8
  104. // SortChunk { chunk_size = 8, start_index = 0 }, // [XXXXXXXX..]
  105. // SortChunk { chunk_size = 8, start_index = 1 }, // [.XXXXXXXX.]
  106. // SortChunk { chunk_size = 8, start_index = 2 }, // [..XXXXXXXX]
  107. //
  108. // // Chunks of size 4
  109. // SortChunk { chunk_size = 4, start_index = 0 }, // [XXXX......]
  110. // SortChunk { chunk_size = 4, start_index = 1 }, // [.XXXX.....]
  111. // // ...
  112. // SortChunk { chunk_size = 4, start_index = 5 }, // [.....XXXX.]
  113. // SortChunk { chunk_size = 4, start_index = 6 }, // [......XXXX]
  114. //
  115. // // Chunks of size 3
  116. // SortChunk { chunk_size = 3, start_index = 0 }, // [XXX.......]
  117. // SortChunk { chunk_size = 3, start_index = 1 }, // [.XXX......]
  118. // // ...
  119. // SortChunk { chunk_size = 3, start_index = 6 }, // [......XXX.]
  120. // SortChunk { chunk_size = 3, start_index = 7 }, // [.......XXX]
  121. //
  122. // // Chunks of size 2
  123. // SortChunk { chunk_size = 2, start_index = 0 }, // [XX........]
  124. // SortChunk { chunk_size = 2, start_index = 1 }, // [.XX.......]
  125. // // ...
  126. // SortChunk { chunk_size = 2, start_index = 7 }, // [.......XX.]
  127. // SortChunk { chunk_size = 2, start_index = 8 }, // [........XX]
  128. // ]
  129. template<typename FN>
  130. static Vector<ShrinkCommand> chunk_commands(size_t run_size, AllowSizeOneChunks allow_chunks_size1, FN chunk_to_command)
  131. {
  132. Vector<u8> sizes = { 8, 4, 3, 2 };
  133. switch (allow_chunks_size1) {
  134. case AllowSizeOneChunks::Yes:
  135. sizes.append(1);
  136. break;
  137. case AllowSizeOneChunks::No:
  138. break;
  139. }
  140. Vector<ShrinkCommand> commands;
  141. for (u8 chunk_size : sizes) {
  142. if (chunk_size > run_size)
  143. continue;
  144. for (size_t i = 0; i < run_size - chunk_size + 1; ++i) {
  145. ShrinkCommand command = chunk_to_command(Chunk { chunk_size, i });
  146. commands.append(command);
  147. }
  148. }
  149. return commands;
  150. }
  151. static Vector<ShrinkCommand> deletion_commands(size_t run_size)
  152. {
  153. return chunk_commands(
  154. run_size,
  155. AllowSizeOneChunks::Yes,
  156. [](Chunk c) { return ShrinkCommand(DeleteChunkAndMaybeDecPrevious { c }); });
  157. }
  158. static Vector<ShrinkCommand> minimize_commands(size_t run_size)
  159. {
  160. Vector<ShrinkCommand> commands;
  161. for (size_t i = 0; i < run_size; ++i) {
  162. ShrinkCommand command = ShrinkCommand(MinimizeChoice { i });
  163. commands.append(command);
  164. }
  165. return commands;
  166. }
  167. static Vector<ShrinkCommand> redistribute_commands(size_t run_size)
  168. {
  169. Vector<ShrinkCommand> commands;
  170. for (size_t offset = 3; offset > 0; --offset) {
  171. if (offset >= run_size)
  172. continue;
  173. for (size_t i = 0; i < run_size - offset; ++i) {
  174. ShrinkCommand command = ShrinkCommand(RedistributeChoicesAndMaybeInc { i, i + offset });
  175. commands.append(command);
  176. }
  177. }
  178. return commands;
  179. }
  180. static Vector<ShrinkCommand> sort_commands(size_t run_size)
  181. {
  182. return chunk_commands(
  183. run_size,
  184. AllowSizeOneChunks::No, // doesn't make sense for sorting
  185. [](Chunk c) { return ShrinkCommand(SortChunk { c }); });
  186. }
  187. static Vector<ShrinkCommand> zero_commands(size_t run_size)
  188. {
  189. return chunk_commands(
  190. run_size,
  191. AllowSizeOneChunks::No, // already happens in binary search
  192. [](Chunk c) { return ShrinkCommand(ZeroChunk { c }); });
  193. }
  194. static Vector<ShrinkCommand> swap_chunk_commands(size_t run_size)
  195. {
  196. return chunk_commands(
  197. // TODO: This is not optimal as the chunks near the end of
  198. // the RandomRun will hit OOB.
  199. //
  200. // This is because SwapChunkWithNeighbour doesn't just
  201. // touch the Chunk but also its right neighbour:
  202. // [_,_,X,X,X,Y,Y,Y,_]
  203. //
  204. // If the chunk is too far to the right, it would go OOB:
  205. // [_,_,_,_,X,X,X,Y,Y]Y
  206. //
  207. // For now, this will work though, there will just be a
  208. // bit of unnecessary work calling .has_a_chance() on
  209. // these chunks that are too far to the right.
  210. run_size,
  211. AllowSizeOneChunks::No, // already happens in "redistribute choice"
  212. [](Chunk c) { return ShrinkCommand(SwapChunkWithNeighbour { c }); });
  213. }
  214. };
  215. } // namespace Randomized
  216. } // namespace Test
  217. template<>
  218. struct AK::Formatter<Test::Randomized::ShrinkCommand> : Formatter<StringView> {
  219. ErrorOr<void> format(FormatBuilder& builder, Test::Randomized::ShrinkCommand command)
  220. {
  221. return Formatter<StringView>::format(builder, TRY(command.to_string()));
  222. }
  223. };