├── .gitignore ├── AUTHORS ├── LICENSE ├── README.md ├── assets └── images │ └── ui_screenshot.PNG ├── data ├── languages │ └── cpp │ │ ├── CMakeLists.txt │ │ ├── binder.h │ │ ├── comparator.h │ │ ├── main.cpp │ │ ├── parser.h │ │ ├── printer.h │ │ ├── problemtest.cpp │ │ ├── problemtest.h │ │ ├── singletreenode.h │ │ ├── solutionwrapper.h │ │ ├── stlincludes.h │ │ ├── treenode.cpp │ │ ├── treenode.h │ │ └── typetraits.h └── problems │ ├── CheckCompletenessOfABinaryTree │ ├── cpp │ │ ├── solution.cpp │ │ ├── solution.md │ │ └── solution_expected.cpp │ ├── description.md │ └── testcases │ │ ├── TestCase1.test │ │ ├── TestCase10.test │ │ ├── TestCase11test │ │ ├── TestCase2.test │ │ ├── TestCase3.test │ │ ├── TestCase4.test │ │ ├── TestCase5.test │ │ ├── TestCase6.test │ │ ├── TestCase7.test │ │ ├── TestCase8.test │ │ └── TestCase9.test │ ├── LongestSubstringWithoutRepeatingCharacters │ ├── cpp │ │ ├── solution.cpp │ │ ├── solution.md │ │ └── solution_expected.cpp │ ├── description.md │ └── testcases │ │ ├── TestCase1.test │ │ ├── TestCase10.test │ │ ├── TestCase11test │ │ ├── TestCase2.test │ │ ├── TestCase3.test │ │ ├── TestCase4.test │ │ ├── TestCase5.test │ │ ├── TestCase6.test │ │ ├── TestCase7.test │ │ ├── TestCase8.test │ │ └── TestCase9.test │ ├── NumberOfIslands │ ├── cpp │ │ ├── solution.cpp │ │ ├── solution.md │ │ └── solution_expected.cpp │ ├── description.md │ └── testcases │ │ ├── TestCase1.test │ │ ├── TestCase2.test │ │ ├── TestCase3.test │ │ ├── TestCase4.test │ │ └── TestCase5.test │ └── TwoSum │ ├── cpp │ ├── solution.cpp │ ├── solution.md │ └── solution_expected.cpp │ ├── description.md │ └── testcases │ ├── TestCase1.test │ ├── TestCase10.test │ ├── TestCase11.test │ ├── TestCase12.test │ ├── TestCase13.test │ ├── TestCase14.test │ ├── TestCase15.test │ ├── TestCase2.test │ ├── TestCase3.test │ ├── TestCase4.test │ ├── TestCase5.test │ ├── TestCase6.test │ ├── TestCase7.test │ ├── TestCase8.test │ └── TestCase9.test ├── docs └── index.md ├── install.bat ├── install.py ├── install.sh └── src ├── app ├── functionextractor.py ├── logger.py ├── openleetcode.bat ├── openleetcode.py ├── openleetcode.sh ├── resultsvalidator.py └── testrunner.py ├── schema └── results_validation_schema.json └── ui ├── .gitignore ├── directory-manager.js ├── index.html ├── index.js ├── main.js ├── openleetcodeui.bat ├── openleetcodeui.sh ├── package-lock.json ├── package.json ├── preload.js └── styles.css /.gitignore: -------------------------------------------------------------------------------- 1 | src/gui/geoip/GeoIP.dat 2 | src/gui/geoip/GeoIP.dat.gz 3 | src/qbittorrent 4 | src/qbittorrent-nox 5 | src/release 6 | src/debug 7 | src/base/version.h 8 | CMakeLists.txt.user* 9 | qbittorrent.pro.user* 10 | conf.pri 11 | Makefile* 12 | *.pyc 13 | *.log 14 | 15 | # Compiled object files 16 | *.o 17 | *.pdb 18 | *.exe 19 | *.dll 20 | 21 | # Generated MOC, resource and UI files 22 | moc_*.cpp 23 | moc_*.h 24 | qrc_*.cpp 25 | ui_*.h 26 | *.moc 27 | *.qm 28 | .DS_Store 29 | .qmake.stash 30 | src/qbittorrent.app 31 | *.dmg 32 | 33 | #Autotools junk 34 | aclocal.m4 35 | autom4te.cache/* 36 | config.status 37 | src/icons/qbt-theme/build-icons/node_modules/ 38 | src/icons/skin/build-icons/node_modules/ 39 | src/icons/skin/build-icons/icons/*.png 40 | 41 | # CMake build directory 42 | bin/ 43 | build/ 44 | *.vcxproj 45 | *.vcxproj.filters 46 | *.sln 47 | problem_builds/ 48 | .vscode/ 49 | install/ 50 | 51 | # Temporary folders generated by openleetcode 52 | testcase_output/ 53 | 54 | # Imported modules 55 | nlohmann/ -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Current maintainer: 2 | * Miro Bucko 3 | 4 | Original author: 5 | * Miro Bucko -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 mbucko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | OpenLeetCode - An open source version of LeetCode 2 | -------------------------------------------------------- 3 | Welcome to the OpenLeetCode Project! 4 | 5 | The motivation behind this project is to be able to practice LeetCode problems on a plane without requiring an internet connection (until Starlink ramps up). This project is not intended to replace or replicate leetcode.com. 6 | 7 | ## Table of Content 8 | - [OpenLeetCode - An open source version of LeetCode](#openleetcode---an-open-source-version-of-leetcode) 9 | - [Table of Content](#table-of-content) 10 | - [Screenshot](#screenshot) 11 | - [Build](#build) 12 | - [Windows](#windows) 13 | - [Building without UI](#building-without-ui) 14 | - [Building with UI](#building-with-ui) 15 | - [Unix](#unix) 16 | - [Building without UI](#building-without-ui-1) 17 | - [Building with UI](#building-with-ui-1) 18 | - [Run](#run) 19 | - [Windows](#windows-1) 20 | - [CLI](#cli) 21 | - [UI](#ui) 22 | - [CLI](#cli-1) 23 | - [UI](#ui-1) 24 | - [How To Use](#how-to-use) 25 | - [List of LeetCode Problems](#list-of-leetcode-problems) 26 | - [Usage](#usage) 27 | - [Note](#note) 28 | - [Requirements](#requirements) 29 | - [Additional Requirements for the UI](#additional-requirements-for-the-ui) 30 | - [Contributing](#contributing) 31 | 32 | 33 | ## Screenshot 34 | ![Screenshot](assets/images/ui_screenshot.PNG) 35 | 36 | ## Build 37 | ### Windows 38 | #### Building without UI 39 | ```cmd 40 | git clone https://github.com/mbucko/openleetcode 41 | cd openleetcode 42 | .\install --prefix=./install 43 | ``` 44 | #### Building with UI 45 | ```cmd 46 | git clone https://github.com/mbucko/openleetcode 47 | cd openleetcode 48 | .\install --prefix=./install --enable_ui 49 | ``` 50 | ### Unix 51 | #### Building without UI 52 | ```bash 53 | git clone https://github.com/mbucko/openleetcode 54 | cd openleetcode 55 | ./install.sh --prefix=./install 56 | ``` 57 | #### Building with UI 58 | ```bash 59 | git clone https://github.com/mbucko/openleetcode 60 | cd openleetcode 61 | ./install.sh --prefix=./install --enable_ui 62 | ``` 63 | ## Run 64 | ### Windows 65 | #### CLI 66 | ```cmd 67 | dir install/OpenLeetCode 68 | ./openleetcode --language cpp --problem TwoSum 69 | ``` 70 | #### UI 71 | ```bash 72 | dir install/OpenLeetCode 73 | ./openleetcodeui 74 | ``` 75 | ### Unix 76 | #### CLI 77 | ```bash 78 | cd install/OpenLeetCode 79 | ./openleetcode.sh --language cpp --problem TwoSum 80 | ``` 81 | #### UI 82 | ```bash 83 | cd install/OpenLeetCode 84 | ./openleetcodeui.sh 85 | ``` 86 | NOTE: UI for unix is yet to be tested. 87 | 88 | ## How To Use 89 | After the build succeeds, the following directory structure will be generated: 90 | 91 | - problems 92 | - NumberOfIslands 93 | - cpp 94 | - solution.cpp 95 | - ... 96 | - testcases 97 | - TestCase1.test 98 | - TestCase2.test 99 | - ... 100 | - description.md 101 | - TwoSum 102 | - .. 103 | - launguage 104 | - cpp 105 | 106 | Just like for LeetCode, you have one file where you solve the problem. For example, the problem called TwoSum has **problems/TwoSum/cpp/solution.cpp**. To add new test cases, you can create a file in the **problems/TwoSum/testcases/** directory with the file extension **.test**, and the solution will automatically be tested against it. 107 | 108 | Each problem is described in the ***description.md*** file location in the problem's directory. For example ***problems/TwoSum/description.md***. 109 | 110 | The format of the .test files are as follows: 111 | 112 | ```text 113 | 114 | 115 | 116 | ``` 117 | 118 | The supported types are: integral type, a string, TreeNode structure, boolean or an array. For example: 119 | 120 | ```text 121 | ["1", "2", "4"] 122 | 8.0 123 | [0, 0] 124 | ``` 125 | 126 | A ThreeNode structure is represented in an array-based structure. For example an array representation `[1, 2, null, null, 3]` results to the following structure: 127 | 128 | 1 129 | / \ 130 | 2 3 131 | 132 | ## List of LeetCode Problems 133 | * TwoSum 134 | * LongestSubstringWithoutRepeatingCharacters 135 | * NumberOfIslands 136 | * CheckCompletenessOfABinaryTree 137 | 138 | The problem names are automatically extracted from the **problems** folder. 139 | 140 | ## Usage 141 | ```text 142 | $ python openleetcode.py --help 143 | usage: openleetcode.py [-h] [--language {cpp}] [--list-problems] [--list-testcases] [--problem problem_name] [--problem_builds_dir dir] [--testcase testcase_name] [--verbose] 144 | 145 | OpenLeetCode problem builder. This script builds and tests LeetCode-like problems locally. Currently, it only supports the C++ language, but it can be extended to support other languages. 146 | 147 | options: 148 | -h, --help show this help message and exit 149 | --language {cpp}, -l {cpp} 150 | The programming language. 151 | --list-problems List problems. 152 | --list-testcases List testcases for a problem specified with '--problem' option. 153 | --problem problem_name, -p problem_name 154 | Name of the problem to build and test. Default: TwoSum. Use --list-problems to list all problems. 155 | --problem_builds_dir dir, -d dir 156 | Specifies the directory with the problems. Typically, this is './problem_builds'. If not provided, the script defaults to './problem_builds' in the same directory as the executable. 157 | --run-expected-tests, -r 158 | Run the expected solution. Default: False. 159 | --testcase testcase_name, -t testcase_name 160 | Name of the testcase to run. '--testcase All' will run all testcases. Default: All. 161 | --verbose, -v Print verbose output 162 | ``` 163 | 164 | ## Note 165 | Curently only C++ is supported but the framework is setup such that other languages can be added. 166 | 167 | ## Requirements 168 | This project requires the following to run: 169 | 170 | - Python 171 | - CMake 3.12 172 | - Git 173 | 174 | #### Additional Requirements for the UI 175 | - npm 176 | 177 | ## Contributing 178 | Feel free to contribute with code, test cases, or even code reviews. 179 | 180 | For a more in-depth guide on how to contribute and information about the inner workings of OpenLeetCode, please refer to the [Docs](docs/index.md). 181 | You can also join our [Discord chat](https://discord.gg/BzkqubYUm8) if you have any questions about the usage or development. 182 | -------------------------------------------------------------------------------- /assets/images/ui_screenshot.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbucko/openleetcode/0fbca72f9b0c2790f44300a04d3d67cba91c6566/assets/images/ui_screenshot.PNG -------------------------------------------------------------------------------- /data/languages/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | set(CMAKE_CXX_STANDARD 20) 4 | set(CMAKE_CXX_STANDARD_REQUIRED True) 5 | set(JSON_BuildTests OFF CACHE INTERNAL "") 6 | 7 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") 8 | if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) 9 | message(FATAL_ERROR "GCC version must be at least 10!") 10 | endif() 11 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 12 | if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 10) 13 | message(FATAL_ERROR "Clang version must be at least 10!") 14 | endif() 15 | elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") 16 | if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.23) 17 | message(FATAL_ERROR "Visual Studio version must be at least 2019!") 18 | endif() 19 | endif() 20 | 21 | project(solution 22 | DESCRIPTION "Open Source version of LeetCode" 23 | LANGUAGES CXX 24 | ) 25 | 26 | include(FetchContent) 27 | 28 | FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) 29 | FetchContent_MakeAvailable(json) 30 | 31 | # Set the build type explicitly 32 | set(CMAKE_BUILD_TYPE Release) 33 | 34 | set(CMAKE_BUILD_TYPE Release) 35 | 36 | add_executable(solution_cpp) 37 | add_executable(solution_expected_cpp) 38 | 39 | target_compile_definitions(solution_expected_cpp PRIVATE EXPECTED) 40 | 41 | set(HEADER_FILES 42 | binder.h 43 | comparator.h 44 | parser.h 45 | printer.h 46 | problemtest.h 47 | solutionwrapper.h 48 | stlincludes.h 49 | treenode.h 50 | typetraits.h 51 | ) 52 | 53 | set(SOURCE_FILES 54 | main.cpp 55 | problemtest.cpp 56 | treenode.cpp 57 | ) 58 | 59 | target_sources(solution_cpp PRIVATE 60 | # headers 61 | ${HEADER_FILES} 62 | # sources 63 | ${SOURCE_FILES} 64 | ) 65 | 66 | target_sources(solution_expected_cpp PRIVATE 67 | # headers 68 | ${HEADER_FILES} 69 | # sources 70 | ${SOURCE_FILES} 71 | ) 72 | 73 | target_link_libraries(solution_cpp PRIVATE nlohmann_json::nlohmann_json) 74 | target_link_libraries(solution_expected_cpp PRIVATE nlohmann_json::nlohmann_json) 75 | 76 | set(CMAKE_INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/bin) 77 | 78 | install(TARGETS solution_cpp 79 | RUNTIME DESTINATION ${CMAKE_INSTALL_DIR} 80 | ) 81 | 82 | install(TARGETS solution_expected_cpp 83 | RUNTIME DESTINATION ${CMAKE_INSTALL_DIR} 84 | ) 85 | -------------------------------------------------------------------------------- /data/languages/cpp/binder.h: -------------------------------------------------------------------------------- 1 | #ifndef BINDER_H 2 | #define BINDER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "parser.h" 12 | #include "solutionfunction.h" 13 | #include "solutionwrapper.h" 14 | 15 | using SolutionFun = decltype(fun); 16 | 17 | class Binder { 18 | public: 19 | using return_type = typename function_traits::return_type; 20 | using arg_tuple_type = 21 | typename function_traits::arg_tuple_type; 22 | 23 | constexpr static size_t func_arg_size_v = 24 | std::tuple_size_v; 25 | 26 | private: 27 | 28 | template 29 | static return_type callFunction(Solution& solution, 30 | arg_tuple_type& args, 31 | std::index_sequence) { 32 | return (solution.*fun)(std::get(args)...); 33 | } 34 | 35 | template 36 | inline static typename std::enable_if::type 37 | parseArgs(std::tuple&, std::vector&) { } 38 | 39 | template 40 | inline static typename std::enable_if::type 41 | parseArgs(std::tuple& t, std::vector& args) { 42 | using element_type = std::tuple_element_t>; 43 | std::get(t) = parse(args[I]); 44 | parseArgs(t, args); 45 | } 46 | 47 | public: 48 | static return_type solve(Solution& solution, 49 | std::vector&& args) { 50 | // args will be exactly the size of the number of arguments the 51 | // function takes + expected return value (as per testcases formatting) 52 | if (std::tuple_size_v > args.size()) { 53 | throw std::invalid_argument("Incorrect number of arguments"); 54 | } 55 | 56 | arg_tuple_type argTuple; 57 | parseArgs(argTuple, args); 58 | 59 | return callFunction( 60 | solution, argTuple, 61 | std::make_index_sequence>{}); 62 | } 63 | }; 64 | 65 | #endif // BINDER_H -------------------------------------------------------------------------------- /data/languages/cpp/comparator.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPARATOR_H 2 | #define COMPARATOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct Comparator { 10 | // template 11 | // inline static bool compare(const T& lhs, const T& rhs, bool order = false); 12 | 13 | template || 15 | std::is_same_v>> 16 | inline static bool compare(const T& lhs, const T& rhs, bool order) { 17 | return lhs == rhs; 18 | } 19 | 20 | template || 22 | std::is_same_v>> 23 | inline static bool compare(std::vector& lhs, 24 | std::vector& rhs, 25 | bool order) { 26 | if (lhs.size() != rhs.size()) { 27 | return false; 28 | } 29 | 30 | if (order) { 31 | return lhs == rhs; 32 | } else { 33 | std::ranges::sort(lhs); 34 | std::ranges::sort(rhs); 35 | return compare(lhs, rhs, true); 36 | } 37 | } 38 | }; 39 | 40 | #endif // COMPARATOR_H -------------------------------------------------------------------------------- /data/languages/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | #include "problemtest.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | bool openFileForWriting(const std::string &filename, std::ofstream &out) { 11 | const auto parent_path = 12 | std::filesystem::path(filename).parent_path(); 13 | 14 | if (!std::filesystem::exists(parent_path) || 15 | !std::filesystem::is_directory(parent_path)) { 16 | std::cerr << "Directory " << parent_path << " does not exist" 17 | << std::endl; 18 | } 19 | 20 | std::ofstream outfile(filename); 21 | 22 | if (!outfile.is_open()) { 23 | std::cerr << "Unable to open file for writing"; 24 | return false; 25 | } 26 | 27 | out = std::move(outfile); 28 | return true; 29 | } 30 | 31 | std::string to_lower(std::string s) { 32 | std::transform(s.begin(), s.end(), s.begin(), ::tolower); 33 | return s; 34 | } 35 | 36 | int main(int argc, char *argv[]) { 37 | if (argc < 4) { 38 | std::cerr << "Usage: " << argv[0] 39 | << " " 40 | << "" << std::endl; 41 | return 1; 42 | } 43 | 44 | const std::string test_dir_name = argv[1]; 45 | const std::string results_file_name = argv[2]; 46 | const std::string testcase_name = argv[3]; 47 | 48 | if (!std::filesystem::is_directory(test_dir_name)) { 49 | std::cerr << "Error, test directory " << test_dir_name 50 | << " does not exist." 51 | << std::endl; 52 | return 1; 53 | } 54 | 55 | if (std::filesystem::exists(results_file_name)) { 56 | std::cerr << "Error, results file " << results_file_name 57 | << " already exists." 58 | << std::endl; 59 | return 1; 60 | } 61 | 62 | std::string testcase_file_name; 63 | if (to_lower(testcase_name) != "all") { 64 | testcase_file_name = test_dir_name + "/" + 65 | testcase_name + ".test"; 66 | if (!std::filesystem::exists(testcase_file_name)) { 67 | std::cerr << "Error, testcase file " << testcase_file_name 68 | << " does not exist." 69 | << std::endl; 70 | return 1; 71 | } 72 | } 73 | 74 | ProblemTest problemTest(test_dir_name, 75 | results_file_name, 76 | testcase_name, 77 | testcase_file_name); 78 | 79 | const bool success = problemTest.run(); 80 | 81 | return success ? 0 : 1; 82 | } 83 | -------------------------------------------------------------------------------- /data/languages/cpp/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSER_H 2 | #define PARSER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "treenode.h" 13 | #include "singletreenode.h" 14 | #include "treenode.h" 15 | #include "typetraits.h" 16 | 17 | void removeOuterSpaces(std::string& value) { 18 | const auto start = value.find_first_not_of(' '); 19 | const auto end = value.find_last_not_of(' '); 20 | value = value.substr(start, end - start + 1); 21 | } 22 | 23 | bool hasEncapsulatedTokens(const std::string& str, char start, char end) { 24 | if (str.empty()) { 25 | return false; 26 | } 27 | if (str[0] != start || str[str.size() - 1] != end) { 28 | return false; 29 | } 30 | return true; 31 | } 32 | 33 | bool isCharFormatOk(const std::string& str) { 34 | return hasEncapsulatedTokens(str, '\"', '\"'); 35 | } 36 | 37 | bool isStringFormatOk(const std::string& str) { 38 | return hasEncapsulatedTokens(str, '\"', '\"'); 39 | } 40 | 41 | bool isArrayFormatOk(const std::string& str) { 42 | return hasEncapsulatedTokens(str, '[', ']'); 43 | } 44 | 45 | std::string removeEncapsulatedTokens(const std::string& str) { 46 | return str.substr(1, str.size() - 2); 47 | } 48 | 49 | std::string removeBrackets(const std::string& str) { 50 | return removeEncapsulatedTokens(str); 51 | } 52 | 53 | std::string removeQuotes(const std::string& str) { 54 | return removeEncapsulatedTokens(str); 55 | } 56 | 57 | std::vector splitIgnoreBrackets(const std::string str, 58 | char delimiter) { 59 | std::vector result; 60 | std::string token; 61 | int level = 0; 62 | for (char c : str) { 63 | if (c == '[') { 64 | ++level; 65 | } else if (c == ']') { 66 | --level; 67 | } 68 | 69 | if (c == delimiter && level == 0) { 70 | result.push_back(token); 71 | token.clear(); 72 | } else { 73 | token += c; 74 | } 75 | } 76 | 77 | if (!token.empty()) { 78 | result.push_back(token); 79 | } 80 | return result; 81 | } 82 | 83 | template 84 | std::enable_if_t, T> parse(std::string value) { 85 | removeOuterSpaces(value); 86 | if (value != "true" && value != "false") { 87 | std::stringstream ss; 88 | ss << "Error: Invalid boolean format. Must be either \"true\" or " 89 | "\"false\". String: " << value; 90 | throw std::runtime_error(ss.str()); 91 | } 92 | return value == "true" ? true : false; 93 | } 94 | 95 | template 96 | std::enable_if_t, T> parse(std::string value) { 97 | removeOuterSpaces(value); 98 | if (value.size() != 3 || !isCharFormatOk(value)) { 99 | std::stringstream ss; 100 | ss << "Error: Invalid char format. Must be of length 3 and contain " 101 | << "\"s. String: " << value; 102 | throw std::runtime_error(ss.str()); 103 | } 104 | return value[1]; 105 | } 106 | 107 | template 108 | std::enable_if_t, T> parse(std::string value) { 109 | removeOuterSpaces(value); 110 | if (!isStringFormatOk(value)) { 111 | std::stringstream ss; 112 | ss << "Error: Invalid string format. The input string must be enclosed" 113 | " in quotes. Current value: " << value; 114 | throw std::runtime_error(ss.str()); 115 | } 116 | return removeQuotes(value); 117 | } 118 | 119 | template 120 | std::enable_if_t && !std::is_same_v, T> 121 | parse(std::string value) { 122 | removeOuterSpaces(value); 123 | T ret{}; 124 | std::stringstream ss(value); 125 | ss >> ret; 126 | return ret; 127 | } 128 | 129 | template 130 | std::enable_if_t, T> parse( 131 | std::string value) { 132 | if (value.empty()) { 133 | std::stringstream ss; 134 | ss << "Error: Invalid TreeNode value. Value: " << value; 135 | throw std::runtime_error(ss.str()); 136 | } 137 | 138 | if (value == "null") { 139 | return nullptr; 140 | } 141 | 142 | return new SingleTreeNode(parse(std::move(value))); 143 | 144 | } 145 | 146 | template 147 | std::enable_if_t::value, T> parse(std::string value) { 148 | using element_type = typename T::value_type; 149 | removeOuterSpaces(value); 150 | T vec; 151 | if (!isArrayFormatOk(value)) { 152 | std::stringstream ss; 153 | ss << "Error: Invalid array format. Array: " << value; 154 | throw std::runtime_error(ss.str()); 155 | } 156 | 157 | std::vector tokens = 158 | splitIgnoreBrackets(removeBrackets(value), ','); 159 | for (auto&& token : tokens) { 160 | vec.push_back(parse(std::move(token))); 161 | } 162 | return vec; 163 | } 164 | 165 | TreeNode* convertToTree(const std::vector& nodes) { 166 | if (nodes.empty() || nodes[0] == nullptr) { 167 | return nullptr; 168 | } 169 | 170 | SingleTreeNode* root = static_cast(nodes[0]); 171 | std::queue queue; 172 | queue.push(root); 173 | 174 | size_t i = 1; 175 | while (!queue.empty() && i < nodes.size()) { 176 | TreeNode* current = queue.front(); 177 | queue.pop(); 178 | 179 | if (i < nodes.size() && nodes[i] != nullptr) { 180 | current->left = static_cast(nodes[i]); 181 | queue.push(static_cast(nodes[i])); 182 | } 183 | ++i; 184 | 185 | if (i < nodes.size() && nodes[i] != nullptr) { 186 | current->right = static_cast(nodes[i]); 187 | queue.push(static_cast(nodes[i])); 188 | } 189 | ++i; 190 | } 191 | 192 | return root; 193 | } 194 | 195 | template 196 | std::enable_if_t, T> parse(std::string value) { 197 | const auto vec = parse>(std::move(value)); 198 | 199 | return convertToTree(vec); 200 | } 201 | 202 | #endif // PARSER_H -------------------------------------------------------------------------------- /data/languages/cpp/printer.h: -------------------------------------------------------------------------------- 1 | #ifndef PRINTER_H 2 | #define PRINTER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "singletreenode.h" 16 | #include "treenode.h" 17 | #include "typetraits.h" 18 | 19 | struct Printer { 20 | template 21 | static std::string toString(const T& value) { 22 | std::stringstream ss; 23 | ss << value; 24 | return ss.str(); 25 | } 26 | 27 | static std::string toString(const std::string& value) { 28 | return value; 29 | } 30 | 31 | template 32 | static std::string toString(const std::vector& value) { 33 | std::stringstream ss; 34 | ss << "["; 35 | for (size_t i = 0; i < value.size(); ++i) { 36 | if (i != 0) { 37 | ss << ", "; 38 | } 39 | ss << Printer::toString(value[i]); 40 | } 41 | ss << "]"; 42 | return ss.str(); 43 | } 44 | 45 | static std::string toString(const SingleTreeNode* value) { 46 | if (value == nullptr) { 47 | return "null"; 48 | } 49 | return Printer::toString(value->val); 50 | } 51 | 52 | static std::string toString(const TreeNode* value) { 53 | const std::vector nodes = toVector(value); 54 | if (nodes.empty()) { 55 | return "[null]"; 56 | } 57 | 58 | return Printer::toString(nodes); 59 | } 60 | 61 | private: 62 | static std::vector toVector(const TreeNode* value) { 63 | std::vector nodes; 64 | if (value == nullptr) { 65 | return nodes; 66 | } 67 | 68 | std::queue queue; 69 | std::unordered_set visited; 70 | queue.push(value); 71 | 72 | while (!queue.empty()) { 73 | const TreeNode* current = queue.front(); 74 | queue.pop(); 75 | 76 | if (visited.count(current) > 0) { 77 | throw std::runtime_error("Circular dependency detected"); 78 | } 79 | visited.insert(current); 80 | 81 | if (current != nullptr) { 82 | nodes.push_back(static_cast(current)); 83 | queue.push(current->left); 84 | queue.push(current->right); 85 | } else { 86 | nodes.push_back(nullptr); 87 | } 88 | } 89 | 90 | nodes.erase(std::find(nodes.rbegin(), nodes.rend(), nullptr).base(), 91 | nodes.end()); 92 | 93 | return nodes; 94 | } 95 | }; 96 | 97 | #endif // PRINTER_H -------------------------------------------------------------------------------- /data/languages/cpp/problemtest.cpp: -------------------------------------------------------------------------------- 1 | #include "problemtest.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "binder.h" 18 | #include "comparator.h" 19 | #include "parser.h" 20 | #include "printer.h" 21 | #include "solutionwrapper.h" 22 | 23 | namespace { 24 | 25 | constexpr static char kAnyValue[] = "*"; 26 | 27 | bool openFileForWriting(const std::string &filename, std::ofstream &out) { 28 | std::ofstream outfile(filename, std::ios::out | std::ios::trunc); 29 | if (!outfile.is_open()) { 30 | std::cerr << "Unable to open file for writing"; 31 | return false; 32 | } 33 | 34 | out = std::move(outfile); 35 | return true; 36 | } 37 | 38 | template 39 | void print(std::ostream& out, const T& value) { 40 | out << Printer::toString(value); 41 | } 42 | 43 | std::vector getTestFiles(const std::string& test_dir_name, 44 | const std::string& testcase_file_name) { 45 | std::vector test_files; 46 | 47 | if (!testcase_file_name.empty()) { 48 | test_files.emplace_back(testcase_file_name); 49 | return test_files; 50 | } 51 | 52 | std::filesystem::directory_iterator dir_iter(test_dir_name); 53 | for (const auto& entry : dir_iter) { 54 | if (entry.is_regular_file() && entry.path().extension() == ".test") { 55 | test_files.emplace_back(entry.path().string()); 56 | } 57 | } 58 | 59 | // sort such that Test2 comes before Test10 60 | std::ranges::sort(test_files, [](const std::string& a, 61 | const std::string& b) { 62 | auto split = [](const std::string& s) { 63 | std::string before, number, after; 64 | std::smatch match; 65 | if (std::regex_search(s, match, std::regex("(\\D*)(\\d*)(\\D*)"))) { 66 | before = match[1].str(); 67 | number = match[2].str(); 68 | after = match[3].str(); 69 | } 70 | return std::make_tuple(before, number.empty() ? -1 : std::stoi(number), after); 71 | }; 72 | const auto split_a = split(a); 73 | const auto split_b = split(b); 74 | 75 | if (std::get<1>(split_a) != -1 && std::get<1>(split_b) != -1) { 76 | return split(a) < split(b); 77 | } 78 | return a < b; 79 | }); 80 | 81 | return test_files; 82 | } 83 | 84 | std::vector toLines(const std::string& testcase_file) { 85 | std::vector lines; 86 | std::ifstream in(testcase_file, std::ios::in); 87 | std::string line; 88 | while (std::getline(in, line)) { 89 | lines.emplace_back(std::move(line)); 90 | } 91 | return lines; 92 | } 93 | 94 | } // annoymous namespace 95 | 96 | ProblemTest::ProblemTest(const std::string& test_dir_name, 97 | const std::string& results_file_name, 98 | const std::string& testcase_filter_name, 99 | const std::string& testcase_file_name) 100 | : test_dir_name_(test_dir_name), 101 | results_file_name_(results_file_name), 102 | testcase_filter_name_(testcase_filter_name), 103 | testcase_file_name_(testcase_file_name) { 104 | } 105 | 106 | auto getDurationSince(const auto& start) { 107 | const auto end = std::chrono::high_resolution_clock::now(); 108 | return std::chrono::duration_cast( 109 | end - start).count(); 110 | } 111 | 112 | std::string getTestcaseName(const std::string& testcase_file_name) { 113 | const std::filesystem::path path(testcase_file_name); 114 | const std::string filename = path.filename().string(); 115 | 116 | std::string::size_type pos = filename.rfind(".test"); 117 | if (pos != std::string::npos) { 118 | return filename.substr(0, pos); 119 | } 120 | return filename; 121 | } 122 | 123 | bool ProblemTest::runTest( 124 | const std::string& testcase_file_name, nlohmann::json& test) const { 125 | bool success = false; 126 | bool areEqual = false; 127 | Solution solution; 128 | 129 | Binder::return_type ret{}; 130 | Binder::return_type expected{}; 131 | 132 | test["testcase_name"] = getTestcaseName(testcase_file_name); 133 | 134 | try { 135 | auto lines = toLines(testcase_file_name); 136 | constexpr size_t expected_testcase_size = 137 | Binder::func_arg_size_v + 1; 138 | if (expected_testcase_size != lines.size()) { 139 | std::stringstream ss; 140 | ss << "Incorrect number of parameters specified in the" 141 | << " testcase file. Must specify one line per " 142 | << "parameter + one line for expected output. " 143 | << "Found: " << lines.size() << " Expected: " 144 | << expected_testcase_size << "."; 145 | std::cerr << ss.str() << std::endl; 146 | success = false; 147 | test["reason"] = ss.str(); 148 | } else { 149 | string expected_str; 150 | std::swap(expected_str, lines.back()); 151 | 152 | if (expected_str == kAnyValue) { 153 | test["status"] = "Skipped"; 154 | test["actual"] = 155 | std::move(Binder::solve(solution, std::move(lines))); 156 | return true; 157 | } 158 | 159 | expected = std::move(parse(expected_str)); 160 | ret = std::move(Binder::solve(solution, std::move(lines))); 161 | success = Comparator::compare(ret, expected, true); 162 | if (!success) { 163 | test["reason"] = 164 | "Mismatch between expected and actual output."; 165 | } 166 | } 167 | } catch (const std::exception& e) { 168 | std::stringstream ss; 169 | std::cerr << "Exception occurred while running the test case. " 170 | << "Exception: " << std::string(e.what()); 171 | test["reason"] = "Exception occurred."; 172 | success = false; 173 | } catch (...) { 174 | test["reason"] = "Exception occurred."; 175 | std::cerr << "Unknown exception occurred while running the test case."; 176 | success = false; 177 | } 178 | 179 | if (success) { 180 | test["status"] = "Success"; 181 | } else { 182 | test["status"] = "Failed"; 183 | test["expected"] = expected; 184 | test["actual"] = ret; 185 | test["testcase_file"] = testcase_file_name; 186 | } 187 | 188 | return success; 189 | } 190 | 191 | bool ProblemTest::run() const { 192 | const auto start = std::chrono::high_resolution_clock::now(); 193 | bool success = false; 194 | std::vector testsJason; 195 | 196 | const vector test_files = getTestFiles(test_dir_name_, 197 | testcase_file_name_); 198 | 199 | for (const auto& testcase_file : test_files) { 200 | testsJason.emplace_back(); 201 | success = runTest(testcase_file, testsJason.back()); 202 | if (!success) { 203 | break; 204 | } 205 | } 206 | 207 | nlohmann::json jsonObj; 208 | jsonObj["duration_ms"] = getDurationSince(start); 209 | jsonObj["testcase_filter_name"] = testcase_filter_name_; 210 | 211 | if (success) { 212 | jsonObj["status"] = "Ok"; 213 | jsonObj["tests"] = testsJason; 214 | } else { 215 | jsonObj["status"] = "Failed"; 216 | jsonObj["tests"] = testsJason; 217 | } 218 | 219 | std::ofstream out; 220 | if (!openFileForWriting(results_file_name_, out)) { 221 | std::cerr << "Error: Could not open output file: " 222 | << results_file_name_ 223 | << std::endl; 224 | return 1; 225 | } 226 | out << std::setw(4) << jsonObj << std::endl; 227 | 228 | return true; 229 | } -------------------------------------------------------------------------------- /data/languages/cpp/problemtest.h: -------------------------------------------------------------------------------- 1 | #ifndef PROBLEMTEST_H 2 | #define PROBLEMTEST_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class ProblemTest { 9 | public: 10 | explicit ProblemTest(const std::string& test_dir_name, 11 | const std::string& results_file_name, 12 | const std::string& testcase_filter_name, 13 | const std::string& testcase_file_name); 14 | 15 | bool run() const; 16 | 17 | private: 18 | const std::string test_dir_name_; 19 | const std::string results_file_name_; 20 | const std::string testcase_filter_name_; 21 | const std::string testcase_file_name_; 22 | 23 | bool runTest(const std::string& testcase_file_name, 24 | nlohmann::json& test) const; 25 | 26 | ProblemTest(const ProblemTest&) = delete; 27 | ProblemTest& operator=(const ProblemTest&) = delete; 28 | }; 29 | 30 | #endif // PROBLEMTEST_H -------------------------------------------------------------------------------- /data/languages/cpp/singletreenode.h: -------------------------------------------------------------------------------- 1 | #ifndef SINGLETREENODE_H 2 | #define SINGLETREENODE_H 3 | 4 | #include "treenode.h" 5 | 6 | struct SingleTreeNode : public TreeNode { 7 | using TreeNode::TreeNode; 8 | }; 9 | 10 | static_assert(sizeof(SingleTreeNode) == sizeof(TreeNode), 11 | "SingleTreeNode is not the same size as TreeNode"); 12 | 13 | #endif // SINGLETREENODE_H -------------------------------------------------------------------------------- /data/languages/cpp/solutionwrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef SOLUTIONWRAPPER_H 2 | #define SOLUTIONWRAPPER_H 3 | 4 | #include "stlincludes.h" 5 | #include "treenode.h" 6 | 7 | using namespace std; 8 | 9 | #ifdef EXPECTED 10 | #include "solution_expected.cpp" 11 | #else 12 | #include "solution.cpp" 13 | #endif 14 | 15 | #endif // SOLUTIONWRAPPER_H -------------------------------------------------------------------------------- /data/languages/cpp/stlincludes.h: -------------------------------------------------------------------------------- 1 | // C++ Full Standard Header Include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include -------------------------------------------------------------------------------- /data/languages/cpp/treenode.cpp: -------------------------------------------------------------------------------- 1 | #include "treenode.h" 2 | 3 | #include "printer.h" 4 | 5 | std::string TreeNode::toString() const { 6 | return Printer::toString(this); 7 | } -------------------------------------------------------------------------------- /data/languages/cpp/treenode.h: -------------------------------------------------------------------------------- 1 | #ifndef TREENODE_H 2 | #define TREENODE_H 3 | 4 | #include 5 | 6 | struct TreeNode { 7 | int val; 8 | TreeNode *left; 9 | TreeNode *right; 10 | 11 | TreeNode() : val(0), left(nullptr), right(nullptr) {} 12 | 13 | TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 14 | 15 | TreeNode(int x, TreeNode *left, TreeNode *right) 16 | : val(x), left(left), right(right) {} 17 | 18 | private: 19 | friend std::ostream& operator<< (std::ostream& os, TreeNode node); 20 | 21 | std::string toString() const; 22 | }; 23 | 24 | inline std::ostream& operator<< (std::ostream& os, TreeNode node) { 25 | return os << node.toString(); 26 | } 27 | 28 | #endif // TREENODE_H -------------------------------------------------------------------------------- /data/languages/cpp/typetraits.h: -------------------------------------------------------------------------------- 1 | #ifndef TYPETRAITS_H 2 | #define TYPETRAITS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | struct function_traits; 10 | 11 | template 12 | struct function_traits { 13 | using return_type = R; 14 | using arg_tuple_type = std::tuple...>; 15 | }; 16 | 17 | template 18 | struct function_traits { 19 | using return_type = R; 20 | using arg_tuple_type = std::tuple...>; 21 | }; 22 | 23 | template 24 | struct is_vector_type : std::false_type {}; 25 | 26 | template 27 | struct is_vector_type> : std::true_type {}; 28 | 29 | #endif // TYPETRAITS_H -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/cpp/solution.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * struct TreeNode { 4 | * int val; 5 | * TreeNode *left; 6 | * TreeNode *right; 7 | * TreeNode() : val(0), left(nullptr), right(nullptr) {} 8 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 9 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 10 | * }; 11 | * 12 | * std::ostream& operator<< (std::ostream& os, TreeNode node); 13 | */ 14 | class Solution { 15 | public: 16 | bool isCompleteTree(TreeNode* root) { 17 | } 18 | }; -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/cpp/solution.md: -------------------------------------------------------------------------------- 1 | ### Approach 2 | 3 | 4 | ### Implementation 5 | 6 | ```cpp 7 | class Solution { 8 | public: 9 | bool isCompleteTree(TreeNode* root) { 10 | deque stack; 11 | stack.push_back(root); 12 | 13 | bool foundNull = false; 14 | while (!stack.empty()) { 15 | TreeNode* node = stack.front(); 16 | stack.pop_front(); 17 | 18 | if (node == nullptr) { 19 | foundNull = true; 20 | continue; 21 | } else { 22 | if (foundNull == true) { 23 | return false; 24 | } else { 25 | stack.push_back(node->left); 26 | stack.push_back(node->right); 27 | } 28 | } 29 | } 30 | return true; 31 | } 32 | }; 33 | ``` 34 | 35 | ### Complexity Analysis 36 | * Time complexity : **O(N)**.
37 | 38 | * Space complexity : **O(log(N))**.
39 | 40 | -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/cpp/solution_expected.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Definition for a binary tree node. 3 | * struct TreeNode { 4 | * int val; 5 | * TreeNode *left; 6 | * TreeNode *right; 7 | * TreeNode() : val(0), left(nullptr), right(nullptr) {} 8 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} 9 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} 10 | * }; 11 | * 12 | * std::ostream& operator<< (std::ostream& os, TreeNode node); 13 | */ 14 | class Solution { 15 | public: 16 | bool isCompleteTree(TreeNode* root) { 17 | deque stack; 18 | stack.push_back(root); 19 | 20 | bool foundNull = false; 21 | while (!stack.empty()) { 22 | TreeNode* node = stack.front(); 23 | stack.pop_front(); 24 | 25 | if (node == nullptr) { 26 | foundNull = true; 27 | continue; 28 | } else { 29 | if (foundNull == true) { 30 | return false; 31 | } else { 32 | stack.push_back(node->left); 33 | stack.push_back(node->right); 34 | } 35 | } 36 | } 37 | return true; 38 | } 39 | }; -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/description.md: -------------------------------------------------------------------------------- 1 | # Check Completeness of a Binary Tree 2 | 3 | Check if the given binary tree root forms a complete binary tree. 4 | 5 | A complete binary tree is characterized by each level being fully filled except potentially the last level. However, all nodes in the last level should be aligned to the left. The last level, `h`, can have between `1` and `2h` nodes. 6 | \ 7 | \ 8 | \ 9 | **Example 1:** 10 | 11 | 1 12 | / \ 13 | / \ 14 | 2 3 15 | / \ / 16 | 4 5 6 17 | 18 | >**Input:** root = [1,2,3,4,5,6]\ 19 | >**Output:** true\ 20 | >**Explanation:** All levels preceding the final one are completely filled (for instance, levels with node-values {1} and {2, 3}), and all nodes in the last level ({4, 5, 6}) are aligned to the left as much as possible. 21 | 22 | **Example 2:** 23 | 24 | 1 25 | / \ 26 | / \ 27 | 2 3 28 | / \ \ 29 | 4 5 7 30 | 31 | >**Input:** root = [1,2,3,4,5,null,7]\ 32 | >**Output:** false\ 33 | >**Explanation:** The node with value 7 isn't as far left as possible. 34 | 35 | **Constraints:** 36 | 37 | * The number of nodes in the tree is in the range ``[1, 100]``. 38 | * ``1 <= Node.val <= 1000`` -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase1.test: -------------------------------------------------------------------------------- 1 | [1,2,3,4,5,6] 2 | true -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase10.test: -------------------------------------------------------------------------------- 1 | [1,2,3,5,6] 2 | true -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase11test: -------------------------------------------------------------------------------- 1 | [1,2,3,5] 2 | true -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase2.test: -------------------------------------------------------------------------------- 1 | [1,2,3,4,5,null,7] 2 | false -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase3.test: -------------------------------------------------------------------------------- 1 | [1] 2 | true -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase4.test: -------------------------------------------------------------------------------- 1 | [1,null,2] 2 | false -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase5.test: -------------------------------------------------------------------------------- 1 | [1,null,2] 2 | false -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase6.test: -------------------------------------------------------------------------------- 1 | [1,2,3] 2 | true -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase7.test: -------------------------------------------------------------------------------- 1 | [1,2,3,5,6,7,8] 2 | true -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase8.test: -------------------------------------------------------------------------------- 1 | [1,2,3,5,6,7] 2 | true -------------------------------------------------------------------------------- /data/problems/CheckCompletenessOfABinaryTree/testcases/TestCase9.test: -------------------------------------------------------------------------------- 1 | [1,2,3,5,6,null,8] 2 | false -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/cpp/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int lengthOfLongestSubstring(string s) { 4 | } 5 | }; -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/cpp/solution.md: -------------------------------------------------------------------------------- 1 | ### Approach 2 | The solution uses the sliding window technique to efficiently along with hash map to find the length of the longest substring without repeating characters. 3 | 4 | ### Implementation 5 | 6 | ```cpp 7 | class Solution { 8 | public: 9 | int lengthOfLongestSubstring(string s) { 10 | int size = s.length(); 11 | int maxLength = 0; 12 | unordered_map lastVisited; 13 | 14 | for (int j = 0, i = 0; j < size; j++){ 15 | if(lastVisited[s[j]] > 0) { 16 | i = max(lastVisited[s[j]], i); 17 | } 18 | maxLength = max(maxLength, j - i + 1); 19 | lastVisited[s[j]] = j + 1; 20 | } 21 | return maxLength; 22 | } 23 | }; 24 | ``` 25 | 26 | ### Complexity Analysis 27 | * Time complexity : **O(n)**. We iterate over **s** exactly **n** times. Each lookup in the hash map costs only **O(1)** time. 28 | 29 | * Space complexity : **O(k)**. We need **O(k)** space for checking a substring has no duplicate characters, where **k** is the size of the hash map. The size of the hash map is upper bounded by unique number of charasters in **s**. 30 | 31 | -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/cpp/solution_expected.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int lengthOfLongestSubstring(string s) { 4 | int size = s.length(); 5 | int maxLength = 0; 6 | unordered_map lastVisited; 7 | 8 | for (int j = 0, i = 0; j < size; j++){ 9 | if(lastVisited[s[j]] > 0) { 10 | i = max(lastVisited[s[j]], i); 11 | } 12 | maxLength = max(maxLength, j - i + 1); 13 | lastVisited[s[j]] = j + 1; 14 | } 15 | return maxLength; 16 | } 17 | }; -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/description.md: -------------------------------------------------------------------------------- 1 | # Longest Substring Without Repeating Characters 2 | 3 | Given a string `s`, find the length of the longest substring that has no repeating characters. 4 | \ 5 | \ 6 | \ 7 | **Example 1:** 8 | >**Input:** s = "abcabcbb"\ 9 | >**Output:** 3\ 10 | >**Explanation:** The answer is "abc", with the length of 3. 11 | 12 | **Example 2:** 13 | >**Input:** s = "bbbbb"\ 14 | >**Output:** 1\ 15 | >**Explanation:** The answer is "b", with the length of 1. 16 | 17 | **Example 3:** 18 | >**Input:** s = "pwwkew"\ 19 | >**Output:** 3\ 20 | >**Explanation:** The answer is "wke", with the length of 3.\ 21 | >Notice that the answer must be a substring, "pwke" is a subsequence and not a substring. 22 | 23 | **Constraints:** 24 | 25 | * ``0 <= s.length <= 5 * 104`` 26 | * ``s consists of English letters, digits, symbols and spaces.`` -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase1.test: -------------------------------------------------------------------------------- 1 | "abcabcbb" 2 | 3 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase10.test: -------------------------------------------------------------------------------- 1 | "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main benefits are lost if a trusted third party is still required to prevent double-spending. We propose a solution to the double-spending problem using a peer-to-peer network. The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU power. As long as a majority of CPU power is controlled by nodes that are not cooperating to attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure. Messages are broadcast on a best effort basis, and nodes can leave and rejoin the network at will, accepting the longest proof-of-work chain as proof of what happened while they were gone." 2 | 13 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase11test: -------------------------------------------------------------------------------- 1 | "abcdefghijklmnopqrstvuwxyz_bcdefghijklmnopqrstvuwxyz_9264" 2 | 30 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase2.test: -------------------------------------------------------------------------------- 1 | "bbbbb" 2 | 1 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase3.test: -------------------------------------------------------------------------------- 1 | "pwwkew" 2 | 3 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase4.test: -------------------------------------------------------------------------------- 1 | "" 2 | 0 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase5.test: -------------------------------------------------------------------------------- 1 | " " 2 | 1 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase6.test: -------------------------------------------------------------------------------- 1 | " " 2 | 1 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase7.test: -------------------------------------------------------------------------------- 1 | "c" 2 | 1 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase8.test: -------------------------------------------------------------------------------- 1 | "aab" 2 | 2 -------------------------------------------------------------------------------- /data/problems/LongestSubstringWithoutRepeatingCharacters/testcases/TestCase9.test: -------------------------------------------------------------------------------- 1 | "badsadasdsalkdjaslkdajskdlasldajshkdjahsdjkhasdkjhk" 2 | 7 -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/cpp/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | int numIslands(vector>& grid) { 4 | 5 | } 6 | }; -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/cpp/solution.md: -------------------------------------------------------------------------------- 1 | ### Approach 2 | 3 | Perform a linear scan of the 2D grid map. If a node contains a '1', it is considered a root node that initiates a Breadth First Search. Add this node to a queue and change its value to '0' to indicate that it has been visited. Continue to search the neighboring nodes of the nodes in the queue iteratively until the queue is empty. 4 | 5 | ### Implementation 6 | 7 | ```cpp 8 | class Solution { 9 | void sinkIsland(vector>& grid, int startI, int startJ) { 10 | deque> pairs; 11 | pairs.push_back(make_pair(startI, startJ)); 12 | 13 | const auto tryAdd = [&grid, &pairs] (int i, int j) { 14 | if (i < 0 || j < 0) return; 15 | if (i >= grid.size() || j >= grid.front().size()) return; 16 | 17 | pairs.push_back({i, j}); 18 | }; 19 | 20 | while (!pairs.empty()) { 21 | const auto [i, j] = pairs.front(); 22 | pairs.pop_front(); 23 | 24 | if (grid[i][j] == '0') continue; 25 | grid[i][j] = '0'; 26 | 27 | tryAdd(i - 1, j); 28 | tryAdd(i + 1, j); 29 | tryAdd(i, j - 1); 30 | tryAdd(i, j + 1); 31 | } 32 | } 33 | 34 | public: 35 | int numIslands(vector>& grid) { 36 | int nIslands = 0; 37 | for (int i = 0; i < grid.size(); ++i) { 38 | for (int j = 0; j < grid.front().size(); ++j) { 39 | if (grid[i][j] == '1') { 40 | ++nIslands; 41 | sinkIsland(grid, i, j); 42 | } 43 | } 44 | } 45 | return nIslands; 46 | } 47 | }; 48 | ``` 49 | 50 | ### Complexity Analysis 51 | 52 | * Time Complexity: The time complexity is **O(M*N)**, where **M** represents the number of rows and **N** represents the number of columns. 53 | 54 | * Space Complexity: The space complexity is **O(M*N)**. -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/cpp/solution_expected.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | void sinkIsland(vector>& grid, int startI, int startJ) { 3 | deque> pairs; 4 | pairs.push_back(make_pair(startI, startJ)); 5 | 6 | const auto tryAdd = [&grid, &pairs] (int i, int j) { 7 | if (i < 0 || j < 0) return; 8 | if (i >= grid.size() || j >= grid.front().size()) return; 9 | 10 | pairs.push_back({i, j}); 11 | }; 12 | 13 | while (!pairs.empty()) { 14 | const auto [i, j] = pairs.front(); 15 | pairs.pop_front(); 16 | 17 | if (grid[i][j] == '0') continue; 18 | grid[i][j] = '0'; 19 | 20 | tryAdd(i - 1, j); 21 | tryAdd(i + 1, j); 22 | tryAdd(i, j - 1); 23 | tryAdd(i, j + 1); 24 | } 25 | } 26 | 27 | public: 28 | int numIslands(vector>& grid) { 29 | int nIslands = 0; 30 | for (int i = 0; i < grid.size(); ++i) { 31 | for (int j = 0; j < grid.front().size(); ++j) { 32 | if (grid[i][j] == '1') { 33 | ++nIslands; 34 | sinkIsland(grid, i, j); 35 | } 36 | } 37 | } 38 | return nIslands; 39 | } 40 | }; -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/description.md: -------------------------------------------------------------------------------- 1 | # Number of Islands 2 | 3 | Given a binary grid of size ```m x n``` that represents a map, where ```'1'``` signifies land and ```'0'``` signifies water, calculate and return the total number of islands present. 4 | 5 | An island is surrounded by water and is formed by connecting neightbouring lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water. 6 | \ 7 | \ 8 | \ 9 | **Example 1:** 10 | 11 | >**Input:** grid = [\ 12 | >  ["1","1","1","1","0"],\ 13 | >  ["1","1","0","1","0"],\ 14 | >  ["1","1","0","0","0"],\ 15 | >  ["0","0","0","0","0"]\ 16 | >]\ 17 | >**Output:** 1 18 | 19 | **Example 2:** 20 | 21 | >**Input:** grid = [\ 22 | >  ["1","1","0","0","0"],\ 23 | >  ["1","1","0","0","0"],\ 24 | >  ["0","0","1","0","0"],\ 25 | >  ["0","0","0","1","1"]\ 26 | >]\ 27 | >**Output:** 3 28 | 29 | 30 | **Constraints:** 31 | 32 | * ``m == grid.length`` 33 | * ``n == grid[i].length`` 34 | * ``1 <= m, n <= 300`` 35 | * ``grid[i][j] is '0' or '1'`` -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/testcases/TestCase1.test: -------------------------------------------------------------------------------- 1 | [["1","1","1","1","0"],["1","1","0","1","0"],["1","1","0","0","0"],["0","0","0","0","0"]] 2 | 1 -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/testcases/TestCase2.test: -------------------------------------------------------------------------------- 1 | [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]] 2 | 3 -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/testcases/TestCase3.test: -------------------------------------------------------------------------------- 1 | [["1","0","0","1","1","1","0","1","1","0","0","0","0","0","0","0","0","0","0","0"],["1","0","0","1","1","0","0","1","0","0","0","1","0","1","0","1","0","0","1","0"],["0","0","0","1","1","1","1","0","1","0","1","1","0","0","0","0","1","0","1","0"],["0","0","0","1","1","0","0","1","0","0","0","1","1","1","0","0","1","0","0","1"],["0","0","0","0","0","0","0","1","1","1","0","0","0","0","0","0","0","0","0","0"],["1","0","0","0","0","1","0","1","0","1","1","0","0","0","0","0","0","1","0","1"],["0","0","0","1","0","0","0","1","0","1","0","1","0","1","0","1","0","1","0","1"],["0","0","0","1","0","1","0","0","1","1","0","1","0","1","1","0","1","1","1","0"],["0","0","0","0","1","0","0","1","1","0","0","0","0","1","0","0","0","1","0","1"],["0","0","1","0","0","1","0","0","0","0","0","1","0","0","1","0","0","0","1","0"],["1","0","0","1","0","0","0","0","0","0","0","1","0","0","1","0","1","0","1","0"],["0","1","0","0","0","1","0","1","0","1","1","0","1","1","1","0","1","1","0","0"],["1","1","0","1","0","0","0","0","1","0","0","0","0","0","0","1","0","0","0","1"],["0","1","0","0","1","1","1","0","0","0","1","1","1","1","1","0","1","0","0","0"],["0","0","1","1","1","0","0","0","1","1","0","0","0","1","0","1","0","0","0","0"],["1","0","0","1","0","1","0","0","0","0","1","0","0","0","1","0","1","0","1","1"],["1","0","1","0","0","0","0","0","0","1","0","0","0","1","0","1","0","0","0","0"],["0","1","1","0","0","0","1","1","1","0","1","0","1","0","1","1","1","1","0","0"],["0","1","0","0","0","0","1","1","0","0","1","0","1","0","0","1","0","0","1","1"],["0","0","0","0","0","0","1","1","1","1","0","1","0","0","0","1","1","0","0","0"]] 2 | 58 -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/testcases/TestCase4.test: -------------------------------------------------------------------------------- 1 | [["0","1","0","0","1","1","1","0","0","0","0","0","1","0","0","0","0","1","0","1"],["1","0","1","0","0","1","1","0","0","1","0","1","0","1","0","1","1","0","0","0"],["0","1","0","0","0","1","1","1","1","0","0","0","0","0","1","1","1","1","0","1"],["1","1","0","0","0","1","1","0","0","0","1","1","1","0","0","1","0","1","1","0"],["0","1","0","1","1","0","1","0","0","0","1","0","0","1","0","0","0","0","0","1"],["1","0","0","1","0","1","0","0","0","1","1","0","1","0","0","1","0","0","0","0"],["1","0","0","0","1","1","0","0","0","0","0","1","0","0","1","0","0","0","0","1"],["1","0","0","0","1","0","1","1","1","0","1","0","1","1","1","1","0","0","0","1"],["1","0","0","1","0","0","0","1","0","0","0","0","0","0","0","0","0","1","0","1"],["0","0","0","1","0","1","1","1","1","1","1","1","1","1","0","0","0","0","1","0"],["1","0","1","0","1","0","0","1","1","1","0","1","1","0","0","1","1","0","0","0"],["0","1","0","0","1","0","0","0","0","0","0","1","1","1","1","0","0","0","1","0"],["1","0","0","0","1","1","1","0","1","0","0","0","1","0","1","0","1","0","0","1"],["0","0","0","0","1","0","1","1","0","1","0","1","0","1","1","1","1","0","0","0"],["0","1","1","0","0","0","0","1","0","0","1","1","1","0","0","1","1","0","1","0"],["1","0","1","1","1","1","1","1","0","1","1","0","1","0","0","1","0","0","0","1"],["1","0","0","0","1","0","1","0","0","1","0","1","0","0","1","0","0","1","1","1"],["0","0","1","0","0","0","0","1","0","0","1","1","0","1","1","1","0","0","0","0"],["0","0","1","0","0","0","0","0","0","1","1","0","1","0","1","0","0","0","1","1"],["1","0","0","0","1","0","1","1","1","0","0","1","0","1","0","1","1","0","0","0"]] 2 | 55 -------------------------------------------------------------------------------- /data/problems/NumberOfIslands/testcases/TestCase5.test: -------------------------------------------------------------------------------- 1 | [["1","1","1","1","1","0","1","1","1","1","1","1","1","1","1","0","1","0","1","1"],["0","1","1","1","1","1","1","1","1","1","1","1","1","0","1","1","1","1","1","0"],["1","0","1","1","1","0","0","1","1","0","1","1","1","1","1","1","1","1","1","1"],["1","1","1","1","0","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"],["1","0","0","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"],["1","0","1","1","1","1","1","1","0","1","1","1","0","1","1","1","0","1","1","1"],["0","1","1","1","1","1","1","1","1","1","1","1","0","1","1","0","1","1","1","1"],["1","1","1","1","1","1","1","1","1","1","1","1","0","1","1","1","1","0","1","1"],["1","1","1","1","1","1","1","1","1","1","0","1","1","1","1","1","1","1","1","1"],["1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"],["0","1","1","1","1","1","1","1","0","1","1","1","1","1","1","1","1","1","1","1"],["1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"],["1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"],["1","1","1","1","1","0","1","1","1","1","1","1","1","0","1","1","1","1","1","1"],["1","0","1","1","1","1","1","0","1","1","1","0","1","1","1","1","0","1","1","1"],["1","1","1","1","1","1","1","1","1","1","1","1","0","1","1","1","1","1","1","0"],["1","1","1","1","1","1","1","1","1","1","1","1","1","0","1","1","1","1","0","0"],["1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"],["1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"],["1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1","1"]] 2 | 1 -------------------------------------------------------------------------------- /data/problems/TwoSum/cpp/solution.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector twoSum(vector& nums, int target) { 4 | } 5 | }; -------------------------------------------------------------------------------- /data/problems/TwoSum/cpp/solution.md: -------------------------------------------------------------------------------- 1 | ### Approach 2 | 3 | Iterate through the array once. For each element, check if the target minus the current element exists in the hash table. If it does, we have found a valid pair of numbers. If not, we add the current element to the hash table. 4 | 5 | ### Implementation 6 | 7 | ```cpp 8 | class Solution { 9 | public: 10 | vector twoSum(vector& nums, int target) { 11 | unordered_map visited; 12 | 13 | for (int i = 0; i < nums.size(); ++i) { 14 | const int curElement = nums[i]; 15 | const int delta = target - curElement; 16 | 17 | const auto it = visited.find(delta); 18 | if (it != visited.end()) { 19 | return {it->second, i}; 20 | } 21 | 22 | visited.insert({curElement, i}); 23 | continue; 24 | 25 | } 26 | return {}; 27 | } 28 | }; 29 | ``` 30 | 31 | ### Complexity Analysis 32 | 33 | * Time complexity: **O(n)**.
34 | We traverse the **nums** containing **n** elements only once. Each lookup in the hash map costs only **O(1)** time. 35 | 36 | * Space complexity: **O(n)**.
37 | The extra space that is required depends on the number of items stored in the hash map. In this case it's **n** elements. -------------------------------------------------------------------------------- /data/problems/TwoSum/cpp/solution_expected.cpp: -------------------------------------------------------------------------------- 1 | class Solution { 2 | public: 3 | vector twoSum(vector& nums, int target) { 4 | unordered_map visited; 5 | 6 | for (int i = 0; i < nums.size(); ++i) { 7 | const int curElement = nums[i]; 8 | const int delta = target - curElement; 9 | 10 | const auto it = visited.find(delta); 11 | if (it != visited.end()) { 12 | return {it->second, i}; 13 | } 14 | 15 | visited.insert({curElement, i}); 16 | continue; 17 | 18 | } 19 | return {}; 20 | } 21 | }; -------------------------------------------------------------------------------- /data/problems/TwoSum/description.md: -------------------------------------------------------------------------------- 1 | # Two Sum 2 | 3 | You are given an array of integers ``nums`` and an integer ``target``, return indices of the two numbers that they add up to the ``target``. 4 | 5 | You may assume that the array has exactly one solution, and you may not use the same interger twice. 6 | 7 | You can return the answer in any order. 8 | \ 9 | \ 10 | \ 11 | **Example 1:** 12 | 13 | >**Input:** nums = [3,7,11,25], target = 10\ 14 | >**Output:** [0,1]\ 15 | >**Explanation:** Because nums[0] + nums[1] == 10, we return [0, 1]. 16 | 17 | **Example 2:** 18 | 19 | >**Input:** nums = [1,3,4], target = 7\ 20 | >**Output:** [1,2] 21 | 22 | **Example 3:** 23 | 24 | >**Input:** nums = [1,1], target = 2\ 25 | >**Output:** [0,1] 26 | 27 | **Constraints:** 28 | 29 | * ``2 <= nums.length <= 104`` 30 | * ``-109 <= nums[i] <= 109`` 31 | * ``-109 <= target <= 109`` 32 | * **Only one valid answer exists.** -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase1.test: -------------------------------------------------------------------------------- 1 | [3,7,11,25] 2 | 10 3 | [0,1] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase10.test: -------------------------------------------------------------------------------- 1 | [2222222,2222222] 2 | 4444444 3 | [0, 1] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase11.test: -------------------------------------------------------------------------------- 1 | [1,6142,8192,10239] 2 | 18431 3 | [2, 3] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase12.test: -------------------------------------------------------------------------------- 1 | [-10,-1,-18,-19] 2 | -19 3 | [1, 2] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase13.test: -------------------------------------------------------------------------------- 1 | [-10,7,19,15] 2 | 9 3 | [0, 2] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase14.test: -------------------------------------------------------------------------------- 1 | [0,3,-3,4,-1] 2 | -1 3 | [0, 4] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase15.test: -------------------------------------------------------------------------------- 1 | [-18,12,3,0] 2 | -6 3 | [0, 1] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase2.test: -------------------------------------------------------------------------------- 1 | [1,3,4] 2 | 7 3 | [1,2] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase3.test: -------------------------------------------------------------------------------- 1 | [1, 1] 2 | 2 3 | [0, 1] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase4.test: -------------------------------------------------------------------------------- 1 | [3, 2, 3] 2 | 6 3 | [0, 2] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase5.test: -------------------------------------------------------------------------------- 1 | [2,5,5,11] 2 | 10 3 | [1, 2] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase6.test: -------------------------------------------------------------------------------- 1 | [0,4,3,0] 2 | 0 3 | [0, 3] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase7.test: -------------------------------------------------------------------------------- 1 | [-3, 4, 3, 90] 2 | 0 3 | [0, 2] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase8.test: -------------------------------------------------------------------------------- 1 | [-1,-2,-3,-4,-5] 2 | -8 3 | [2, 4] -------------------------------------------------------------------------------- /data/problems/TwoSum/testcases/TestCase9.test: -------------------------------------------------------------------------------- 1 | [5,75,25] 2 | 100 3 | [1, 2] -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Welcome to the OpenLeetCode Project! This guide will describe how to add support for a new language. 4 | 5 | ## Table of Contents 6 | 7 | - [Getting Started](#getting-started) 8 | - [Table of Contents](#table-of-contents) 9 | - [Adding support for a new language](#adding-support-for-a-new-language) 10 | - [Building and Running the Testcases](#building-and-running-the-testcases) 11 | - [Setup](#setup) 12 | - [Build and Run](#build-and-run) 13 | - [The Results](#the-results) 14 | - [Schema Validation](#schema-validation) 15 | 16 | ## Adding support for a new language 17 | 18 | The support for each language should be self contained within its own folder, located in ``problems_build/problems/language/``. For example, for C++ there is a folder ``cpp`` as shown below: 19 | 20 | - problem_builds 21 | - problems 22 | - launguage 23 | - cpp 24 | - java 25 | - python 26 | 27 | Any newly added language must adhere to the guidelines in the following sections. 28 | 29 | ## Building and Running the Testcases 30 | 31 | #### Setup 32 | When ``openleetcode`` is executed, two of the specified parameters are ``problem`` and ``language``. In this example, let's assume the problem is ``TwoSum`` and the language is ``Cpp``. 33 | 34 | ``openleetcode`` copies all files from ``problems_build/language/cpp`` into the ``problems_build/problems/TwoSum/cpp``, including source files and CMake files. The folder already contains ``solution.cpp`` in the case of C++ laguage. 35 | 36 | #### Build and Run 37 | Once the copying is complete, CMake is executed to build the binary and subsequently run it against the specified test cases from ``problems_build/problems/TwoSum/testcases.``. 38 | 39 | #### The Results 40 | The results of this run will be stored inside ``problems_build/problems/TwoSum/cpp/testcase_output/TwoSum-.results``. 41 | 42 | 43 | ## Schema Validation 44 | Once ``openleetcode`` executes the built binary against the testcases, it then picks up the results file and adds two properties: ``stderr`` and ``stdout`` then write it back to its original location. The ``.result`` file is in a json format specified by a validation schema that is used to validate this results file. The validation schema can be found in ``testcase_output/results_validation_schema.json``. 45 | 46 | Once ``openleetcode`` executes the built binary against the test cases, it then retrieves the results file and adds two properties: ``stderr`` and ``stdout`` before writing it back to its original location. The ``.result`` file is in JSON format, as specified by a validation schema in ``testcase_output/results_validation_schema.json``. This schema is also used to validating the results file. 47 | 48 | Note: Schema validation ensures consistent formatting of the results file for multiple languages. A UI component will rely on the results file being in a consistent format, regardless of the programming language. -------------------------------------------------------------------------------- /install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | python "%~dp0install.py" %* -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import fnmatch 3 | import os 4 | import shutil 5 | import subprocess 6 | 7 | 8 | if os.name == "nt": 9 | default_prefix = "C:\\Program Files" 10 | script_sufix = ".bat" 11 | else: 12 | default_prefix = "/usr/local" 13 | script_sufix = ".sh" 14 | 15 | def installUI(src_dir, build_dir, install_dir): 16 | ui_build_dir = os.path.join(build_dir, "ui") 17 | if os.path.exists(ui_build_dir): 18 | shutil.rmtree(ui_build_dir) 19 | 20 | src_dir = os.path.join(src_dir, "src", "ui") 21 | shutil.copytree(src_dir, ui_build_dir) 22 | 23 | cmd_npm_install = "npm install" 24 | cmd_npx_build = f"npx electron-packager . --out={install_dir} --overwrite" 25 | 26 | print(f"Installing OpenLeetCodeUI dependencies...") 27 | result = subprocess.run(cmd_npm_install, 28 | stdout=subprocess.PIPE, 29 | stderr=subprocess.STDOUT, 30 | shell=True, 31 | cwd=ui_build_dir) 32 | if result.returncode != 0: 33 | print(result.stdout.decode('utf-8')) 34 | raise RuntimeError(f"Error running the command: {cmd_npm_install}") 35 | 36 | print(f"Building OpenLeetCodeUI...") 37 | result = subprocess.run(cmd_npx_build, 38 | stdout=subprocess.PIPE, 39 | stderr=subprocess.STDOUT, 40 | shell=True, 41 | cwd=ui_build_dir) 42 | if result.returncode != 0: 43 | print(result.stdout.decode('utf-8')) 44 | raise RuntimeError(f"Error running the command: {cmd_npx_build}") 45 | 46 | openleetcode_ui_dir = os.path.join( 47 | install_dir, 48 | [ 49 | d for d in os.listdir(install_dir) 50 | if fnmatch.fnmatch(d, "OpenLeetCodeUI-*-*") 51 | ][0] 52 | ) 53 | 54 | new_openleetcode_ui_dir = os.path.join(install_dir, "OpenLeetCodeUI") 55 | if os.path.exists(new_openleetcode_ui_dir): 56 | shutil.rmtree(new_openleetcode_ui_dir) 57 | 58 | os.rename(openleetcode_ui_dir, new_openleetcode_ui_dir) 59 | 60 | if os.name == "nt": 61 | script = "openleetcodeui.bat" 62 | elif os.name == "posix": 63 | script = "openleetcodeui.sh" 64 | else: 65 | raise RuntimeError("Unsupported OS") 66 | 67 | print(f"Installing OpenLeetCodeUI...") 68 | script = os.path.join(src_dir, script) 69 | if not os.path.exists(script): 70 | raise FileNotFoundError(f"No file found at {script}") 71 | shutil.copy(script, install_dir) 72 | 73 | print("OpenLeetCodeUI installed at", new_openleetcode_ui_dir) 74 | 75 | def installOpenLeetCode(src_dir, install_dir): 76 | print(f"Installing OpenLeetCode...") 77 | data_dir = os.path.join(src_dir, 'data') 78 | if not os.path.exists(data_dir): 79 | raise FileNotFoundError(f"No data directory found at {data_dir}") 80 | 81 | shutil.copytree(data_dir, install_dir, dirs_exist_ok=True) 82 | 83 | schema_file = os.path.join(src_dir, "src", "schema", "results_validation_schema.json") 84 | if not os.path.exists(schema_file): 85 | raise FileNotFoundError(f"No schema file found at {schema_file}") 86 | shutil.copy(schema_file, install_dir) 87 | 88 | if os.name == "nt": 89 | script = "openleetcode.bat" 90 | elif os.name == "posix": 91 | script = "openleetcode.sh" 92 | else: 93 | raise RuntimeError("Unsupported OS") 94 | 95 | app_files = [ 96 | "functionextractor.py", 97 | "logger.py", 98 | "openleetcode.py", 99 | "resultsvalidator.py", 100 | "testrunner.py", 101 | script 102 | ] 103 | 104 | for file in app_files: 105 | file = os.path.join(src_dir, "src", "app", file) 106 | if not os.path.exists(file): 107 | raise FileNotFoundError(f"No file found at {file}") 108 | shutil.copy(file, install_dir) 109 | 110 | print("OpenLeetCode installed at", install_dir) 111 | 112 | def main(): 113 | print("OpenLeetCode Installer") 114 | 115 | parser = argparse.ArgumentParser(description="Installer for OpenLeetCode") 116 | parser.add_argument("--build_dir", help="Build directory", default="build") 117 | parser.add_argument("--prefix", help="Installation prefix", default=default_prefix) 118 | parser.add_argument("--enable_ui", help="Enable UI installation", action="store_true", default=False) 119 | 120 | args = parser.parse_args() 121 | 122 | src_dir = os.path.dirname(os.path.abspath(__file__)) 123 | build_dir = os.path.abspath(args.build_dir) 124 | if not os.path.exists(build_dir): 125 | os.makedirs(build_dir) 126 | 127 | install_dir = os.path.abspath(os.path.join(args.prefix, "OpenLeetCode")) 128 | if not os.path.exists(install_dir): 129 | os.makedirs(install_dir) 130 | 131 | installOpenLeetCode(src_dir, install_dir) 132 | 133 | if args.enable_ui: 134 | installUI(src_dir, build_dir, install_dir) 135 | 136 | print( 137 | f"Installation complete!\nYou can now run OpenLeetCode using " 138 | f"openleetcode{script_sufix}" 139 | f"{ ' or openleetcodeui' + script_sufix if args.enable_ui else ''}." 140 | ) 141 | 142 | if __name__ == '__main__': 143 | main() -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 "$(dirname "$0")/install.py" "$@" -------------------------------------------------------------------------------- /src/app/functionextractor.py: -------------------------------------------------------------------------------- 1 | # functionextractor.py 2 | 3 | import re 4 | 5 | OUTPUT_FILE_PREFIX = "#include \"solutionwrapper.h\"\n\nauto fun = &Solution::" 6 | OUTPUT_FILE_SUFFIX = ";" 7 | 8 | def _get_function_name(content): 9 | match = re.search(r'\bpublic:\s+([\w:<>,\s]+)\s+(\w+)\(', content) 10 | if match: 11 | return_type, function_name = match.groups() 12 | return function_name; 13 | else: 14 | return None 15 | 16 | def get_function(input_file_name, output_file_name): 17 | with open(input_file_name, 'r') as file: 18 | content = file.read() 19 | if not content: 20 | print(f"Could not read the content of {input_file_name}") 21 | return None 22 | function_name = _get_function_name(content) 23 | if not function_name: 24 | print(f"Could not find the function name in {input_file_name}") 25 | return None 26 | with open(output_file_name, 'w') as file: 27 | ret = file.write(f"{OUTPUT_FILE_PREFIX}" + 28 | f"{function_name}" + 29 | f"{OUTPUT_FILE_SUFFIX}") 30 | if not ret: 31 | print(f"Could not write to {output_file_name}") 32 | return None 33 | return function_name -------------------------------------------------------------------------------- /src/app/logger.py: -------------------------------------------------------------------------------- 1 | # logger.py 2 | 3 | _verbose = False 4 | 5 | def red(text): 6 | return f"\033[91m{text}\033[0m" 7 | 8 | def green(text): 9 | return f"\033[92m{text}\033[0m" 10 | 11 | def set_verbose(verbose: bool): 12 | global _verbose 13 | _verbose = verbose 14 | 15 | def log(message: str): 16 | if _verbose: 17 | print(message) 18 | 19 | def logResults(results): 20 | duration = ("Unknown" if "duration_ms" not in results 21 | else str(results["duration_ms"]) + "ms") 22 | print("Status: " + str(results["status"]) + "\n" + 23 | "Duration: " + duration) -------------------------------------------------------------------------------- /src/app/openleetcode.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | python "%~dp0openleetcode.py" %* -------------------------------------------------------------------------------- /src/app/openleetcode.py: -------------------------------------------------------------------------------- 1 | # openleetcode.py 2 | 3 | import argparse 4 | import json 5 | import logger 6 | import os 7 | import re 8 | import shutil 9 | import subprocess 10 | import sys 11 | 12 | import testrunner 13 | import functionextractor 14 | import resultsvalidator 15 | 16 | TESTCAST_OUTPUT_DIR = "testcase_output" 17 | VALIDATION_SCHEMA_FILE_NAME = "results_validation_schema.json" 18 | 19 | def naturalSort(s): 20 | return [int(text) if text.isdigit() else text.lower() 21 | for text in re.split('([0-9]+)', s)] 22 | 23 | def run(command, directory): 24 | logger.log("Running command: " + command) 25 | result = subprocess.run(command, 26 | stdout=subprocess.PIPE, 27 | stderr=subprocess.STDOUT, 28 | shell=True, 29 | cwd=directory) 30 | logger.log(result.stdout.decode('utf-8')) 31 | if result.returncode != 0: 32 | logger.log(f"Error running the command: {command}") 33 | return result.returncode 34 | 35 | def getExeExtension(): 36 | if os.name == 'posix': 37 | return "" 38 | elif os.name == 'nt': 39 | return ".exe" 40 | else: 41 | return "" 42 | 43 | def main(): 44 | parser = argparse.ArgumentParser( 45 | description="OpenLeetCode problem builder. This script builds and " 46 | "tests a LeetCode-like problems locally. Currently, it " 47 | "only supports C++ language, but it can be extended to " 48 | "support other languages.") 49 | parser.add_argument( 50 | "--language", "-l", 51 | choices=['cpp'], 52 | default="cpp", 53 | help="The programming language.") 54 | parser.add_argument( 55 | "--list-problems", 56 | action="store_true", 57 | default=False, 58 | help="List problems.") 59 | parser.add_argument( 60 | "--list-testcases", 61 | action="store_true", 62 | default=False, 63 | help="List testcases for a problem specified with '--problem' option.") 64 | parser.add_argument( 65 | "--problem", "-p", 66 | metavar='problem_name', 67 | default="TwoSum", 68 | type=str, 69 | help="Name of the problem to build and test. Default: TwoSum. Use " 70 | "--list-problems to list all problems.") 71 | parser.add_argument( 72 | "--problem_builds_dir", "-d", 73 | metavar='dir', 74 | type=str, 75 | help=("Specifies the directory with the problems. Typically, this is " 76 | "'./problem_builds'. If not provided, the script defaults to " 77 | "'./problem_builds' in the same directory as the executable.")) 78 | parser.add_argument( 79 | "--run-expected-tests", "-r", 80 | action="store_true", 81 | default=False, 82 | help="Run the expected solution. Default: False.") 83 | parser.add_argument( 84 | "--testcase", "-t", 85 | metavar='testcase_name', 86 | default="All", 87 | type=str, 88 | help="Name of the testcase to run. '--testcase All' will run all " 89 | "testcases. Default: All.") 90 | parser.add_argument("--verbose", "-v", 91 | action="store_true", 92 | default=False, 93 | help="Print verbose output") 94 | 95 | args = parser.parse_args() 96 | 97 | logger.set_verbose(args.verbose) 98 | 99 | openleetcode_dir = os.path.dirname(os.path.realpath(__file__)) 100 | if args.problem_builds_dir is None: 101 | 102 | problem_builds_dir = openleetcode_dir 103 | else: 104 | problem_builds_dir = os.path.abspath(args.problem_builds_dir) 105 | 106 | if not os.path.isdir(problem_builds_dir): 107 | print(logger.red(f"The problems directory {problem_builds_dir} " 108 | f"does not exist.")) 109 | sys.exit(1) 110 | 111 | problems_dir = os.path.abspath(os.path.join(problem_builds_dir, "problems")) 112 | 113 | if not os.path.isdir(problems_dir): 114 | print(logger.red(f"The problems directory {problems_dir} does not exist.")) 115 | sys.exit(1) 116 | 117 | if (args.list_problems): 118 | print("List of problems:") 119 | for problem in os.listdir(problems_dir): 120 | print(problem) 121 | sys.exit(1) 122 | 123 | problem_dir = os.path.join(problem_builds_dir, "problems", args.problem) 124 | if not os.path.isdir(problem_dir): 125 | print(logger.red(f"The problem directory {problem_dir} does not exist. " 126 | f"Check the problem_builds_dir and problem " 127 | f"arguments.")) 128 | sys.exit(1) 129 | 130 | src_template_dir = os.path.join(problem_builds_dir, "languages", 131 | args.language) 132 | if not os.path.isdir(src_template_dir): 133 | print(logger.red(f"The source template directory {src_template_dir} " 134 | f"does not exist. This usually happen when the " 135 | f"language is not supported. Check the language " 136 | f"argument.")) 137 | sys.exit(1) 138 | 139 | src_dir = os.path.abspath(os.path.join(problem_dir, args.language)) 140 | if not os.path.isdir(src_dir): 141 | print(logger.red(f"The source directory {src_dir} does not exist. This" 142 | f" usually happen when the language is not supported." 143 | f" Check the language argument.")) 144 | sys.exit(1) 145 | 146 | build_dir = os.path.abspath(os.path.join(src_dir, "build")) 147 | 148 | testcases_dir = os.path.abspath(os.path.join(src_dir, "../testcases")) 149 | 150 | if (args.list_testcases): 151 | print("List of testcases:") 152 | for testcase in sorted(os.listdir(testcases_dir), key=naturalSort): 153 | print(testcase.replace(".test", "")) 154 | sys.exit(0) 155 | 156 | if (args.testcase != "All"): 157 | testcase_file = os.path.join(testcases_dir, args.testcase + ".test") 158 | if not os.path.isfile(testcase_file): 159 | print(logger.red(f"The testcase file {testcase_file} does not " 160 | "exist.")) 161 | sys.exit(1) 162 | 163 | print("Running OpenLeetCode on problem " + args.problem + 164 | " for testcase " + args.testcase + " in language " + args.language) 165 | logger.log(f"Building the problem {args.problem} " 166 | f"in {args.language} language.") 167 | logger.log(f"OpenLeetCode directory: {openleetcode_dir}") 168 | logger.log(f"Problem directory: {problem_dir}") 169 | logger.log(f"Problems directory: {problems_dir}") 170 | logger.log(f"Problem builds directory: {problem_builds_dir}") 171 | logger.log(f"Source directory: {src_dir}") 172 | logger.log(f"Build directory: {build_dir}") 173 | logger.log(f"Template source directory: {src_template_dir}") 174 | logger.log(f"Testcases directory: {testcases_dir}") 175 | 176 | # Copy the template source files to the problem directory if they don't 177 | # already exist 178 | for file in os.listdir(src_template_dir): 179 | src_file = os.path.join(src_template_dir, file) 180 | dst_file = os.path.join(src_dir, file) 181 | if not os.path.isfile(dst_file): 182 | logger.log(f"Copying {src_file} to {dst_file}") 183 | shutil.copy(src_file, dst_file) 184 | 185 | solution_file_name = os.path.join(src_dir, "solution.cpp") 186 | solution_function_file_name = os.path.join(src_dir, "solutionfunction.h") 187 | if not os.path.isfile(solution_function_file_name): 188 | ret = functionextractor.get_function(solution_file_name, 189 | solution_function_file_name) 190 | logger.log(f"Extracted function name: {ret}") 191 | logger.log(f"Writing the function name to {solution_function_file_name}") 192 | 193 | if not ret: 194 | print(logger.red(f"Could not extract the function name from " 195 | f"{solution_file_name}.")) 196 | sys.exit(1) 197 | 198 | validation_schema_file = os.path.abspath( 199 | os.path.join(openleetcode_dir, VALIDATION_SCHEMA_FILE_NAME)) 200 | if not os.path.isfile(validation_schema_file): 201 | print(logger.red(f"The validation schema file {validation_schema_file} " 202 | f"does not exist.")) 203 | sys.exit(1) 204 | 205 | with open(validation_schema_file, 'r') as f: 206 | try: 207 | schema = json.load(f) 208 | except Exception as e: 209 | print(logger.red(f"Error reading the validation schema file. " 210 | f"error={e}")) 211 | sys.exit(1) 212 | resultsvalidator.set_schema(schema) 213 | 214 | if not os.path.isfile(os.path.join(build_dir, "CMakeCache.txt")): 215 | print("CMakeCache.txt does not exist. Running CMake to configure the ") 216 | if run(f"cmake -B {build_dir} -DCMAKE_BUILD_TYPE=Debug", src_dir) != 0: 217 | print(logger.red(f"CMake failed!")) 218 | sys.exit(1) 219 | else: 220 | print("CMakeCache.txt exists. Skipping CMake configuration.") 221 | 222 | if run(f"cmake --build . --config Debug -j", build_dir) != 0: 223 | print(logger.red("Build failed!")) 224 | sys.exit(1) 225 | 226 | if run(f"cmake --install . --config Debug -v", build_dir) != 0: 227 | print(logger.red("Cmake install failed!")) 228 | sys.exit(1) 229 | 230 | bin_dir = os.path.abspath(os.path.join(src_dir, "bin")) 231 | 232 | if not os.path.isdir(bin_dir): 233 | print(logger.red(f"The bin directory {bin_dir} does not exist. Check " 234 | "the problem_builds_dir and problem arguments.")) 235 | sys.exit(1) 236 | 237 | exe_file_name = ( 238 | "solution_expected_" 239 | if args.run_expected_tests 240 | else "solution_" 241 | ) 242 | 243 | exe_file = os.path.abspath(os.path.join( 244 | bin_dir, f"{exe_file_name}{args.language}" + getExeExtension())) 245 | 246 | if not os.path.isfile(exe_file): 247 | print(logger.red(f"The file {exe_file} does not exist. Check the " 248 | f"problem_builds_dir and problem arguments.")) 249 | sys.exit(1) 250 | 251 | if not os.path.isdir(testcases_dir): 252 | print(logger.red(f"The test directory {testcases_dir} does not exist. " 253 | f"Check the problem_builds_dir and problem " 254 | f"arguments.")) 255 | sys.exit(1) 256 | 257 | if not os.path.isdir(TESTCAST_OUTPUT_DIR): 258 | os.mkdir(TESTCAST_OUTPUT_DIR) 259 | 260 | output_file_dir = os.path.abspath(os.path.join(TESTCAST_OUTPUT_DIR)) 261 | 262 | ret, error_message = testrunner.runTests(exe_file, 263 | testcases_dir, 264 | output_file_dir, 265 | args.problem, 266 | args.testcase) 267 | 268 | if ret != 0: 269 | print(logger.red(f"Tests failed! Error: {error_message}")) 270 | sys.exit(1) 271 | 272 | if __name__ == "__main__": 273 | main() -------------------------------------------------------------------------------- /src/app/openleetcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 "$(dirname "$0")/openleetcode.py" "$@" -------------------------------------------------------------------------------- /src/app/resultsvalidator.py: -------------------------------------------------------------------------------- 1 | # resultsvalidator.py 2 | 3 | import json 4 | 5 | from jsonschema import validate 6 | 7 | import logger 8 | 9 | def set_schema(schema): 10 | global _validation_schema 11 | _validation_schema = schema 12 | 13 | def validateResults(results): 14 | try: 15 | validate(instance=results, schema=_validation_schema) 16 | logger.log("Results JSON is valid against the schema.") 17 | except Exception as e: 18 | return -1, f"JSON is not valid against the schema. error={e}" 19 | return 0, "" 20 | 21 | def validateResults(results): 22 | if not _validation_schema: 23 | raise ValueError("Validation schema is not set. Please call " 24 | "set_schema() before calling validateResults().") 25 | try: 26 | validate(instance=results, schema=_validation_schema) 27 | logger.log("Results JSON is valid against the schema.") 28 | except Exception as e: 29 | return -1, f"JSON is not valid against the schema. error={e}" 30 | return 0, "" -------------------------------------------------------------------------------- /src/app/testrunner.py: -------------------------------------------------------------------------------- 1 | # testrunner.py 2 | 3 | import json 4 | import os 5 | import re 6 | import subprocess 7 | 8 | from datetime import datetime 9 | 10 | import logger 11 | import resultsvalidator 12 | 13 | # In the future this might depend on the language (e.g. for pyhon, use 15s) 14 | PROBLEM_LTE_S = 3 15 | 16 | def sort_key(file): 17 | # Extract the number from the filename 18 | match = re.search(r'\d+', file) 19 | if match: 20 | return int(match.group()) 21 | return file 22 | 23 | def printSuccess(testcase_name): 24 | print(logger.green(testcase_name + ": SUCCESS")) 25 | 26 | def printFailure(testcase_name, message): 27 | print(logger.red(f"{testcase_name}: FAILURE - {message}")) 28 | 29 | def getTestcaseName(file): 30 | return file[:-5] 31 | 32 | def runTests(exec_filename, 33 | testcase_dir, 34 | output_dir_name, 35 | problem_name, 36 | testcase_name): 37 | date = datetime.now().strftime("%Y-%m-%d-%H-%M-%S") 38 | test_restults_filename = os.path.join(output_dir_name, 39 | f"{problem_name}-{date}.results") 40 | 41 | if not os.path.isdir(output_dir_name): 42 | os.mkdir(output_dir_name) 43 | 44 | if not os.path.isfile(exec_filename): 45 | return 1, f"The file {exec_filename} does not exist." 46 | if True and not os.path.isdir(testcase_dir): 47 | return 1, f"The testcase directory {testcase_dir} does not exist." 48 | 49 | try: 50 | command = ( 51 | f"{exec_filename} " 52 | f"{testcase_dir} " 53 | f"{test_restults_filename} " 54 | f"{testcase_name}" 55 | ) 56 | logger.log(f"Running command: {command}") 57 | subprocess_obj = subprocess.run(command, 58 | shell=True, 59 | cwd=os.path.dirname(exec_filename), 60 | timeout=PROBLEM_LTE_S, 61 | stdout=subprocess.PIPE, 62 | stderr=subprocess.PIPE) 63 | except subprocess.TimeoutExpired: 64 | return 1, f"Time Limit Exceeded" 65 | except Exception as e: 66 | return 1, f"Error running the test, error={e}" 67 | 68 | if not os.path.isfile(test_restults_filename): 69 | jsonResultsObj = { 70 | "status": "Failed", 71 | "stderr": subprocess_obj.stderr.decode('utf-8'), 72 | "stdout": subprocess_obj.stdout.decode('utf-8'), 73 | "errorcode": hex(subprocess_obj.returncode) 74 | } 75 | with open(test_restults_filename, 'w') as f: 76 | json.dump(jsonResultsObj, f, indent=4) 77 | 78 | # read test_restults_filename into a json object 79 | with open(test_restults_filename, 'r') as f: 80 | results = json.load(f) 81 | results["stderr"] = subprocess_obj.stderr.decode('utf-8') 82 | if testcase_name != "All": 83 | results["stdout"] = subprocess_obj.stdout.decode('utf-8') 84 | 85 | with open(test_restults_filename, 'w') as f: 86 | json.dump(results, f, indent=4) 87 | 88 | print(f"Results written to {test_restults_filename}") 89 | 90 | ret, message = resultsvalidator.validateResults(results) 91 | if ret != 0: 92 | return ret, message 93 | 94 | logger.logResults(results) 95 | 96 | return subprocess_obj.returncode, subprocess_obj.stderr.decode('utf-8') -------------------------------------------------------------------------------- /src/schema/results_validation_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "definitions": { 3 | "duration_ms_schema": { 4 | "type": "integer" 5 | }, 6 | "status_schema": { 7 | "type": "string" 8 | }, 9 | "stderr_schema": { 10 | "type": "string" 11 | }, 12 | "stdout_schema": { 13 | "type": "string" 14 | }, 15 | "testcase_filter_name_schema": { 16 | "type": "string" 17 | }, 18 | "test_schema": { 19 | "type": "object", 20 | "properties": { 21 | "actual": { 22 | "anyOf": [ 23 | {"type": "string"}, 24 | {"type": "integer"}, 25 | {"type": "array"}, 26 | {"type": "boolean"} 27 | ] 28 | }, 29 | "expected": { 30 | "anyOf": [ 31 | {"type": "string"}, 32 | {"type": "integer"}, 33 | {"type": "array"}, 34 | {"type": "boolean"} 35 | ] 36 | }, 37 | "reason": { 38 | "type": "string" 39 | }, 40 | "status": { 41 | "type": "string" 42 | }, 43 | "testcase_file": { 44 | "type": "string" 45 | }, 46 | "testcase_name": { 47 | "type": "string" 48 | } 49 | }, 50 | "required": ["status", "testcase_name"], 51 | "if": { 52 | "properties": { 53 | "status": { "const": "Failed" } 54 | } 55 | }, 56 | "then": { 57 | "required": ["actual", "expected", "reason", "testcase_file"] 58 | }, 59 | "else": { 60 | "if": { 61 | "properties": { 62 | "status": { "const": "Skipped" } 63 | } 64 | }, 65 | "then": { 66 | "required": ["actual"] 67 | } 68 | } 69 | }, 70 | "tests_schema": { 71 | "type": "array", 72 | "minItems": 1, 73 | "items": { 74 | "$ref": "#/definitions/test_schema" 75 | } 76 | } 77 | }, 78 | "type": "object", 79 | "properties": { 80 | "duration_ms": { 81 | "$ref": "#/definitions/duration_ms_schema" 82 | }, 83 | "status": { 84 | "$ref": "#/definitions/status_schema" 85 | }, 86 | "tests": { 87 | "$ref": "#/definitions/tests_schema" 88 | }, 89 | "stderr": { 90 | "$ref": "#/definitions/stderr_schema" 91 | }, 92 | "stdout": { 93 | "$ref": "#/definitions/stdout_schema" 94 | }, 95 | "testcase_filter_name": { 96 | "$ref": "#/definitions/testcase_filter_name_schema" 97 | } 98 | }, 99 | "required": ["status"], 100 | "allOf": [ 101 | { 102 | "if": { 103 | "properties": { 104 | "errorcode": {} 105 | }, 106 | "required": ["errorcode"] 107 | }, 108 | "then": { 109 | "required": ["stdout", "stderr"] 110 | }, 111 | "else": { 112 | "required": ["duration_ms", "tests", "stderr"], 113 | "if": { 114 | "not": { 115 | "properties": { 116 | "testcase_filter_name": { "pattern": "^All$", "flags": "i" } 117 | } 118 | } 119 | }, 120 | "then": { 121 | "required": ["stdout"] 122 | } 123 | } 124 | } 125 | ] 126 | } -------------------------------------------------------------------------------- /src/ui/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | out 3 | filePaths.tmp -------------------------------------------------------------------------------- /src/ui/directory-manager.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | global.dirDict = null; 5 | global.languages = null; 6 | global.problemNames = null; 7 | global.resultsSchemaJson = null; 8 | 9 | global.problemBuildsDir = null; 10 | 11 | LANGUAGES_DIR_NAME = "languages"; 12 | PROBLEMS_DIR_NAME = "problems"; 13 | TESTCASES_DIR_NAME = "testcases"; 14 | RESULTS_VALIDATION_SCHEMA_FILENAME = "results_validation_schema.json"; 15 | 16 | function getPathsFile() { 17 | return path.resolve("filePaths.tmp") 18 | } 19 | 20 | function readMdFile(filePath) { 21 | const marked = require('marked'); 22 | marked.setOptions({ 23 | breaks: false, 24 | }); 25 | const data = fs.readFileSync(filePath, 'utf8'); 26 | if (!data) { 27 | throw new Error(`File does not exist: ${filePath}`); 28 | } 29 | return marked.marked(data); 30 | } 31 | 32 | function populateLanguages(languagesDir) { 33 | var languages = []; 34 | fs.readdirSync(languagesDir).forEach(language => { 35 | languages.push(language); 36 | }); 37 | return languages; 38 | } 39 | 40 | function populateProblems(problemsDir) { 41 | var problems = []; 42 | fs.readdirSync(problemsDir).forEach(problemName => { 43 | problems.push(problemName); 44 | }); 45 | return problems; 46 | } 47 | 48 | function calculateDirectories() { 49 | if (global.dirDict) { 50 | return; 51 | } 52 | 53 | const pathsFile = getPathsFile(); 54 | if (!fs.existsSync(pathsFile)) { 55 | throw new Error(`Paths file does not exist: ${pathsFile}`); 56 | } 57 | 58 | global.problemBuildsDir = fs.readFileSync(pathsFile, 'utf8'); 59 | 60 | localDirDict = {}; 61 | 62 | const problemsDir = path.join(problemBuildsDir, PROBLEMS_DIR_NAME); 63 | if (!fs.existsSync(problemsDir)) { 64 | throw new Error(`Problems directory does not exist: ${problemsDir}`); 65 | } 66 | 67 | const languagesDir = path.join(problemBuildsDir, LANGUAGES_DIR_NAME); 68 | if (!fs.existsSync(languagesDir)) { 69 | throw new Error(`Languages directory does not exist: ${languagesDir}`); 70 | } 71 | 72 | const languages = populateLanguages(languagesDir); 73 | const problemNames = populateProblems(problemsDir); 74 | 75 | fs.readdirSync(problemsDir).forEach(problemName => { 76 | // For now assume cpp, later expand to all languages 77 | const language = "cpp"; 78 | const problemDir = path.join(problemsDir, problemName); 79 | const descriptionFile = path.join(problemDir, 'description.md'); 80 | const solutionFile = path.join(problemDir, language, 'solution.md'); 81 | const userSolutionFilename = path.join(problemDir, language, 'solution.cpp'); 82 | 83 | if (!fs.existsSync(userSolutionFilename)) { 84 | throw new Error(`User solution file does not exist: ${userSolutionFilename}`); 85 | } 86 | 87 | global.localDirDict[problemName] = 88 | global.localDirDict[problemName] || {}; 89 | global.localDirDict[problemName]["description"] = 90 | readMdFile(descriptionFile); 91 | global.localDirDict[problemName][language] = 92 | global.localDirDict[problemName][language] || {}; 93 | global.localDirDict[problemName][language]["solution"] = 94 | readMdFile(solutionFile); 95 | global.localDirDict[problemName][language] 96 | ["user-solution-filename"] = userSolutionFilename; 97 | }); 98 | 99 | const resultsSchemaFilename = 100 | path.join(problemBuildsDir, RESULTS_VALIDATION_SCHEMA_FILENAME); 101 | 102 | if (!fs.existsSync(resultsSchemaFilename)) { 103 | throw new Error(`Results validation schema file 104 | does not exist: 105 | ${resultsSchemaFilename}`); 106 | } 107 | 108 | resultsSchema = fs.readFileSync(resultsSchemaFilename, 'utf8'); 109 | 110 | 111 | const resultsSchemaJson = JSON.parse(resultsSchema); 112 | 113 | if (!resultsSchemaJson) { 114 | throw new Error(`Could not parse results validation schema from 115 | ${resultsSchema}`); 116 | } 117 | 118 | global.dirDict = localDirDict; 119 | global.languages = languages; 120 | global.problemNames = problemNames; 121 | global.resultsSchemaJson = resultsSchemaJson; 122 | } 123 | 124 | class DirectoryManager { 125 | constructor() { 126 | console.log("Initializing DirectoryManager"); 127 | calculateDirectories(); 128 | } 129 | 130 | getDescription(problemName) { 131 | return global.dirDict[problemName]["description"]; 132 | } 133 | 134 | getSolution(problemName) { 135 | return global.dirDict[problemName]["cpp"]["solution"]; 136 | } 137 | 138 | getUserSolutionFilename(problemName) { 139 | return global.dirDict[problemName]["cpp"]["user-solution-filename"]; 140 | } 141 | 142 | getLanguages() { 143 | return [...global.languages]; 144 | } 145 | 146 | getProblemNames() { 147 | return [...global.problemNames]; 148 | } 149 | 150 | getResultsSchemaJson() { 151 | return global.resultsSchemaJson; 152 | } 153 | 154 | getCustomTestcaseName() { 155 | return "_custom_testcase"; 156 | } 157 | 158 | getCustomTestcaseFilename(problemName) { 159 | return path.join(problemBuildsDir, 160 | PROBLEMS_DIR_NAME, 161 | problemName, 162 | TESTCASES_DIR_NAME, 163 | this.getCustomTestcaseName() + '.test'); 164 | } 165 | } 166 | 167 | module.exports = { 168 | DirectoryManager, 169 | getPathsFile, 170 | }; -------------------------------------------------------------------------------- /src/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | OpenLeetCode 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 17 | 18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 |
Description
26 |
27 |
Solution
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 | 38 | 39 |
40 | 41 |
42 |
Code
43 |
44 |
45 | 46 | 47 |
48 |
49 |
Testcase
50 |
51 |
Test Results
52 |
53 |
Compilation
54 |
55 |
56 | 57 |
58 |
Input:
59 | 60 |
Expected:
61 | 62 |
Output:
63 | 64 |
Stdout:
65 | 66 |
Stderr:
67 | 68 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |

77 |         
78 |
79 | 80 |
81 | 82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /src/ui/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const file = require('fs'); 3 | const amdLoader = require('monaco-editor/min/vs/loader.js'); 4 | const Split = require('split.js') 5 | const { ipcRenderer } = require('electron'); 6 | const { exec } = require('child_process'); 7 | const DirectoryManager = require('./directory-manager.js'); 8 | const { Validator } = require('jsonschema'); 9 | 10 | const amdRequire = amdLoader.require; 11 | const amdDefine = amdLoader.require.define; 12 | var editor; 13 | 14 | const directory_manager = require('./directory-manager.js'); 15 | const directoryManager = new directory_manager.DirectoryManager(); 16 | 17 | amdRequire.config({ 18 | baseUrl: path.join(__dirname, './node_modules/monaco-editor/min') 19 | }); 20 | 21 | var activeProblem = null; 22 | 23 | // workaround monaco-css not understanding the environment 24 | self.module = undefined; 25 | 26 | function saveSolution(language, content) { 27 | if (!previousProblem) { 28 | return; 29 | } 30 | 31 | const userSolutionFilename = 32 | directoryManager.getUserSolutionFilename(previousProblem); 33 | 34 | if (file.existsSync(userSolutionFilename) && 35 | file.readFileSync(userSolutionFilename, 'utf8') === content) { 36 | console.log("No changes to save"); 37 | return; 38 | } 39 | console.log("Saving problem " + previousProblem + " to " + 40 | userSolutionFilename); 41 | file.writeFileSync(userSolutionFilename, content); 42 | } 43 | 44 | function parseResultsFileFromStdout(stdout) { 45 | // stdout: 46 | // LongestSubstringWithoutRepeatingCharacters for testcase All in language cpp 47 | // Results written to /path\to/openleetcode/src/ui/testcase_output/.results 48 | // Status: 49 | // Duration: ms 50 | match = stdout.match(/Results written to (.*\.results)/); 51 | if (!match || match.length === 0) { 52 | return null; 53 | } 54 | return match[1]; 55 | } 56 | 57 | function parseBuildError(stdout) { 58 | // Running command: cmake --build ... 59 | // 60 | // Error running the command: cmake --build 61 | const regex = /cmake --build[\s\S]*?cmake --build/; 62 | const match = stdout.match(regex); 63 | if (!match || match.length === 0) { 64 | return stdout; 65 | } 66 | const buildError = match[0].split('\n').slice(1, -1).join('\n'); 67 | 68 | return buildError; 69 | } 70 | 71 | function validateResults(results) { 72 | try { 73 | const schema = directoryManager.getResultsSchemaJson(); 74 | const v = new Validator(); 75 | const validation = v.validate(results, schema); 76 | if (!validation.valid) { 77 | console.error("Validation errors:", validation.errors); 78 | return false; 79 | } 80 | } catch (e) { 81 | console.error("Error validating data:", e); 82 | return false; 83 | } 84 | 85 | return true; 86 | } 87 | 88 | function readTestcaseFile(filename) { 89 | if (filename == undefined) { 90 | console.error("Testcase file not defined"); 91 | return "Testcase file not defined"; 92 | } 93 | try { 94 | var testcaseFileContent = file.readFileSync(filename, "utf8"); 95 | testcaseFileContent = 96 | testcaseFileContent.replace(/\n/g, "
 "); 97 | return testcaseFileContent; 98 | } catch (err) { 99 | console.error("Error reading file ${filename}:", err); 100 | return "Error reading file ${filename}: ${err}"; 101 | } 102 | } 103 | 104 | function setTestResults(results) { 105 | if (!validateResults(results)) { 106 | return; 107 | } 108 | console.log("Setting test results: " + JSON.stringify(results)); 109 | const div = document.getElementById('test-results-content'); 110 | 111 | let html = ` 112 |

