Skip to content

Commit 99ad167

Browse files
committed
Add support for excel format, tqdm for question generation progress
1 parent db85d79 commit 99ad167

File tree

11 files changed

+199
-35
lines changed

11 files changed

+199
-35
lines changed

.github/workflows/deploy-docs.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Docs
2+
on: [push, pull_request, workflow_dispatch]
3+
permissions:
4+
contents: write
5+
jobs:
6+
docs:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v3
10+
- uses: actions/setup-python@v3
11+
- name: Install dependencies
12+
run: |
13+
pip install sphinx sphinx_rtd_theme sphinx-argparse
14+
- name: Sphinx build
15+
run: |
16+
sphinx-build doc _build
17+
- name: Deploy
18+
uses: peaceiris/actions-gh-pages@v3
19+
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
20+
with:
21+
publish_branch: gh-pages
22+
github_token: ${{ secrets.GITHUB_TOKEN }}
23+
publish_dir: _build/
24+
force_orphan: true

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
# Changelog
22

3+
## 1.2.0
4+
- Added Neetcode 150 and Neetcode All to presets
5+
- Added support for exporting to Excel
6+
- Added TQDM progress bar to reflect progress of card generation
7+
38
## 1.1.0
49
- Added support for using presets of questions, including the Grind 75, Grind 169, and the original Blind 75. This argument is an optional argument instead of inputting urls or a file of questions.

