commit 9ddcad2f0896a870c21dfb07014280cb36be5c64 Author: jackfiled Date: Thu Jun 12 22:23:50 2025 +0800 init repo. diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f669e03 --- /dev/null +++ b/.clang-format @@ -0,0 +1,163 @@ +# public, protected, private 修饰符对齐 +# AccessModifierOffset: 2 + +# 长函数调用时,参数对齐, 括号形式 +# someLongFunction( +# argument1, argument2 +# ) +# +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: None + +# 连续的赋值语句对齐 +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: true + PadOperators: true + +#AlignConsecutiveBitFields: false +AlignConsecutiveDeclarations: None +AlignConsecutiveMacros: None +AlignEscapedNewlines: Right + +# Align +# x = aaaaaaaa + +# bbbbbbbb +# +# when BreakBeforeBinaryOperators is set +# +# x = aaaaaaaa + +# bbbbbbbb +# +# AlignAfterOperator +# x = aaaaaaaa +# + bbbbbbbb +#AlignOperands: AlignAfterOperator + +AlignTrailingComments: + Kind: Always + OverEmptyLines: 2 + + # true: + # callFunction( + # a, b, c, d); + # + # false: + # callFunction(a, + # b, + # c, + # d); +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: true +#AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLambdasOnASingleLine: Empty +AllowShortLoopsOnASingleLine: false + +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false + +# 在template声明时,是否换行 +# template +# T foo() { +# } +# template +# T foo(int aaaaaaaaaaaaaaaaaaaaa, +# int bbbbbbbbbbbbbbbbbbbbb) { +# } +AlwaysBreakTemplateDeclarations: Yes + +BinPackArguments: false +BinPackParameters: false +#BitFieldColonSpacing: Both +BreakBeforeBraces: "Allman" + +# true: +# veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongDescription +# ? firstValue +# : SecondValueVeryVeryVeryVeryLong; +# +# false: +# veryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLongDescription ? +# firstValue : +# SecondValueVeryVeryVeryVeryLong; +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakStringLiterals: false + +ColumnLimit: 0 # 0: no limit +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +Cpp11BracedListStyle: true + + + +FixNamespaceComments: true # 加上丢失的namespace注释 +IncludeBlocks: Preserve +#IndentCaseBlocks: false +IndentCaseLabels: true +IndentGotoLabels: false + +# #if FOO +# #if BAR +# #include +# #endif +# #endif +IndentPPDirectives: BeforeHash +# IndentAccessModifiers: true +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 3 +NamespaceIndentation: None + +# Left: +# int* a; +# Right: +# int *a; +# Middle: +# int * a; +PointerAlignment: Right + +# QualifierOrder + +ReferenceAlignment: Right + +# 按照列数限制, 将注释进行换行 +ReflowComments: false + +SortIncludes: CaseSensitive +SortUsingDeclarations: true + +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCaseColon: false +SpaceBeforeCtorInitializerColon: false +SpaceBeforeInheritanceColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 4 +SpacesInAngles: Leave +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +# Constructor +ConstructorInitializerAllOnOneLineOrOnePerLine: true + +Standard: c++20 +TabWidth: 4 +UseTab: Never \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc9c276 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +build/ +cmake-build-*/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e2f9fe9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.20) +project(leetcode-cpp) + +set(CMAKE_CXX_STANDARD 20) +include_directories(include) + +find_package(CURL REQUIRED) +find_package(nlohmann_json REQUIRED) + +add_executable(leetcode-fetcher main.cpp + src/fetcher.cpp) + +target_link_libraries(leetcode-fetcher PRIVATE CURL::libcurl) +target_link_libraries(leetcode-fetcher PRIVATE nlohmann_json::nlohmann_json) diff --git a/include/fetcher.h b/include/fetcher.h new file mode 100644 index 0000000..d6d42b9 --- /dev/null +++ b/include/fetcher.h @@ -0,0 +1,126 @@ +// +// Created by ricardo on 12/06/25. +// + +#ifndef FETCHER_H +#define FETCHER_H +#include +#include +#include +#include + +struct CurlDeleter +{ + void operator()(CURL *curl) const + { + if (curl != nullptr) + { + curl_easy_cleanup(curl); + } + } +}; + +struct LeetCodeProblem +{ + LeetCodeProblem(bool paidOnly, + std::string frontendQuestionId, + int questionId, + std::string questionTitle, + std::string questionTitleSlug) + : paidOnly(paidOnly) + , frontendQuestionId(std::move(frontendQuestionId)) + , questionId(questionId) + , questionTitle(std::move(questionTitle)) + , questionTitleSlug(std::move(questionTitleSlug)) + { + } + + bool paidOnly; + std::string frontendQuestionId; + int questionId; + std::string questionTitle; + std::string questionTitleSlug; +}; + +struct CodeDefinition +{ + std::string value; + std::string text; + std::string defaultCode; + + CodeDefinition(std::string value, std::string text, std::string defaultCode) + : value(std::move(value)) + , text(std::move(text)) + , defaultCode(std::move(defaultCode)) + { + } +}; + +struct ProblemContent +{ + std::string title; + std::string title_slug; + std::string content; + std::vector codeDefinitions; + int questionId; + + ProblemContent(std::string title, + std::string title_slug, + std::string content, + std::vector codeDefinitions, + int questionId) + : title(std::move(title)) + , title_slug(std::move(title_slug)) + , content(std::move(content)) + , codeDefinitions(std::move(codeDefinitions)) + , questionId(questionId) + { + } + + std::string formatTemplate(const std::string &templateContent) const; +}; + +struct Fetcher +{ + Fetcher() + { + // Initialize the cURL global environment. + curl_global_init(CURL_GLOBAL_ALL); + client = std::unique_ptr(curl_easy_init()); + } + + ~Fetcher() + { + curl_global_cleanup(); + } + + void fetchProblem(const std::string &idString) const; + +private: + std::unique_ptr client; + + std::string kProblemsUrl = "https://leetcode.cn/api/problems/algorithms/"; + std::string kGraphQlUrl = "https://leetcode.cn/graphql"; + + [[nodiscard]] std::vector getProblems() 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) + { + auto string = static_cast(userData); + auto body = static_cast(contents); + string->append(body, size * bufferLength); + + return size * bufferLength; + } + + static nlohmann::json formatQueryJson(const std::string &title); + + static std::unique_ptr extractContentFromJson( + const nlohmann::json &json, + const LeetCodeProblem &problem); + + static std::string readTemplateFile(); +}; + +#endif //FETCHER_H \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..45a512f --- /dev/null +++ b/main.cpp @@ -0,0 +1,16 @@ +#include "fetcher.h" + +int main(int argc, char **argv) +{ + Fetcher fetcher; + + if (argc != 2) + { + std::cout << "The fetcher expect the program id."; + return -1; + } + + fetcher.fetchProblem(argv[1]); + + return 0; +} \ No newline at end of file diff --git a/src/fetcher.cpp b/src/fetcher.cpp new file mode 100644 index 0000000..5069883 --- /dev/null +++ b/src/fetcher.cpp @@ -0,0 +1,228 @@ +// +// Created by ricardo on 12/06/25. +// +#include +#include "fetcher.h" + +std::vector Fetcher::getProblems() const +{ + std::string body; + + curl_easy_setopt(client.get(), CURLOPT_URL, kProblemsUrl.c_str()); + curl_easy_setopt(client.get(), CURLOPT_WRITEFUNCTION, Fetcher::httpWriteCallback); + curl_easy_setopt(client.get(), CURLOPT_WRITEDATA, &body); + + const CURLcode code = curl_easy_perform(client.get()); + + if (code != CURLE_OK) + { + std::cout << "Failed to fetch problems." << std::endl; + return {}; + } + + std::vector problems; + + try + { + nlohmann::json jsonResponse = nlohmann::json::parse(body); + + for (const auto &item : jsonResponse["stat_status_pairs"]) + { + bool paidOnly = item["paid_only"]; + + const auto &stat_item = item["stat"]; + + std::string frontendQuestionId = stat_item["frontend_question_id"]; + int questionId = stat_item["question_id"]; + std::string questionTitle = stat_item["question__title"]; + std::string questionTitleSlug = stat_item["question__title_slug"]; + + problems.emplace_back(paidOnly, frontendQuestionId, questionId, questionTitle, questionTitleSlug); + } + } + catch (const nlohmann::json::parse_error &e) + { + std::cout << "JSON parse error: " << e.what() << std::endl; + std::cout << "Raw response: " << body << std::endl; + } + + return problems; +} + +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; // 防止空字符串导致死循环 + + std::string result; + size_t pos = 0; + size_t old_len = old_sub.size(); + size_t new_len = new_sub.size(); + + // 预计算总长度以减少内存分配 + size_t total_length = original.size(); + size_t count = 0; + size_t start = 0; + + while ((pos = original.find(old_sub, start)) != std::string::npos) + { + total_length += (new_len - old_len); // 更新总长度 + count++; + start = pos + old_len; // 下一次查找起始位置 + } + + result.reserve(total_length); // 预分配内存 + + start = 0; + while ((pos = original.find(old_sub, start)) != std::string::npos) + { + // 添加旧子串之前的部分 + result.append(original.data() + start, pos - start); + // 添加新子串 + result.append(new_sub.data(), new_sub.size()); + // 更新起始位置 + start = pos + old_len; + } + + // 添加剩余部分 + result.append(original.data() + start, original.size() - start); + + return result; +} + +std::string ProblemContent::formatTemplate(const std::string &templateContent) const +{ + const auto it = std::ranges::find_if(codeDefinitions, + [](const CodeDefinition &definition) + { + return definition.value == "cpp"; + }); + + if (it == codeDefinitions.end()) + { + throw std::runtime_error("The target problem has no C++ template."); + } + + 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); + + return result; +} + +void Fetcher::fetchProblem(const std::string &idString) const +{ + std::vector problems = getProblems(); + + const auto it = std::ranges::find_if(problems, + [idString](const LeetCodeProblem &problem) + { + return problem.frontendQuestionId == idString; + }); + + if (it == problems.end()) + { + 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); + + 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(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) + { + throw std::runtime_error("Failed to fetch problem."); + } + + const nlohmann::json jsonResponse = nlohmann::json::parse(responseBody); + const std::unique_ptr problemContent = extractContentFromJson(jsonResponse, *it); + + std::string templateFile = readTemplateFile(); + + std::cout << problemContent->formatTemplate(templateFile) << std::endl; +} + +nlohmann::json Fetcher::formatQueryJson(const std::string &title) +{ + nlohmann::json result; + + result["operationName"] = "questionData"; + result["query"] = R"(query questionData($titleSlug: String!) { + question(titleSlug: $titleSlug) { + content + stats + codeDefinition + sampleTestCase + } +})"; + + nlohmann::json variables; + variables["titleSlug"] = title; + result["variables"] = variables; + + return result; +} + +std::unique_ptr Fetcher::extractContentFromJson(const nlohmann::json &json, const LeetCodeProblem &problem) +{ + const auto questionJson = json["data"]["question"]; + std::string content = questionJson["content"]; + + std::vector codeDefinitions; + const std::string codeDefinitionString = questionJson["codeDefinition"]; + + for (nlohmann::json codeDefinitionJson = nlohmann::json::parse(codeDefinitionString); + const auto &codeDefinitionItem : codeDefinitionJson) + { + std::string value = codeDefinitionItem["value"]; + std::string text = codeDefinitionItem["text"]; + std::string defaultCode = codeDefinitionItem["defaultCode"]; + + codeDefinitions.emplace_back(value, text, defaultCode); + } + + return std::make_unique( + problem.questionTitle, + problem.questionTitleSlug, + content, + codeDefinitions, + std::stoi(problem.frontendQuestionId)); +} + +std::string Fetcher::readTemplateFile() +{ + std::ifstream file; + file.open("src/template.cpp"); + + std::stringstream buffer; + if (file.is_open()) + { + buffer << file.rdbuf(); + } + else + { + throw std::runtime_error("Failed to read template file."); + } + + file.close(); + + return buffer.str(); +} \ No newline at end of file diff --git a/src/template.cpp b/src/template.cpp new file mode 100644 index 0000000..506658a --- /dev/null +++ b/src/template.cpp @@ -0,0 +1,9 @@ +/** +* [__PROBLEM_ID__] __PROBLEM_TITLE__ + */ + +// submission codes start here + +__PROBLEM_DEFAULT_CODE__ + +// submission codes end