Compare commits

...

8 Commits

Author SHA1 Message Date
b3dba32504 20250619 finished. 2025-06-19 15:46:08 +08:00
f438304378 20250618 finished. 2025-06-18 14:06:08 +08:00
c0cd44f606 20250617 finished. 2025-06-17 12:41:55 +08:00
70d44b7e9e 20250616 finished. 2025-06-16 14:35:47 +08:00
fd548c7ba8 20250615 finished. 2025-06-15 21:44:53 +08:00
88959d6513 20250614 finished. 2025-06-14 19:45:50 +08:00
b70046b7da 20250613 finished. 2025-06-13 16:53:40 +08:00
52d9629a5c feat: develop the basic functions. 2025-06-13 16:53:25 +08:00
16 changed files with 632 additions and 35 deletions

View File

@@ -110,8 +110,8 @@ IndentGotoLabels: false
# #endif
# #endif
IndentPPDirectives: BeforeHash
# IndentAccessModifiers: true
IndentWidth: 4
AccessModifierOffset: -4
KeepEmptyLinesAtTheStartOfBlocks: false
MaxEmptyLinesToKeep: 3
NamespaceIndentation: None

View File

@@ -6,6 +6,9 @@ include_directories(include)
find_package(CURL REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(GTest REQUIRED)
add_subdirectory(src)
add_executable(leetcode-fetcher main.cpp
src/fetcher.cpp)

View File

@@ -59,25 +59,27 @@ struct CodeDefinition
struct ProblemContent
{
std::string title;
std::string title_slug;
std::string titleSlug;
std::string content;
std::vector<CodeDefinition> codeDefinitions;
int questionId;
ProblemContent(std::string title,
std::string title_slug,
std::string titleSlug,
std::string content,
std::vector<CodeDefinition> codeDefinitions,
int questionId)
: title(std::move(title))
, title_slug(std::move(title_slug))
, titleSlug(std::move(titleSlug))
, content(std::move(content))
, codeDefinitions(std::move(codeDefinitions))
, questionId(questionId)
{
}
std::string formatTemplate(const std::string &templateContent) const;
[[nodiscard]] std::string formatTemplate(const std::string &templateContent) const;
[[nodiscard]] std::string formatFilename() const;
};
struct Fetcher
@@ -104,6 +106,8 @@ private:
[[nodiscard]] std::vector<LeetCodeProblem> getProblems() const;
[[nodiscard]] std::unique_ptr<ProblemContent> fetchProblemContent(const LeetCodeProblem &problem) const;
/// The callback function used by curl to write the http response content into a string.
static size_t httpWriteCallback(void *contents, const size_t size, size_t bufferLength, void *userData)
{
@@ -121,6 +125,8 @@ private:
const LeetCodeProblem &problem);
static std::string readTemplateFile();
static bool validateExistedProblem(const ProblemContent &problem);
};
#endif //FETCHER_H