README.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@
55
[![PyPi](https://img.shields.io/pypi/v/leetcode-study-tool)](https://pypi.org/project/leetcode-study-tool/)
66
![contributions welcome](https://img.shields.io/badge/contributions-welcome-blue.svg?style=flat)
77

8-
This package lets you get grokking as quickly as possible with Leetcode. It provides a command-line tool for interracting with Leetcode to create flashcards for study,
9-
which can then be imported into Anki. Currently, this tool supports taking in a list of or popular study sets (including the [Blind 75](https://www.teamblind.com/post/New-Year-Gift---Curated-List-of-Top-75-LeetCode-Questions-to-Save-Your-Time-OaM1orEU), [Grind 75](https://www.techinterviewhandbook.org/grind75), and [Neetcode 150](https://neetcode.io/practice)) and outputting
10-
problems in a format that can be imported to Anki. These cards include three fields:
11-
1. The front of the study card, containing the question ID, Title, URL, and problem description
12-
2. The publicly available solutions (and NeetCode solution, if available)
13-
3. The tags associated with the problem (i.e., if the problem involves a hash map, arrays, etc...)
8+
This package lets you get grokking as quickly as possible with Leetcode. It provides a command-line tool for interracting with Leetcode to create either an Excel file or Anki flashcards for study. Currently, this tool supports taking in a list of leetcode question slugs or URLs or popular study sets (including the [Blind 75](https://www.teamblind.com/post/New-Year-Gift---Curated-List-of-Top-75-LeetCode-Questions-to-Save-Your-Time-OaM1orEU), [Grind 75](https://www.techinterviewhandbook.org/grind75), and [Neetcode 150](https://neetcode.io/practice)).
149

1510
## Why?
1611
This package was created as an opinionated alternative to other existing packages (as listed at the bottom of this README).
@@ -24,7 +19,7 @@ $ pip install leetcode-study-tool
2419
```shell
2520
usage: leetcode-study-tool [-h]
2621
(--url URL | --file FILE | --preset {blind_75,grind_75,grind_169,neetcode_150,neetcode_all})
27-
[--format {anki}] [--csrf CSRF] [--output OUTPUT]
22+
[--format {anki,excel}] [--csrf CSRF] [--output OUTPUT]
2823
[--language LANGUAGE]
2924

3025
Generates problems from LeetCode questions in a desired format.
@@ -33,11 +28,11 @@ options:
3328
-h, --help show this help message and exit
3429
--url URL, -u URL The URL(s) or slug(s) of the LeetCode question(s) to generate
3530
problem(s) for. (default: None)
36-
--file FILE, -f FILE The file containing the URL(s) or slug(s) of the LeetCode
37-
question(s) to generate problem(s) for. (default: None)
31+
--file FILE, -f FILE The file containing the URL(s) or slug(s) of the LeetCode question(s)
32+
to generate problem(s) for. (default: None)
3833
--preset {blind_75,grind_75,grind_169,neetcode_150,neetcode_all}, -p {blind_75,grind_75,grind_169,neetcode_150,neetcode_all}
3934
The preset to use to generate problem(s) for. (default: None)
40-
--format {anki}, -F {anki}
35+
--format {anki,excel}, -F {anki,excel}
4136
The format to save the Leetcode problem(s) in. (default: anki)
4237
--csrf CSRF, -c CSRF The CSRF token to use for LeetCode authentication. (default: None)
4338
--output OUTPUT, -o OUTPUT
@@ -59,16 +54,33 @@ which will generate the file `output.txt`. We can then open Anki to import these
5954

6055
![anki demo](static/anki-demo.gif)
6156

57+
## Anki
58+
When generating an Anki output, the resulting "cards" are saved as a `.txt` file. These cards include three fields:
59+
1. The front of the study card, containing the question ID, Title, URL, and problem description
60+
2. The publicly available solutions (and NeetCode solution, if available)
61+
3. The tags associated with the problem (i.e., if the problem involves a hash map, arrays, etc...)
62+
63+
## Excel
64+
When generating an Excel output, the resulting questions are saved in an `.xlsx` file. Each problem includes the following fields:
65+
1. ID of the leetcode question
66+
2. Title of the leetcode question
67+
3. URL of the leetcode question
68+
4. Last date that this question was attempted by the user (please note that this is not pulled from your leetcode profile, but left for you to update as you progress in solving leetcode questions)
69+
5. The tags associated with the problem (i.e., if the problem involves a hash map, arrays, etc...)
70+
6. Neetcode video link (if it exists)
71+
7. Solution links for the problem (if they are reachable)
72+
8. Companies that have asked this question recently in interviews (if they are reachable)
6273

6374
## Roadmap
75+
- [X] Use TQDM to show card generation progress
76+
- [X] Add support for exporting to an excel sheet
77+
- [X] Add support for showing neetcode solutions on the back of the card as a
78+
- [ ] Add support for getting the difficulty of questions
6479
- [ ] Add support for fetching premium questions via authentification
6580
- [ ] Add support for importing cards into Quizlet
6681
- [ ] Add support for fetching questions by topic or tag
67-
- [ ] Add support for exporting to an excel sheet
68-
- [X] Add support for showing neetcode solutions on the back of the card as a link
69-
- [ ] Add support for determining which fields to show on the card
70-
- [ ] Use TQDM to show card generation progress
71-
- [ ] Allow for the definition of custom formatters and outputs
82+
link
83+
- [ ] Allow for the definition of custom formatters and outputs (including which fields are included or excluded)
7284
- [ ] Reach 90% test coverage
7385

7486
## Other Usefull Stuff

leetcode_study_tool/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def generate_parser() -> argparse.ArgumentParser:
5555
"-F",
5656
type=str,
5757
default="anki",
58-
choices=["anki"],
58+
choices=["anki", "excel"],
5959
help="The format to save the Leetcode problem(s) in.",
6060
)
6161

leetcode_study_tool/creator.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
import html
33
import os
44
import re
5-
from multiprocessing import Pool
5+
from datetime import date
66
from typing import List, Union
77

8+
from p_tqdm import p_map
9+
810
from .constants.presets import PRESET_MAP
911
from .formatters import FORMAT_MAP
1012
from .outputs import SAVE_MAP
@@ -49,11 +51,13 @@ def create_problems(self) -> None:
4951
"""
5052
Create the problems for Anki.
5153
"""
52-
with Pool() as pool:
53-
problems = pool.map(
54-
self._generate_problem,
55-
self.urls,
56-
)
54+
problems = p_map(self._generate_problem, self.urls)
55+
56+
# with Pool() as pool:
57+
# problems = pool.map(
58+
# self._generate_problem,
59+
# self.urls,
60+
# )
5761

5862
self._save_output(problems, self.output)
5963

@@ -83,10 +87,12 @@ def _sanitize(self, input: Union[str, list, None]) -> Union[str, list]:
8387
input = re.sub(r"(<br>){2,}", "<br>", input)
8488
return input
8589

86-
def _save_output(self, problems: List[Union[str, None]], file: str) -> None:
90+
def _save_output(
91+
self, problems: List[Union[List[Union[str, date]], None]], file: str
92+
) -> None:
8793
file_name = os.path.splitext(os.path.basename(file))[0]
8894

89-
SAVE_MAP[self.format](problems, file_name)
95+
SAVE_MAP[self.format](problems, file_name) # type: ignore
9096

9197
def _generate_problem(self, url: str) -> Union[str, None]:
9298
"""

leetcode_study_tool/formatters.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
from datetime import date
12
from textwrap import dedent
2-
from typing import List
3+
from typing import List, Union
34

45
from leetcode_study_tool.constants.leetcode_to_neetcode import (
56
LEETCODE_TO_NEETCODE, # fmt: skip
@@ -11,7 +12,7 @@ def format_list_element(
1112
title: str, elements: List[str], is_link: bool = False
1213
) -> str:
1314
"""
14-
formats a list element for the given title and elements
15+
formats an HTML list element for the given title and elements
1516
1617
Arguments
1718
---------
@@ -141,7 +142,7 @@ def format_quizlet(url: str, slug: str, data: dict):
141142
pass
142143

143144

144-
def format_excel(url: str, slug: str, data: dict):
145+
def format_excel(url: str, slug: str, data: dict) -> List[Union[str, date]]:
145146
"""
146147
formats an Excel problem for the given URL and data
147148
@@ -157,9 +158,37 @@ def format_excel(url: str, slug: str, data: dict):
157158
Returns
158159
-------
159160
str
160-
The Excel problem for the given URL and data.
161+
The Excel problem for the given URL and data. The problem
162+
is formatted as a list of strings, where each string is a
163+
column in the Excel file. This row will have the ordering:
164+
[id, title, url, date attempted, tags, neetcode, solutions, companies]
161165
"""
162-
pass
166+
row = []
167+
row.append(data["id"])
168+
row.append(data["title"])
169+
row.append(get_url(url))
170+
row.append(date.today())
171+
row.append(", ".join([tag["name"] for tag in data["tags"]]))
172+
if str(data["id"]) in LEETCODE_TO_NEETCODE:
173+
neetcode = LEETCODE_TO_NEETCODE[str(data["id"])]
174+
row.append(neetcode["url"])
175+
else:
176+
row.append("")
177+
row.append(
178+
"\n".join(
179+
[
180+
format_solution_link(slug, solution["id"])
181+
for solution in data["solutions"]
182+
]
183+
)
184+
)
185+
if data.get("companies"):
186+
row.append(
187+
", ".join([company["name"] for company in data["companies"]])
188+
)
189+
else:
190+
row.append("")
191+
return row
163192

164193

165194
FORMAT_MAP = {

leetcode_study_tool/outputs.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
import os
2+
from datetime import date
13
from typing import List, Union
24

5+
import xlsxwriter
6+
37

48
def save_string(problems: List[Union[str, None]], file: str) -> None:
59
"""
@@ -18,18 +22,66 @@ def save_string(problems: List[Union[str, None]], file: str) -> None:
1822
f.write(problem + "\n")
1923

2024

21-
def save_excel(problems: List[Union[str, None]], file: str) -> None:
25+
def save_excel(
26+
problems: List[Union[List[Union[str, date]], None]], file: str
27+
) -> None:
2228
"""
2329
Save the given problems to the given Excel file.
2430
2531
Arguments
2632
---------
27-
problems : List[Union[str, None]]
33+
problems : List[Union[List[str], None]]
2834
The problems to save.
2935
file : str
3036
The file to save the problems to.
3137
"""
32-
pass
38+
if os.path.exists(file + ".xlsx"):
39+
os.remove(file + ".xlsx")
40+
workbook = xlsxwriter.Workbook(file + ".xlsx")
41+
worksheet = workbook.add_worksheet()
42+
43+
worksheet.write(0, 0, "ID")
44+
worksheet.write(0, 1, "Title")
45+
worksheet.write(
46+
0,
47+
2,
48+
"Url",
49+
)
50+
worksheet.write(0, 3, "Date Attempted")
51+
worksheet.write(0, 4, "Tags")
52+
worksheet.write(0, 5, "Neetcode")
53+
worksheet.write(0, 6, "Solutions")
54+
worksheet.write(0, 7, "Companies")
55+
56+
# Add bold formatting to the first row
57+
bold = workbook.add_format({"bold": True})
58+
worksheet.set_row(0, None, bold)
59+
60+
# Add gradient formatting to the date attempted row
61+
worksheet.conditional_format(
62+
1,
63+
3,
64+
len(problems),
65+
3,
66+
{
67+
"type": "3_color_scale",
68+
"min_color": "red",
69+
"mid_color": "yellow",
70+
"max_color": "green",
71+
},
72+
)
73+
74+
date_format = workbook.add_format({"num_format": "dd/mm/yy"})
75+
76+
for i, problem in enumerate(problems, start=1):
77+
if problem:
78+
for j, line in enumerate(problem):
79+
if type(line) == date and j == 3:
80+
worksheet.write_datetime(i, j, line, date_format)
81+
else:
82+
worksheet.write(i, j, line)
83+
84+
workbook.close()
3385

3486

3587
SAVE_MAP = {

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "leetcode-study-tool"
7-
version = "1.1.0"
7+
version = "1.2.0"
88
description = "A tool for studying Leetcode with Python"
99
authors = [{name="John Sutor", email="johnsutor3@gmail.com" }]
1010
license = {file = "LICENSE.txt"}
1111
readme = "README.md"
12-
dependencies = ["requests"]
12+
dependencies = ["requests", "XlsxWriter", "p_tqdm"]
1313
keywords = ["leetcode", "leet", "study", "Anki"]
1414
classifiers=[
1515
# Development status

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
setuptools==68.0.0
22
wheel==0.38.4
33
requests==2.31.0
4-
types-requests==2.31
4+
types-requests==2.31
5+
xlsxwriter==3.1.2
6+
p_tqdm==1.4.0

tests/test_formatters.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,21 @@ def test_format_anki(self):
3939
formatters.format_anki(get_url("two-sum"), "two-sum", data),
4040
self.correct_anki_formatted_two_sum_problem,
4141
)
42+
43+
def test_format_excel(self):
44+
data = get_data("two-sum")
45+
# Don't check the last two elements of the list because they
46+
# will change over time. Also, mocking time causes issues with
47+
# urllib, so avoid.
48+
output = formatters.format_excel(get_url("two-sum"), "two-sum", data)
49+
output = output[:3] + output[4:-2]
50+
self.assertListEqual(
51+
output,
52+
[
53+
"1",
54+
"Two Sum",
55+
"https://leetcode.com/problems/two-sum/",
56+
"Array, Hash Table",
57+
"https://youtube.com/watch?v=KLlXCFG5TnA",
58+
],
59+
)

0 commit comments

Comments
 (0)