Skip to content

Commit c4b4005

Browse files
Add company-specific problem practice functionality
Introduced "Company Mode" to handle questions based on specific companies and their recent trends. This includes company-based filters, durations, topics, and difficulties with caching support and validation to enhance user experience and efficiency.
1 parent 4bc6eb2 commit c4b4005

File tree

12 files changed

+759
-87
lines changed

12 files changed

+759
-87
lines changed

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Squidleet is a command-line LeetCode practice game that allows you to solve Leet
1515
- **Daily Challenge Integration**: Fetch and solve the LeetCode Daily Coding Challenge directly from the terminal.
1616
- **Study Plan Mode**: Fetch random problems based on a specific study plan.
1717
- **Random Problem Practice**: Get a randomly selected LeetCode problem to practice.
18+
- **Company Mode**: Fetch random problems asked by a specific company over a given duration (e.g., last 30 days).
1819
- **Specific Problem Mode**: Solve a specific problem by providing its problem slug.
1920
- **Problem Fetching**: Added enhanced fetching capabilities, including filtering based on difficulty (e.g., `easy`, `medium`, `hard`) and more.
2021
- **Submit Solutions**: Users can now directly submit their solutions to LeetCode from the terminal via a `submit_solution` function.
@@ -151,6 +152,36 @@ Selected 🎯 Study Plan Mode: top-interview-150
151152
⏳ You have 45 min minutes to solve the problem. Good luck!
152153
```
153154

155+
### Company Mode
156+
157+
Company Mode allows you to fetch random problems asked by a specific company.
158+
159+
⚠️ **Note**: This mode requires the `--leetcode-session` argument to be set with a valid LeetCode session cookie. This is because the company-specific problem data is not available publicly and requires a valid [LeetCode Premium](https://leetcode.com/subscribe) subscription.
160+
161+
```bash
162+
python3 main.py --mode company --company-name microsoft --duration thirty-days
163+
```
164+
165+
Optional arguments:
166+
- `--open-in-browser`: Opens the problem in a browser window.
167+
- `--editor`: Specify the preferred code editor (e.g., `vim`, `nano`). Default is the system-configured default editor.
168+
- `--difficulty`: Choose between `easy`, `medium`, or `hard` or select multiple using comma-separated list (e.g., `easy,medium`).
169+
- `--tags`: Filter problems based on tags. Example usage: `--tags Array,Hash Table`.
170+
- `--duration`: Fetch the problems asked by the company over a given span of time. Valid values: `thirty-days`, `three-months`, `six-months`, `more-than-six-months`, or `all`. Default is `all`.
171+
172+
```text
173+
Welcome to 🦑 SquidLeet!
174+
🔐 Using authenticated session
175+
Selected 👔 Company Mode: Top Questions asked at Microsoft in the last 30 days
176+
🎯 Problem Selected: Maximum Length of a Concatenated String with Unique Characters
177+
✨ Difficulty: Medium
178+
🔗 URL: https://leetcode.com/problems/maximum-length-of-a-concatenated-string-with-unique-characters
179+
🏷️ Tags: Array, String, Backtracking, Bit Manipulation
180+
📈 Acceptance Rate: 54.2%
181+
...
182+
⏳ You have 45 min minutes to solve the problem. Good luck!
183+
```
184+
154185
## Configurations
155186
Squidleet uses a `LEETCODE_SESSION` cookie for authentication. Setting the `LEETCODE_SESSION` environment variable is necessary for all operations, including fetching and submitting problems.
156187

api/CachedLeetCodeAPI.py

Lines changed: 116 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
import json
22
import hashlib
33
import tempfile
4-
from typing import Any, Callable, Optional
4+
from typing import Any, Callable, Optional, List
55
from pathlib import Path
66
import time
77

88
from api.LeetCodeAPI import LeetCodeAPI
99
from utils.logger import log, LogLevel
1010

1111

12+
def _get_cache_key(unique_id: str) -> str:
13+
"""
14+
Generate a cache key based on a unique identifier (e.g., query or API parameters).
15+
:param unique_id: Unique identifier for the API request (e.g., slug, query, variable JSON).
16+
:return: Cache filename based on the MD5 hash of the unique ID.
17+
"""
18+
return hashlib.md5(unique_id.encode("utf-8")).hexdigest()
19+
20+
1221
class CachedLeetCodeAPI:
1322
def __init__(self, cache_dir: Optional[str] = None, cache_expiry: int = 3600):
1423
"""
@@ -29,14 +38,6 @@ def __init__(self, cache_dir: Optional[str] = None, cache_expiry: int = 3600):
2938
LeetCodeAPI()
3039
) # Delegate actual API calls to existing LeetCodeAPI class
3140

32-
def _get_cache_key(self, unique_id: str) -> str:
33-
"""
34-
Generate a cache key based on a unique identifier (e.g., query or API parameters).
35-
:param unique_id: Unique identifier for the API request (e.g., slug, query, variable JSON).
36-
:return: Cache filename based on the MD5 hash of the unique ID.
37-
"""
38-
return hashlib.md5(unique_id.encode("utf-8")).hexdigest()
39-
4041
def _read_from_cache(self, cache_key: str) -> Any:
4142
"""
4243
Read data from cache if it exists and is valid.
@@ -73,7 +74,7 @@ def _fetch_with_cache(
7374
:param kwargs: Keyword arguments to pass to the fetch function.
7475
:return: Fetched or cached data.
7576
"""
76-
cache_key = self._get_cache_key(unique_id)
77+
cache_key = _get_cache_key(unique_id)
7778

7879
# Check cache first
7980
cached_data = self._read_from_cache(cache_key)
@@ -104,7 +105,7 @@ def fetch_daily_challenge(self, *args, **kwargs):
104105
Cached version of fetch_daily_challenge
105106
"""
106107
return self._fetch_with_cache(
107-
self.api.fetch_daily_challenge, "fetch_daily_challenge"
108+
self.api.fetch_daily_challenge, "fetch_daily_challenge", *args, **kwargs
108109
)
109110

110111
def fetch_problem(self, problem_slug: str, *args, **kwargs):
@@ -124,3 +125,107 @@ def get_study_plan(self, slug: str, *args, **kwargs):
124125
return self._fetch_with_cache(
125126
self.api.get_study_plan, unique_id, slug, *args, **kwargs
126127
)
128+
129+
def fetch_company_questions(self, company_slug: str, *args, **kwargs):
130+
"""
131+
Cached version of fetch_company_questions
132+
"""
133+
unique_id = f"fetch_company_questions-{company_slug}"
134+
return self._fetch_with_cache(
135+
self.api.fetch_company_questions, unique_id, company_slug, *args, **kwargs
136+
)
137+
138+
def fetch_company_questions_for_duration(
139+
self,
140+
company_name: str,
141+
duration: str,
142+
difficulties: Optional[List[str]] = None,
143+
tags: Optional[list] = None,
144+
*args,
145+
**kwargs,
146+
):
147+
"""
148+
Wrapper around `fetch_company_questions` that accepts a company name,
149+
duration, and an optional difficulty filter to fetch questions for a specific time range.
150+
151+
This method is cached to avoid redundant API calls.
152+
153+
:param company_name: Name of the company (e.g., 'amazon', 'google').
154+
:param duration: Duration to filter questions (e.g., 'thirty-days', 'three-months').
155+
Valid durations:
156+
- "thirty-days"
157+
- "three-months"
158+
- "six-months"
159+
- "more-than-six-months"
160+
- "all"
161+
:param difficulties: (Optional) Difficulty filter to narrow down the results.
162+
Valid options: "EASY", "MEDIUM", "HARD".
163+
:param tags: (Optional) List of topic tags to filter the questions.
164+
:return: A dictionary containing the questions and metadata.
165+
"""
166+
167+
# Validate duration input
168+
valid_durations = [
169+
"thirty-days",
170+
"three-months",
171+
"six-months",
172+
"more-than-six-months",
173+
"all",
174+
]
175+
if duration not in valid_durations:
176+
raise ValueError(
177+
f"Invalid duration: {duration}. Must be one of {valid_durations}."
178+
)
179+
180+
# Validate difficulty input
181+
valid_difficulties = ["EASY", "MEDIUM", "HARD"]
182+
difficulty_filter = None
183+
if difficulties:
184+
if difficulties not in valid_difficulties:
185+
raise ValueError(
186+
f"Invalid difficulty: {difficulties}. Must be one of {valid_difficulties}."
187+
)
188+
189+
# Create a difficulty filter with the operator "IS"
190+
difficulty_filter = {"difficulties": difficulties, "operator": "IS"}
191+
192+
# Validate tags input
193+
topic_filter = None
194+
if tags:
195+
topic_tags = self.api.get_topic_tags()
196+
valid_tags = [tag.lower() for tag in topic_tags]
197+
standardized_tags = [tag.lower() for tag in tags]
198+
if any(tag not in valid_tags for tag in standardized_tags):
199+
raise ValueError(
200+
f"Invalid tags. Supported tags: {', '.join(valid_tags)}"
201+
)
202+
203+
# Create a topic filter with the operator "IS"
204+
topic_filter = {
205+
"tags": standardized_tags,
206+
"operator": "IS",
207+
}
208+
209+
# Construct the favorite_slug based on company name and duration
210+
favorite_slug = f"{company_name.lower()}-{duration}"
211+
212+
# Append difficulties to the unique id if provided
213+
unique_id = (
214+
f"{favorite_slug}-{'-'.join(difficulties)}"
215+
if difficulties
216+
else favorite_slug
217+
)
218+
219+
results = self._fetch_with_cache(
220+
self.api.fetch_company_questions,
221+
unique_id,
222+
favorite_slug,
223+
difficulty_filter,
224+
topic_filter,
225+
*args,
226+
**kwargs,
227+
)
228+
229+
# Navigate to the questions in the actual response structure
230+
favorite_question_list = results.get("data", {}).get("favoriteQuestionList", {})
231+
return favorite_question_list.get("questions", [])

0 commit comments

Comments
 (0)