31
justfile Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env just --justfile
update:
git pull
build: update
#!/usr/bin/env bash
set -euxo pipefail
mkdir -p cmake-build-debug-clang
cd cmake-build-debug-clang
cmake .. -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
ninja
fmt:
clang-format -i src/problems/*.cpp
test: fmt build
./cmake-build-debug-clang/src/problem-tests
commit: test
#!/usr/bin/env bash
set -euxo pipefail
time=$(date "+%Y%m%d")
message="$time finished."
git add -A
git commit -m "$message"
git push
pull id: build
./cmake-build-debug-clang/leetcode-fetcher {{ id }}

View File

@@ -6,8 +6,7 @@ int main(int argc, char **argv)
if (argc != 2)
{
std::cout << "The fetcher expect the program id.";
return -1;
throw std::invalid_argument("The fetcher expect the program id.");
}
fetcher.fetchProblem(argv[1]);

7
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,7 @@
enable_testing()
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/problems PROBLEMS_SRC)
add_executable(problem-tests ${PROBLEMS_SRC})
target_link_libraries(problem-tests GTest::gtest_main)

View File

@@ -49,9 +49,40 @@ std::vector<LeetCodeProblem> Fetcher::getProblems() const
return problems;
}
std::string replaceString(const std::string &original,
const std::string_view old_sub,
const std::string_view new_sub)
std::unique_ptr<ProblemContent> Fetcher::fetchProblemContent(const LeetCodeProblem &problem) const
{
curl_easy_setopt(client.get(), CURLOPT_URL, kGraphQlUrl.c_str());
curl_easy_setopt(client.get(), CURLOPT_POST, 1L);
curl_slist *headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(client.get(), CURLOPT_HTTPHEADER, headers);
std::string requestBody = formatQueryJson(problem.questionTitleSlug).dump();
curl_easy_setopt(client.get(), CURLOPT_POSTFIELDS, requestBody.c_str());
curl_easy_setopt(client.get(), CURLOPT_POSTFIELDSIZE, requestBody.size());
std::string responseBody;
curl_easy_setopt(client.get(), CURLOPT_WRITEFUNCTION, Fetcher::httpWriteCallback);
curl_easy_setopt(client.get(), CURLOPT_WRITEDATA, &responseBody);
CURLcode code = curl_easy_perform(client.get());
if (code != CURLE_OK)
{
throw std::runtime_error("Failed to fetch problem.");
}
const nlohmann::json jsonResponse = nlohmann::json::parse(responseBody);
return extractContentFromJson(jsonResponse, problem);
}
static std::string replaceString(const std::string &original,
const std::string_view old_sub,
const std::string_view new_sub)
{
if (old_sub.empty())
return original; // 防止空字符串导致死循环
@@ -108,10 +139,21 @@ std::string ProblemContent::formatTemplate(const std::string &templateContent) c
std::string result = replaceString(templateContent, "__PROBLEM_ID__", std::to_string(questionId));
result = replaceString(result, "__PROBLEM_TITLE__", title);
result = replaceString(result, "__PROBLEM_DEFAULT_CODE__", it->defaultCode);
std::string testCaseName = "P" + std::to_string(questionId);
result = replaceString(result, "__TEST_CASE_NAME__", testCaseName);
return result;
}
std::string ProblemContent::formatFilename() const
{
std::ostringstream stream("p");
stream << questionId;
stream << "-" << titleSlug << ".cpp";
return stream.str();
}
void Fetcher::fetchProblem(const std::string &idString) const
{
std::vector<LeetCodeProblem> problems = getProblems();
@@ -127,37 +169,21 @@ void Fetcher::fetchProblem(const std::string &idString) const
throw std::runtime_error("The target problem does not exist.");
}
curl_easy_setopt(client.get(), CURLOPT_URL, kGraphQlUrl.c_str());
curl_easy_setopt(client.get(), CURLOPT_POST, 1L);
std::unique_ptr<ProblemContent> problemContent = fetchProblemContent(*it);
const std::string templateFile = readTemplateFile();
std::string problemFileContent = problemContent->formatTemplate(templateFile);
std::string problemFilename = problemContent->formatFilename();
curl_slist *headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/json");
auto problemFile = std::ofstream("src/problems/" + problemFilename);
curl_easy_setopt(client.get(), CURLOPT_HTTPHEADER, headers);
std::string requestBody = formatQueryJson(it->questionTitleSlug).dump();
curl_easy_setopt(client.get(), CURLOPT_POSTFIELDS, requestBody.c_str());
curl_easy_setopt(client.get(), CURLOPT_POSTFIELDSIZE, requestBody.size());
std::string responseBody;
curl_easy_setopt(client.get(), CURLOPT_WRITEFUNCTION, Fetcher::httpWriteCallback);
curl_easy_setopt(client.get(), CURLOPT_WRITEDATA, &responseBody);
CURLcode code = curl_easy_perform(client.get());
if (code != CURLE_OK)
if (!problemFile.is_open())
{
throw std::runtime_error("Failed to fetch problem.");
throw std::runtime_error("Failed to open problem file.");
}
const nlohmann::json jsonResponse = nlohmann::json::parse(responseBody);
const std::unique_ptr<ProblemContent> problemContent = extractContentFromJson(jsonResponse, *it);
problemFile << problemFileContent;
std::string templateFile = readTemplateFile();
std::cout << problemContent->formatTemplate(templateFile) << std::endl;
problemFile.close();
}
nlohmann::json Fetcher::formatQueryJson(const std::string &title)
@@ -225,4 +251,26 @@ std::string Fetcher::readTemplateFile()
file.close();
return buffer.str();
}
bool Fetcher::validateExistedProblem(const ProblemContent &problem)
{
std::filesystem::path problemDirectory = "src/problems";
std::string defaultFilename = problem.formatFilename();
if (!std::filesystem::exists(problemDirectory))
{
throw std::runtime_error("The problem directory is not exitsed.");
}
return std::ranges::any_of(std::filesystem::directory_iterator(problemDirectory),
[&](const std::filesystem::directory_entry &entry)
{
if (!entry.is_regular_file())
{
return false;
}
return entry.path().filename() == defaultFilename;
});
}

View File

@@ -0,0 +1,40 @@
/**
* [1] Two Sum
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
class Solution
{
public:
vector<int> twoSum(vector<int> &nums, int target)
{
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); ++i)
{
if (const auto &it = map.find(target - nums[i]); it != map.end())
{
return {it->second, i};
}
map.insert({nums[i], i});
}
return {};
}
};
// submission codes endo
TEST(P1, Test1)
{
Solution s;
vector nums = {2, 7, 11, 15};
vector result = {0, 1};
ASSERT_EQ(s.twoSum(nums, 9), result);
}

View File

@@ -0,0 +1,94 @@
/**
* [1432] Max Difference You Can Get From Changing an Integer
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
class Solution
{
public:
int maxDiff(int num)
{
string numString = to_string(num);
// Select the first, not 9 number.
int targetPos = 0;
while (targetPos < numString.size() - 1)
{
if (numString[targetPos] != '9')
{
break;
}
targetPos += 1;
}
auto maxNumber = replaceDigit(numString, numString[targetPos], '9');
targetPos = 0;
char minimumChar = '1';
// If the first number is not 1, select the first number.
// If the first number is 1, select next, not zero number.
if (numString[targetPos] == '1')
{
targetPos = 1;
minimumChar = '0';
while (targetPos < numString.size())
{
if (numString[targetPos] == '1')
{
// Can not replace 1 when the first number is 1.
targetPos += 1;
continue;
}
if (numString[targetPos] != '0')
{
break;
}
targetPos += 1;
}
}
int minNumber = num;
if (targetPos != numString.size())
{
minNumber = replaceDigit(numString, numString[targetPos], minimumChar);
}
return maxNumber - minNumber;
}
static auto replaceDigit(const string &num, char source, char target) -> int
{
int result = 0;
for (const char i : num)
{
if (i == source)
{
result = result * 10 + target - '0';
}
else
{
result = result * 10 + i - '0';
}
}
return result;
}
};
// submission codes end
TEST(P1432, Test1)
{
ASSERT_EQ(888, Solution().maxDiff(555));
ASSERT_EQ(8, Solution().maxDiff(9));
ASSERT_EQ(888, Solution().maxDiff(111));
ASSERT_EQ(80000, Solution().maxDiff(10000));
}

View File

@@ -0,0 +1,49 @@
/**
* [2016] Maximum Difference Between Increasing Elements
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
class Solution
{
public:
int maximumDifference(vector<int> &nums)
{
vector<int> heap;
heap.reserve(nums.size());
ranges::make_heap(heap, greater());
int result = -1;
for (int i = 1; i < nums.size(); ++i)
{
heap.push_back(nums[i - 1]);
ranges::push_heap(heap, greater());
if (heap[0] < nums[i])
{
result = max(result, nums[i] - heap[0]);
}
}
return result;
}
};
// submission codes end
TEST(P2016, Test1)
{
vector nums1 = {7, 1, 5, 4};
ASSERT_EQ(4, Solution().maximumDifference(nums1));
vector nums2 = {9, 4, 3, 2};
ASSERT_EQ(-1, Solution().maximumDifference(nums2));
vector nums3 = {1, 5, 2, 10};
ASSERT_EQ(9, Solution().maximumDifference(nums3));
}

View File

@@ -0,0 +1,50 @@
/**
* [2294] Partition Array Such That Maximum Difference Is K
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
class Solution
{
public:
int partitionArray(vector<int> &nums, int k)
{
ranges::sort(nums);
// At least to split into one segment.
int result = 1;
int pos = 1;
int minValue = nums[0];
while (pos < nums.size())
{
if (nums[pos] > minValue + k)
{
result += 1;
minValue = nums[pos];
}
else
{
pos += 1;
}
}
return result;
}
};
// submission codes end
TEST(P2294, Test1)
{
vector nums1 = {3, 6, 1, 2, 5};
ASSERT_EQ(2, Solution().partitionArray(nums1, 2));
vector nums2 = {1, 2, 3};
ASSERT_EQ(2, Solution().partitionArray(nums2, 1));
vector nums3 = {2, 2, 4, 5};
ASSERT_EQ(3, Solution().partitionArray(nums3, 0));
}

View File

@@ -0,0 +1,71 @@
/**
* [2566] Maximum Difference by Remapping a Digit
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
class Solution
{
public:
int minMaxDifference(int num)
{
const string numString = to_string(num);
// Select the first not 9 number when converting to maximum value.
int targetPos = 0;
while (targetPos < numString.size() - 1)
{
if (numString[targetPos] != '9')
{
break;
}
targetPos += 1;
}
int maxNum = 0;
char targetChar = numString[targetPos];
for (const auto c : numString)
{
if (c == targetChar)
{
maxNum = maxNum * 10 + 9;
}
else
{
maxNum = maxNum * 10 + c - '0';
}
}
// The minimum value is replacing the first number into zero.
targetChar = numString[0];
int minNum = 0;
for (const auto c : numString)
{
if (c == targetChar)
{
minNum = minNum * 10;
}
else
{
minNum = minNum * 10 + c - '0';
}
}
return maxNum - minNum;
}
};
// submission codes end
TEST(P2566, Test1)
{
ASSERT_EQ(99009, Solution().minMaxDifference(11891));
ASSERT_EQ(99, Solution().minMaxDifference(90));
}

View File

@@ -0,0 +1,60 @@
/**
* [2616] Minimize the Maximum Difference of Pairs
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
class Solution
{
public:
int minimizeMax(vector<int> &nums, int p)
{
ranges::sort(nums);
auto check = [&](int value) -> bool
{
int count = 0;
for (int i = 0; i < nums.size() - 1; ++i)
{
if (nums[i + 1] - nums[i] <= value)
{
count += 1;
i += 1;
}
}
return count >= p;
};
int left = 0, right = nums.back() - nums[0];
while (left < right)
{
int middle = (left + right) >> 1;
if (check(middle))
{
right = middle;
}
else
{
left = middle + 1;
}
}
return left;
}
};
// submission codes end.
TEST(P2616, Test1)
{
vector nums = {10, 1, 2, 7, 1, 3};
Solution s;
ASSERT_EQ(s.minimizeMax(nums, 2), 1);
}

View File

@@ -0,0 +1,51 @@
/**
* [2966] Divide Array Into Arrays With Max Difference
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
class Solution
{
public:
vector<vector<int>> divideArray(vector<int> &nums, int k)
{
ranges::sort(nums);
vector<vector<int>> result;
bool flag = true;
result.reserve(nums.size() / 3);
for (int i = 0; i < nums.size(); i += 3)
{
if (nums[i + 2] - nums[i + 1] > k || nums[i + 1] - nums[i] > k || nums[i + 2] - nums[i] > k)
{
flag = false;
break;
}
vector array = {nums[i], nums[i + 1], nums[i + 2]};
result.push_back(move(array));
}
if (flag)
{
return result;
}
return {};
}
};
// submission codes end
TEST(P2966, Test1)
{
vector nums = {1, 3, 4, 8, 7, 9, 3, 5, 1};
vector<vector<int>> result = {{1, 1, 3}, {3, 4, 5}, {7, 8, 9}};
ASSERT_EQ(result, Solution().divideArray(nums, 2));
}

View File

@@ -0,0 +1,80 @@
/**
* [3405] Count the Number of Arrays with K Matching Adjacent Elements
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
constexpr long long MOD = 1e9 + 7;
constexpr long long UPPER_BOUND = 1e5;
static long long fact[UPPER_BOUND];
static long long inverseFact[UPPER_BOUND];
class Solution
{
static long long quickPower(long long x, int n)
{
long long result = 1;
while (n > 0)
{
if ((n & 1) == 1)
{
result = result * x % MOD;
}
x = x * x % MOD;
n >>= 1;
}
return result;
}
static long long combine(int n, int m)
{
return fact[n] * inverseFact[m] % MOD * inverseFact[n - m] % MOD;
}
static void init()
{
if (fact[0] != 0)
{
return;
}
fact[0] = 1;
for (int i = 1; i < UPPER_BOUND; ++i)
{
fact[i] = fact[i - 1] * i % MOD;
}
// Modular Multiplicative Inverse is calculated by the quick power.
inverseFact[UPPER_BOUND - 1] = quickPower(fact[UPPER_BOUND - 1], MOD - 2);
for (int i = UPPER_BOUND - 1; i > 0; --i)
{
inverseFact[i - 1] = inverseFact[i] * i % MOD;
}
}
public:
int countGoodArrays(int n, int m, int k)
{
init();
const long long result = combine(n - 1, k) * m % MOD * quickPower(m - 1, n - k - 1) % MOD;
return result;
}
};
// submission codes end
TEST(P3405, Test1)
{
Solution s;
ASSERT_EQ(4, s.countGoodArrays(3, 2, 1));
ASSERT_EQ(6, s.countGoodArrays(4, 2, 2));
ASSERT_EQ(2, s.countGoodArrays(5, 2, 0));
}

View File

@@ -1,9 +1,17 @@
/**
* [__PROBLEM_ID__] __PROBLEM_TITLE__
*/
#include <bits/stdc++.h>
#include <gtest/gtest.h>
using namespace std;
// submission codes start here
__PROBLEM_DEFAULT_CODE__
// submission codes end
TEST(__TEST_CASE_NAME__, Test1)
{
}