Skip to content

Commit

Permalink
Add first missing positive integer
Browse files Browse the repository at this point in the history
  • Loading branch information
oboukli committed Aug 1, 2023
1 parent b434e60 commit 80b7670
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand All @@ -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"
)
Expand Down
44 changes: 44 additions & 0 deletions benchmark/first_missing_positive_benchmark.cpp
Original file line number Diff line number Diff line change
@@ -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 <catch2/catch_test_macros.hpp>

#include <nanobench.h>

#include <nameof.hpp>

#include "forfun/first_missing_positive.hpp"

TEST_CASE("forfun::first_missing_positive benchmarking") {
ankerl::nanobench::Bench()

.title("Lowest missing positive integer")

.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);
})

;
}
78 changes: 78 additions & 0 deletions include/forfun/first_missing_positive.hpp
Original file line number Diff line number Diff line change
@@ -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 <algorithm>
#include <cstddef>
#include <vector>

namespace forfun::first_missing_positive {

namespace {

template <typename RandomIt>
constexpr 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]] constexpr int lowest_missing(std::vector<int> 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<std::size_t>(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_
7 changes: 7 additions & 0 deletions src/first_missing_positive.cpp
Original file line number Diff line number Diff line change
@@ -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"
63 changes: 63 additions & 0 deletions test/first_missing_positive_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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 <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>

#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);
}
}

0 comments on commit 80b7670

Please sign in to comment.