Duration: ${results.duration_ms} ms

113 |

Status: ${results.status}

114 |

Testcase Filter: ${results.testcase_filter_name}

115 |
116 | `; 117 | 118 | html += results.tests.map(test => { 119 | 120 | var testcase; 121 | if (test.testcase_file !== undefined) { 122 | testcase = readTestcaseFile(test.testcase_file); 123 | } 124 | 125 | return ` 126 |

Testcase Name: ${test.testcase_name}

127 |

Status: ${test.status}

128 | ${test.actual ? `

Actual: ${JSON.stringify(test.actual)}

` : ''} 129 | ${test.expected ? `

Expected: ${JSON.stringify(test.expected)}

` : ''} 130 | ${test.reason ? `

Reason: ${test.reason}

` : ''} 131 | ${testcase ? `

Testcase: ${testcase}

` : ''} 132 |
133 | `; 134 | }).join(''); 135 | 136 | div.innerHTML = html; 137 | document.getElementById('tab-test-results-button').click(); 138 | } 139 | 140 | function run(callback, testcase = 'All', expected = false) { 141 | saveSolution('cpp', editor.getValue()); 142 | const pathsFile = DirectoryManager.getPathsFile(); 143 | if (!file.existsSync(pathsFile)) { 144 | throw new Error(`Paths file does not exist: ${pathsFile}`); 145 | } 146 | 147 | problemBuildsDir = file.readFileSync(pathsFile, 'utf8'); 148 | problemBuildsDir = path.resolve(problemBuildsDir); 149 | const extension = process.platform === 'win32' ? '.bat' : '.sh'; 150 | 151 | const command = `${problemBuildsDir}/openleetcode${extension} ` + 152 | `--problem_builds_dir ${problemBuildsDir} ` + 153 | `--language cpp ` + 154 | `--problem ${activeProblem} ` + 155 | `--testcase ${testcase} ` + 156 | `${expected ? '--run-expected-tests ' : ''}` + 157 | `--verbose`; 158 | 159 | console.log("Running command: " + command); 160 | 161 | var resultsFilename; 162 | exec(command, (error, stdout, stderr) => { 163 | var element = document.getElementById("compilation-content"); 164 | 165 | element.textContent = ""; 166 | resultsFilename = parseResultsFileFromStdout(stdout); 167 | if (!resultsFilename || !file.existsSync(resultsFilename)) { 168 | console.log("Setting error"); 169 | console.log("Error running the command, error: " + error + 170 | ", stderr: " + stderr + ", stdout: " + stdout); 171 | element.textContent = parseBuildError(stdout); 172 | document.getElementById('tab-compilation-button').click(); 173 | return; 174 | } 175 | 176 | const results = file.readFileSync(resultsFilename, 'utf8'); 177 | console.log(results); 178 | const resultsJson = JSON.parse(results); 179 | errorcode = resultsJson["errorcode"]; 180 | console.log("errorcode: " + errorcode); 181 | if (errorcode != undefined && errorcode !== 0) { 182 | html = "

Errorcode: " + resultsJson.errorcode + "

"; 183 | html += "

Stdout: " + resultsJson.stdout + "

"; 184 | html += "

Stderr: " + resultsJson.stderr + "

"; 185 | 186 | element.innerHTML = html; 187 | document.getElementById('tab-compilation-button').click(); 188 | return; 189 | } else { 190 | console.log("Setting results"); 191 | callback(resultsJson); 192 | } 193 | }); 194 | } 195 | 196 | function setCustomTestcaseResults(results) { 197 | if (!validateResults(results)) { 198 | return; 199 | } 200 | 201 | document.getElementById('testcase-stderr').textContent = results.stderr; 202 | 203 | if (results.tests.length !== 1) { 204 | console.error("Expected 1 custom test results, got " + 205 | results.tests.length); 206 | return; 207 | } 208 | 209 | if (results.tests[0].status !== "Skipped") { 210 | console.error("Expected custom test status to be 'skipped', got " + 211 | results.tests[0].status); 212 | return; 213 | } 214 | 215 | console.log("Setting custom testcase results: " + JSON.stringify(results)); 216 | 217 | document.getElementById('testcase-stdout').textContent = results.stdout; 218 | document.getElementById('testcase-output').textContent = 219 | JSON.stringify(results.tests[0].actual); 220 | 221 | run(setExpectedTestcaseResults, directoryManager.getCustomTestcaseName(), 222 | true); 223 | 224 | document.getElementById('tab-testcases').click(); 225 | } 226 | 227 | function setExpectedTestcaseResults(expected) { 228 | if (!validateResults(expected)) { 229 | return; 230 | } 231 | 232 | if (expected.tests.length !== 1) { 233 | console.error("Expected 1 test results, got " + 234 | expected.tests.length); 235 | return; 236 | } 237 | 238 | if (expected.tests[0].status !== "Skipped") { 239 | console.error("Expected test status to be 'skipped', got " + 240 | expected.tests[0].status); 241 | } 242 | 243 | document.getElementById('expected-output').textContent = 244 | JSON.stringify(expected.tests[0].actual); 245 | } 246 | 247 | function runCustomTestcase() { 248 | console.log("Running custom testcase for " + activeProblem); 249 | 250 | document.getElementById('testcase-stdout').textContent = ""; 251 | document.getElementById('testcase-stderr').textContent = ""; 252 | document.getElementById('testcase-output').textContent = ""; 253 | document.getElementById('compilation-content').textContent = ""; 254 | document.getElementById('test-results-content').innerHTML = ""; 255 | 256 | const input = document.getElementById('input-container').value + "\n*"; 257 | const customTestcaseFilename = 258 | directoryManager.getCustomTestcaseFilename(activeProblem); 259 | if (!file.existsSync(path.dirname(customTestcaseFilename))) { 260 | console.log('The directory does not exist. Directory: ' + path.dirname(customTestcaseFilename)); 261 | return; 262 | } 263 | 264 | file.writeFileSync(customTestcaseFilename, input); 265 | if (!file.existsSync(customTestcaseFilename)) { 266 | throw new Error(`Failed to write custom testcase to ` + 267 | `${customTestcaseFilename}`); 268 | } 269 | 270 | console.log('Custom testcase written to ' + customTestcaseFilename); 271 | 272 | run(setCustomTestcaseResults, directoryManager.getCustomTestcaseName()); 273 | } 274 | 275 | function setDescription(problemName) { 276 | var element = 277 | document.querySelector('.markdown-content#description-content'); 278 | element.innerHTML = directoryManager.getDescription(problemName); 279 | } 280 | 281 | function setSolution(problemName) { 282 | var element = document.querySelector('.markdown-content#solution-content'); 283 | element.innerHTML = directoryManager.getSolution(problemName); 284 | } 285 | 286 | function setUserSolution(problemName) { 287 | var element = document.querySelector('#user-solution-content'); 288 | const userSolutionFilename = 289 | directoryManager.getUserSolutionFilename(problemName); 290 | editor.setValue(file.readFileSync(userSolutionFilename, 'utf8')); 291 | } 292 | 293 | var previousProblem; 294 | function onProblemSelected(problemName) { 295 | document.getElementById('testcase-stdout').textContent = ""; 296 | document.getElementById('testcase-stderr').textContent = ""; 297 | document.getElementById('testcase-output').textContent = ""; 298 | 299 | saveSolution('cpp', editor.getValue()); 300 | previousProblem = problemName; 301 | 302 | console.log(`Problem selected: ${problemName}`); 303 | setDescription(problemName); 304 | setSolution(problemName); 305 | setUserSolution(problemName); 306 | activeProblem = problemName; 307 | } 308 | 309 | function initializeProblemsCombo(problemNames) { 310 | var select = document.getElementById('problem-select'); 311 | problemNames.forEach(problemName => { 312 | var option = document.createElement('option'); 313 | option.value = problemName; 314 | option.textContent = problemName; 315 | select.appendChild(option); 316 | }); 317 | 318 | select.addEventListener('change', function(event) { 319 | onProblemSelected(event.target.value); 320 | }); 321 | } 322 | 323 | function initializeSaveCommand() { 324 | ipcRenderer.on('save-command', () => { 325 | console.log('Received save command'); 326 | saveSolution('cpp', editor.getValue()); 327 | }); 328 | 329 | document.getElementById('save-button') 330 | .addEventListener('click', function() { 331 | console.log('Save button clicked'); 332 | saveSolution('cpp', editor.getValue()); 333 | }); 334 | } 335 | 336 | function initializeRunCommand() { 337 | ipcRenderer.on('run-command', () => { 338 | console.log('Received run command'); 339 | document.getElementById('compilation-content').textContent = ""; 340 | document.getElementById('test-results-content').innerHTML = ""; 341 | run(setTestResults); 342 | }); 343 | 344 | document.getElementById('run-button') 345 | .addEventListener('click', function() { 346 | console.log('Run button clicked'); 347 | document.getElementById('compilation-content').textContent = ""; 348 | document.getElementById('test-results-content').innerHTML = ""; 349 | run(setTestResults); 350 | }); 351 | } 352 | 353 | function initializeCustomTestcaseCommand() { 354 | ipcRenderer.on('custom-testcase-command', () => { 355 | console.log('Received custom testcase command'); 356 | runCustomTestcase(); 357 | }); 358 | 359 | document.getElementById('custom-testcase-button') 360 | .addEventListener('click', function() { 361 | console.log('Custom testcase button clicked'); 362 | runCustomTestcase(); 363 | }); 364 | } 365 | 366 | document.addEventListener('DOMContentLoaded', (event) => { 367 | var tabs = document.querySelectorAll('.tab'); 368 | 369 | // For now... 370 | const problemNames = directoryManager.getProblemNames(); 371 | initializeProblemsCombo(problemNames); 372 | initializeSaveCommand(); 373 | initializeRunCommand(); 374 | initializeCustomTestcaseCommand(); 375 | 376 | amdRequire(['vs/editor/editor.main'], function() { 377 | monaco.editor.setTheme('vs-dark'); 378 | editor = monaco.editor.create( 379 | document.getElementById('user-solution-content'), { 380 | language: 'cpp', 381 | minimap: { enabled: false }, 382 | scrollbar: { 383 | vertical: 'auto', 384 | horizontal: 'auto' 385 | }, 386 | automaticLayout: true, 387 | scrollBeyondLastLine: false 388 | }); 389 | 390 | onProblemSelected(problemNames[0]); 391 | }); 392 | 393 | tabs.forEach(tab => { 394 | tab.addEventListener('click', function(event) { 395 | console.log('Tab clicked: ' + this.textContent); 396 | var tabContents = event.target.parentNode.parentNode.querySelectorAll('.tab-content'); 397 | tabContents.forEach(content => { 398 | content.classList.remove('active'); 399 | }); 400 | 401 | var paneId = this.textContent.toLowerCase().replace(/\s/g, '-'); 402 | var selectedPane = document.getElementById('tab-' + paneId); 403 | if (selectedPane) { 404 | selectedPane.classList.add('active'); 405 | } 406 | }); 407 | }); 408 | }); 409 | 410 | document.addEventListener('DOMContentLoaded', (event) => { 411 | Split(['#left-panel', '#right-panel'], { 412 | minSize: 100, 413 | sizes: [50, 50], 414 | gutterSize: 7, 415 | }) 416 | 417 | Split(['#top-right-panel', '#bottom-right-panel'], { 418 | minSize: 100, 419 | sizes: [60, 40], 420 | gutterSize: 7, 421 | direction: 'vertical', 422 | cursor: 'row-resize', 423 | }) 424 | }); -------------------------------------------------------------------------------- /src/ui/main.js: -------------------------------------------------------------------------------- 1 | const { app, BrowserWindow, globalShortcut } = require('electron'); 2 | const path = require('path'); 3 | const DirectoryManager = require('./directory-manager.js'); 4 | 5 | saveFilePaths(); 6 | 7 | let win; 8 | 9 | function saveFilePaths() { 10 | var problemBuildsDir = "./problem_builds"; 11 | var problemBuildsArg = process.argv.find(arg => arg.startsWith('--problem_builds_dir=')); 12 | 13 | if (problemBuildsArg && problemBuildsArg.length > 0) { 14 | problemBuildsDir = problemBuildsArg.split('=')[1]; 15 | console.log("Setting problemBuildsDir to " + problemBuildsDir); 16 | } else { 17 | console.log("problemBuildsDir was not set. Using default " + problemBuildsDir); 18 | console.log("process.argv: " + process.argv); 19 | } 20 | problemBuildsDir = path.resolve(problemBuildsDir); 21 | 22 | const fs = require('fs'); 23 | fs.writeFileSync(DirectoryManager.getPathsFile(), 24 | problemBuildsDir, 'utf8'); 25 | } 26 | 27 | function createWindow() { 28 | win = new BrowserWindow({ 29 | width: 1400, 30 | height: 1000, 31 | webPreferences: { 32 | preload: path.join(__dirname, 'preload.js'), 33 | nodeIntegration: true, 34 | contextIsolation: false, 35 | } 36 | }); 37 | 38 | win.setMenuBarVisibility(false); 39 | win.loadFile('index.html'); 40 | 41 | // win.webContents.openDevTools(); 42 | 43 | win.on('closed', () => { 44 | win = null 45 | }) 46 | } 47 | 48 | function registerSaveCommand() { 49 | const ret = globalShortcut.register('CommandOrControl+S', () => { 50 | console.log('CommandOrControl+S is pressed') 51 | win.webContents.send('save-command') 52 | }) 53 | 54 | if (!ret) { 55 | console.log('Registration failed!') 56 | } 57 | 58 | console.log("CommandOrControl+S registered: " + 59 | globalShortcut.isRegistered('CommandOrControl+S')) 60 | 61 | } 62 | 63 | function registerRunCommand() { 64 | const ret = globalShortcut.register('CommandOrControl+R', () => { 65 | console.log('CommandOrControl+R is pressed') 66 | win.webContents.send('run-command') 67 | }) 68 | 69 | if (!ret) { 70 | console.log('Registration failed!') 71 | } 72 | 73 | console.log("CommandOrControl+R registered: " + 74 | globalShortcut.isRegistered('CommandOrControl+R')) 75 | } 76 | 77 | function registerCustomTestcaseCommand() { 78 | const ret = globalShortcut.register('CommandOrControl+T', () => { 79 | console.log('CommandOrControl+T is pressed') 80 | win.webContents.send('custom-testcase-command') 81 | }) 82 | 83 | if (!ret) { 84 | console.log('Registration failed!') 85 | } 86 | 87 | console.log("CommandOrControl+T registered: " + 88 | globalShortcut.isRegistered('CommandOrControl+T')) 89 | } 90 | 91 | function registerCommands() { 92 | registerSaveCommand(); 93 | registerRunCommand(); 94 | registerCustomTestcaseCommand(); 95 | } 96 | 97 | app.whenReady().then(() => { 98 | const argv = process.argv; 99 | 100 | createWindow() 101 | app.on('activate', () => { 102 | if (BrowserWindow.getAllWindows().length === 0) { 103 | createWindow() 104 | } 105 | }) 106 | registerCommands(); 107 | }); 108 | 109 | app.on('window-all-closed', () => { 110 | if (process.platform !== 'darwin') { 111 | app.quit() 112 | } 113 | }) 114 | 115 | app.on('activate', () => { 116 | if (win === null) { 117 | createWindow() 118 | } 119 | }) 120 | 121 | app.on('will-quit', () => { 122 | globalShortcut.unregisterAll() 123 | }) -------------------------------------------------------------------------------- /src/ui/openleetcodeui.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | pushd "%~dp0" 5 | 6 | call :run_exe 7 | popd 8 | exit /b 9 | 10 | :run_exe 11 | for /D %%D in (OpenLeetCodeUI) do ( 12 | if exist "%%D\OpenLeetCodeUI.exe" ( 13 | echo Running OpenLeetCodeUI.exe in %%D 14 | start "" "%%D\OpenLeetCodeUI.exe" --problem_builds_dir=%~dp0 15 | exit /b 16 | ) 17 | ) 18 | echo No OpenLeetCodeUI.exe found in %~dp0OpenLeetCodeUI directory. 19 | exit /b -------------------------------------------------------------------------------- /src/ui/openleetcodeui.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | run_exe() { 4 | if [[ "$(uname)" == "Darwin" ]]; then 5 | app_path="./OpenLeetCodeUI/OpenLeetCodeUI.app/Contents/MacOS/OpenLeetCodeUI" 6 | echo "app_path: $app_path" 7 | if [ -x "$app_path" ]; then 8 | echo "Running OpenLeetCodeUI" 9 | "$app_path" --problem_builds_dir="$(pwd)" 10 | exit 11 | else 12 | echo "Error: OpenLeetCodeUI executable not found or not executable at $app_path" 13 | fi 14 | elif [[ "$(uname)" == "Linux" ]]; then 15 | for D in OpenLeetCodeUI; do 16 | if [ -f "${D}/OpenLeetCodeUI" ]; then 17 | echo "Running OpenLeetCodeUI in ${D}" 18 | "${D}/OpenLeetCodeUI.app" --problem_builds_dir=$(pwd) 19 | exit 20 | fi 21 | done 22 | echo "No OpenLeetCodeUI found in $(pwd)/OpenLeetCodeUI directory." 23 | else 24 | echo "Only Mac and Linux are supported." 25 | fi 26 | } 27 | 28 | pushd "$(dirname "$0")" > /dev/null 29 | run_exe 30 | popd > /dev/null 31 | -------------------------------------------------------------------------------- /src/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openleetcodeui", 3 | "productName": "OpenLeetCodeUI", 4 | "description": "OpenLeetCode UI", 5 | "keywords": [], 6 | "main": "./main.js", 7 | "version": "1.0.0", 8 | "author": "mbucko", 9 | "scripts": { 10 | "start": "electron . -- --problem_builds_dir=../../problem_builds" 11 | }, 12 | "dependencies": { 13 | "jsonschema": "^1.4.1", 14 | "marked": "^12.0.0", 15 | "monaco-editor": "^0.46.0", 16 | "split.js": "^1.6.5" 17 | }, 18 | "devDependencies": { 19 | "electron": "^29.1.0", 20 | "electron-packager": "^17.1.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ui/preload.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('DOMContentLoaded', () => { 2 | const replaceText = (selector, text) => { 3 | const element = document.getElementById(selector) 4 | if (element) element.innerText = text 5 | } 6 | 7 | for (const dependency of ['chrome', 'node', 'electron']) { 8 | replaceText(`${dependency}-version`, process.versions[dependency]) 9 | } 10 | }) -------------------------------------------------------------------------------- /src/ui/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --grey-very-light: #606060; 3 | --grey-light: #434343; 4 | --grey-medium: #333333; 5 | --grey-dark: #262626; 6 | --grey-very-dark: #1e1e1e; 7 | --white: #ffffff; 8 | --grey-font-color: #B0B0B0; 9 | 10 | --panel-background: var(--grey-very-dark); 11 | --border_color: var(--grey-very-light); 12 | --content-background: var(--grey-dark); 13 | --hover_color: var(--grey-light); 14 | --panel-border: var(--grey-very-light); 15 | --splitter: var(--grey-light); 16 | --tab-background: var(--grey-medium); 17 | --content-font-color: var(--white); 18 | --tab-font-color: var(--grey-font-color); 19 | 20 | --content-font-family: "Segoe UI"; 21 | 22 | --scrollbar-foreground: #d4d4d4; 23 | --scrollbarSlider-background: rgba(121, 121, 121, 0.4); 24 | } 25 | 26 | body { 27 | margin: 0; 28 | padding: 0; 29 | background-color: var(--panel-background); 30 | overflow: hidden; 31 | } 32 | 33 | .label { 34 | color: var(--content-font-color); 35 | background-color: var(--grey-very-dark); 36 | } 37 | 38 | select, button { 39 | background-color: var(--grey-medium); 40 | color: var(--white); 41 | border: none; 42 | padding: 5px 10px; 43 | } 44 | 45 | button { 46 | border-radius: 4px; 47 | margin-left: 10px; 48 | } 49 | 50 | button:hover { 51 | background-color: var(--hover_color); 52 | } 53 | 54 | select { 55 | border-radius: 4px; 56 | } 57 | 58 | select:hover { 59 | background-color: var(--hover_color); 60 | } 61 | 62 | .control-panel { 63 | margin: 4px; 64 | } 65 | 66 | .root-panel { 67 | margin: 1px 1 1px 1px; 68 | border: 2px solid var(--grey-very-dark); 69 | overflow: hidden; 70 | } 71 | 72 | .panel { 73 | padding: 4; 74 | box-sizing: border-box; 75 | background-color: var(--panel-background); 76 | } 77 | 78 | .markdown-content { 79 | color: var(--panel_foreground); 80 | background-color: var(--grey-very-dark); 81 | height: 100%; 82 | } 83 | .tab-panel { 84 | display: flex; 85 | background-color: var(--tab-background); 86 | padding: 5px; 87 | color: var(--tab-font-color); 88 | height: 18px; 89 | } 90 | 91 | .tab { 92 | padding: 0 10px 0 10px; 93 | cursor: pointer; 94 | border-radius: 1px; 95 | color: var(--tab-font-color); 96 | } 97 | 98 | .tab:hover { 99 | background-color: var(--hover_color); 100 | } 101 | 102 | .content { 103 | background-color: var(--grey-very-dark); 104 | color: var(--content-font-color); 105 | font-family: var(--content-font-family); 106 | overflow-y: auto; 107 | overflow-x: clip; 108 | padding: 3px; 109 | } 110 | 111 | .bottom-right-content { 112 | overflow-y: auto; 113 | } 114 | 115 | .tab-content { 116 | display: none; 117 | height: 100%; 118 | background-color: var(--grey-very-dark); 119 | box-sizing: border-box; 120 | } 121 | 122 | .tab-content.active { 123 | display: block; 124 | } 125 | 126 | .tab.selected { 127 | background-color: var(--panel_background); 128 | } 129 | 130 | .compilation-content { 131 | box-sizing: border-box; 132 | background-color: var(--grey-very-dark); 133 | width: 100%; 134 | white-space: pre-wrap; 135 | word-break: break-all; 136 | margin: 0; 137 | font-size: small; 138 | } 139 | 140 | .tab-separator { 141 | background-color: var(--border_color); 142 | width: 1px; 143 | margin: 3px 0; 144 | } 145 | 146 | .panel { 147 | display: flex; 148 | background-color: var(--grey-dark); 149 | } 150 | 151 | .panel-row { 152 | display: flex; 153 | flex-direction: row; 154 | } 155 | 156 | .panel-col { 157 | display: flex; 158 | flex-direction: column; 159 | overflow: hidden; 160 | } 161 | 162 | .panel-item { 163 | flex-grow: 1; 164 | overflow: auto; 165 | } 166 | 167 | #top-right-panel { 168 | overflow: hidden; 169 | height: 100%; 170 | } 171 | 172 | .panel-item-fixed-height { 173 | flex: none; 174 | } 175 | 176 | code { 177 | background-color: #ffffff12; 178 | border-color: #f7faff1f; 179 | border-radius: 5px; 180 | border-width: 1px; 181 | color: #eff1f6bf; 182 | font-family: Menlo,sans-serif; 183 | font-size: .75rem; 184 | line-height: 1rem; 185 | padding: 0.125rem; 186 | white-space: pre-wrap; 187 | } 188 | 189 | .editor{ 190 | height: 100%; 191 | } 192 | 193 | html, body { 194 | height: 100%; 195 | } 196 | 197 | #test-results-content, #user-solution-content, .tab-test-results, #bottom-right-panel, #root-panel, #tab-compilation, #compilation-content { 198 | height: 100%; 199 | overflow: auto; 200 | } 201 | 202 | #bottom-right-panel { 203 | overflow: hidden; 204 | } 205 | 206 | .gutter { 207 | background-color: var(--grey-medium); 208 | background-repeat: no-repeat; 209 | background-position: 50%; 210 | } 211 | 212 | .gutter.gutter-horizontal { 213 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg=='); 214 | cursor: col-resize; 215 | } 216 | 217 | .gutter.gutter-vertical { 218 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII='); 219 | cursor: row-resize; 220 | } 221 | 222 | #tab-testcase { 223 | height: 100%; 224 | } 225 | 226 | #tab-testcase-container { 227 | height: 100%; 228 | overflow-y: visible; 229 | overflow-x: hidden; 230 | display: flex; 231 | flex-direction: column; 232 | } 233 | 234 | .testcase-container-text { 235 | background-color: var(--grey-very-dark); 236 | color: var(--content-font-color); 237 | font-family: var(--content-font-family); 238 | overflow: hidden; 239 | padding: 3px; 240 | margin-right: 20px; 241 | height: 60px; 242 | width: 400px; 243 | } 244 | 245 | .testcase-container-title { 246 | margin: 10px 3px 3px 3px; 247 | } --------------------------------------------------------------------------------