diff --git a/CMakeLists.txt b/CMakeLists.txt index cd652d8..d29585a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,12 +27,14 @@ project( add_library( forfun SHARED + "include/forfun/first_missing_positive.hpp" "include/forfun/fizzbuzz.hpp" "include/forfun/lru_cache.hpp" "include/forfun/palindrome.hpp" "include/forfun/palindromic_number.hpp" "include/forfun/project_euler/p0001_multiples_of_3_or_5.hpp" "include/forfun/sonar.hpp" + "src/first_missing_positive.cpp" "src/fizzbuzz.cpp" "src/lru_cache.cpp" "src/palindrome.cpp" @@ -102,6 +104,7 @@ add_executable( find_package(Catch2 3 REQUIRED) add_executable( tests + "test/first_missing_positive_test.cpp" "test/fizzbuzz_test.cpp" ) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain) @@ -112,6 +115,7 @@ find_package(nanobench CONFIG REQUIRED) add_executable( benchmark "benchmark/benchmark.cpp" + "benchmark/first_missing_positive_benchmark.cpp" "benchmark/lru_cache_benchmark.cpp" "benchmark/palindrome_benchmark.cpp" ) diff --git a/benchmark/first_missing_positive_benchmark.cpp b/benchmark/first_missing_positive_benchmark.cpp new file mode 100644 index 0000000..d4d1540 --- /dev/null +++ b/benchmark/first_missing_positive_benchmark.cpp @@ -0,0 +1,44 @@ +// Copyright (c) Omar Boukli-Hacene. All rights reserved. +// Distributed under an MIT-style license that can be +// found in the LICENSE file. + +// SPDX-License-Identifier: MIT + +#include + +#include + +#include + +#include "forfun/first_missing_positive.hpp" + +TEST_CASE("forfun::first_missing_positive benchmarking") { + ankerl::nanobench::Bench() + + .title("Arbitrary case") + + .run( + NAMEOF_RAW(forfun::first_missing_positive::lowest_missing).c_str(), + []() { + auto r{forfun::first_missing_positive::lowest_missing({ + // clang-format off + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 9, 8, 7, 6, 5, 4, 3, 2, 1, -1, + 0, 0, 0, 0, 0, 0, 0, 0, + // clang-format on + })}; + ankerl::nanobench::doNotOptimizeAway(r); + }) + + ; +} diff --git a/include/forfun/first_missing_positive.hpp b/include/forfun/first_missing_positive.hpp new file mode 100644 index 0000000..a216c94 --- /dev/null +++ b/include/forfun/first_missing_positive.hpp @@ -0,0 +1,78 @@ +// Copyright (c) Omar Boukli-Hacene. All rights reserved. +// Distributed under an MIT-style license that can be +// found in the LICENSE file. + +// SPDX-License-Identifier: MIT + +/// Problem source: +/// https://simontoth.substack.com/p/daily-bite-of-c-smallest-missing +/// https://compiler-explorer.com/z/G7r4rebhd +/// +/// Given a list of integers, determine the smallest missing +/// positive integer. +/// +/// Importantly your solution must run in O(n) time, and while +/// you are permitted to modify the input, you can only use +/// constant additional memory. + +#ifndef FIRST_MISSING_POSITIVE_HPP_ +#define FIRST_MISSING_POSITIVE_HPP_ + +#include +#include +#include + +namespace forfun::first_missing_positive { + +namespace { + +template +void quasi_sort(RandomIt first, RandomIt const src_iterator) noexcept { + auto const n{*src_iterator}; + RandomIt const dest_iterator{first + std::max(0, n - 1)}; + + if (n < 1) { + return; + } + + if (auto const tmp{*dest_iterator}; tmp != n) { + *dest_iterator = n; + *src_iterator = tmp; + + return quasi_sort(first, src_iterator); + } +} + +} // namespace + +[[nodiscard]] int lowest_missing(std::vector numbers) noexcept { + auto max{numbers.size()}; + auto begin{numbers.begin()}; + auto end{numbers.end()}; + + for (auto it{begin}; it != end; ++it) { + int const current{*it}; + if (current < 1) { + --max; + } else if (static_cast(current) > max) { + --max; + *it = 0; + } else { + quasi_sort(begin, it); + } + } + + int min_num{1}; + auto const endIt = begin + max; + for (auto it{begin}; it != endIt; ++it) { + if (*it == min_num) { + ++min_num; + } + } + + return min_num; +} + +} // namespace forfun::first_missing_positive + +#endif // FIRST_MISSING_POSITIVE_HPP_ diff --git a/src/first_missing_positive.cpp b/src/first_missing_positive.cpp new file mode 100644 index 0000000..3f8ac4a --- /dev/null +++ b/src/first_missing_positive.cpp @@ -0,0 +1,7 @@ +// Copyright (c) Omar Boukli-Hacene. All rights reserved. +// Distributed under an MIT-style license that can be +// found in the LICENSE file. + +// SPDX-License-Identifier: MIT + +#include "forfun/first_missing_positive.hpp" diff --git a/test/first_missing_positive_test.cpp b/test/first_missing_positive_test.cpp new file mode 100644 index 0000000..db259f1 --- /dev/null +++ b/test/first_missing_positive_test.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Omar Boukli-Hacene. All rights reserved. +// Distributed under an MIT-style license that can be +// found in the LICENSE file. + +// SPDX-License-Identifier: MIT + +#include +#include + +#include "forfun/first_missing_positive.hpp" + +TEST_CASE("first_missing_positive") { + using forfun::first_missing_positive::lowest_missing; + + SECTION("Basic") { + REQUIRE(lowest_missing({}) == 1); + REQUIRE(lowest_missing({0}) == 1); + REQUIRE(lowest_missing({1}) == 2); + REQUIRE(lowest_missing({2}) == 1); + } + + SECTION("Two elements") { + REQUIRE(lowest_missing({-1, 0}) == 1); + REQUIRE(lowest_missing({0, 1}) == 2); + REQUIRE(lowest_missing({1, 0}) == 2); + REQUIRE(lowest_missing({1, 2}) == 3); + REQUIRE(lowest_missing({2, 1}) == 3); + } + + SECTION("Duplicates") { + REQUIRE(lowest_missing({0, 0, 0, 0}) == 1); + REQUIRE(lowest_missing({1, 1, 1, 1}) == 2); + REQUIRE(lowest_missing({6, 6, 6, 6, 6}) == 1); + REQUIRE(lowest_missing({6, 6, 6, 6, 1}) == 2); + } + + SECTION("Negative numbers") { + REQUIRE(lowest_missing({-2}) == 1); + REQUIRE(lowest_missing({-1}) == 1); + REQUIRE(lowest_missing({-1, -1, -1, -1}) == 1); + REQUIRE(lowest_missing({-6, -5, -4, 1, 2}) == 3); + } + + SECTION("Misc") { + REQUIRE(lowest_missing({1, 2, 3, 4, 5, 6, 7, 8, 9}) == 10); + REQUIRE(lowest_missing({9, 8, 7, 6, 5, 4, 3, 2, 1}) == 10); + REQUIRE(lowest_missing({1, 2, 3, 4, 5, 4, 3, 2, 1}) == 6); + REQUIRE(lowest_missing({9, 7, 5, 4, 3, 2, 1}) == 6); + REQUIRE(lowest_missing({8, 20, 10, 5, 4, 3, 2, 1}) == 6); + REQUIRE(lowest_missing({7, 11, 6, 6, -1, 4, 1, 2}) == 3); + } + + SECTION("Test cases copied from Simon Toth)") { + REQUIRE(lowest_missing({9, 8, 7, 4, 3, 2, 1}) == 5); + REQUIRE(lowest_missing({1, 1}) == 2); + } + + SECTION("Test cases copied from LeetCode") { + REQUIRE(lowest_missing({1, 2, 0}) == 3); + REQUIRE(lowest_missing({3, 4, -1, 1}) == 2); + REQUIRE(lowest_missing({7, 8, 9, 11, 12}) == 1); + } + + return 0; +}