Skip to content

Commit 404d109

Browse files
committed
initial commit
0 parents  commit 404d109

File tree

7 files changed

+420
-0
lines changed

7 files changed

+420
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea/

build.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"path"
6+
)
7+
8+
func buildProblem(id, basePath string) {
9+
item := getItem(id)
10+
titleSlug := item.Question.TitleSlug
11+
question := getQuestion(titleSlug)
12+
err := createQuestion(basePath, item, question)
13+
if err != nil {
14+
fmt.Println(err)
15+
return
16+
}
17+
fmt.Printf("create problem <%s> successfully!\n", question.QuestionTitle)
18+
}
19+
20+
func buildAllProblems() {
21+
// 一键创建所有题目,按难度和类型分文件夹保存
22+
cardSlugs := []string{
23+
"top-interview-questions-easy",
24+
"top-interview-questions-medium",
25+
"top-interview-questions-hard",
26+
}
27+
for _, cardSlug := range cardSlugs {
28+
cardFolder := path.Join("./" + cardSlug)
29+
err := createFolder(cardFolder)
30+
if err != nil {
31+
fmt.Println(err)
32+
return
33+
}
34+
card := getCard(cardSlug)
35+
for _, c := range card.Chapters {
36+
chapter := getChapter(c.Id, cardSlug)
37+
chapterFolder := path.Join(cardFolder +"/"+ chapter.Slug)
38+
err := createFolder(chapterFolder)
39+
if err != nil {
40+
fmt.Println(err)
41+
return
42+
}
43+
for _, item := range chapter.Items {
44+
buildProblem(item.Id, chapterFolder)
45+
}
46+
}
47+
}
48+
}

create.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"path"
9+
)
10+
11+
const problemBaseCN = "https://leetcode-cn.com/problems/"
12+
const problemBase = "https://leetcode.com/problems/"
13+
14+
func createQuestion(basePath string, item *Item, question *Question) error {
15+
titleSlug := item.Question.TitleSlug
16+
folder := path.Join(basePath, titleSlug)
17+
err := createFolder(folder)
18+
if err != nil {
19+
return err
20+
}
21+
defaultCode := parseDefaultCode(question.CodeDefinition)
22+
err = createSolutionFile(folder, defaultCode)
23+
if err != nil {
24+
return err
25+
}
26+
err = createReadmeFile(folder, item.Title, titleSlug, question.TranslatedContent)
27+
if err != nil {
28+
return err
29+
}
30+
return nil
31+
}
32+
33+
func createFolder(folder string) error {
34+
err := os.Mkdir(folder, os.ModePerm)
35+
if err != nil {
36+
if os.IsExist(err) {
37+
fmt.Println("folder existed: ", folder)
38+
return err
39+
}
40+
}
41+
return nil
42+
}
43+
44+
func createSolutionFile(folder, defaultCode string) error {
45+
solutionPath := path.Join(folder, "solution.go")
46+
f, err := os.Create(solutionPath)
47+
if err != nil {
48+
return fmt.Errorf("failed to create file: %s\n", solutionPath)
49+
}
50+
defer f.Close()
51+
w := bufio.NewWriter(f)
52+
fmt.Fprintln(w, "package main")
53+
fmt.Fprintln(w)
54+
fmt.Fprint(w, defaultCode)
55+
return w.Flush()
56+
}
57+
58+
func createReadmeFile(folder, title, titleSlug, content string) error {
59+
readmePath := path.Join(folder, "readme.md")
60+
f, err := os.Create(readmePath)
61+
if err != nil {
62+
return fmt.Errorf("failed to create file: %s\n", readmePath)
63+
}
64+
defer f.Close()
65+
w := bufio.NewWriter(f)
66+
fmt.Fprintln(w, "## "+title)
67+
fmt.Fprintln(w)
68+
fmt.Fprint(w, content)
69+
fmt.Fprintln(w)
70+
fmt.Fprintln(w,"-----")
71+
fmt.Fprintln(w)
72+
fmt.Fprintln(w, "### 链接:")
73+
fmt.Fprintln(w)
74+
fmt.Fprintln(w, "中文:"+problemBaseCN+titleSlug)
75+
fmt.Fprintln(w)
76+
fmt.Fprintln(w, "英文:"+problemBase+titleSlug)
77+
return w.Flush()
78+
}
79+
80+
func parseDefaultCode(codeDeinition string) string {
81+
var CodeDefinitions []CodeDefinition
82+
bytes := []byte(codeDeinition)
83+
err := json.Unmarshal(bytes, &CodeDefinitions)
84+
if err != nil {
85+
fmt.Println("Failed to parse code definition json: ", err)
86+
return ""
87+
}
88+
for _, item := range CodeDefinitions {
89+
if item.Value == "golang" {
90+
return item.DefaultCode
91+
}
92+
}
93+
return ""
94+
}
95+
96+
type CodeDefinition struct {
97+
Value string
98+
Text string
99+
DefaultCode string
100+
}

main.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
)
7+
8+
func main() {
9+
// 按需爬取题目,保存到本地文件夹中:readme.md(题目描述),solution函数(main包)。
10+
// 抓取并构建卡片中的所有题目
11+
// 所需要的信息:
12+
// 简要:id,title,type,question{questionId,title,titleSlug}
13+
// 详细:questionId,questionTitle,categoryTitle,codeDefinition,content,translatedContent
14+
id := flag.String("id", "", "the ID of an problem item")
15+
isAll := flag.Bool("all", false, "build all problems")
16+
flag.Parse()
17+
if *isAll {
18+
buildAllProblems()
19+
} else {
20+
if len(*id) == 0 {
21+
fmt.Println("Please input the Id of problem item.")
22+
return
23+
} else {
24+
buildProblem(*id,"./")
25+
}
26+
}
27+
}

query.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
"net/http"
8+
"strings"
9+
)
10+
11+
const ApiUrl = "https://leetcode-cn.com/graphql"
12+
const ContentType = "application/json"
13+
14+
const getItemTmpl = `{"operationName":"GetItem","variables":{"itemId":"%s"},"query":"query GetItem($itemId: String!) {\n item(id: $itemId) {\n id\n title\n type\n paidOnly\n lang\n question {\n questionId\n title\n titleSlug\n __typename\n }\n article {\n id\n title\n __typename\n }\n video {\n id\n __typename\n }\n htmlArticle {\n id\n __typename\n }\n webPage {\n id\n __typename\n }\n __typename\n }\n isCurrentUserAuthenticated\n}\n"}`
15+
const getQuestionTmpl = `{"operationName":"GetQuestion","variables":{"titleSlug":"%s"},"query":"query GetQuestion($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n questionId\n sessionId\n questionTitle\n categoryTitle\n submitUrl\n interpretUrl\n codeDefinition\n sampleTestCase\n enableTestMode\n metaData\n langToValidPlayground\n enableRunCode\n enableSubmit\n judgerAvailable\n infoVerified\n envInfo\n content\n translatedContent\n urlManager\n __typename\n }\n isCurrentUserAuthenticated\n}\n"}`
16+
const getCardDetailTmpl = `{"operationName":"GetExtendedCardDetail","variables":{"cardSlug":"%s"},"query":"query GetExtendedCardDetail($cardSlug: String!) {\n card(cardSlug: $cardSlug) {\n id\n title\n slug\n description\n introduction\n chapters {\n id\n __typename\n }\n __typename\n }\n}\n"}`
17+
const getChapterTmpl = `{"operationName":"GetChapter","variables":{"chapterId":"%s","cardSlug":"%s"},"query":"query GetChapter($chapterId: String, $cardSlug: String) {\n chapter(chapterId: $chapterId, cardSlug: $cardSlug) {\n ...ExtendedChapterDetail\n description\n __typename\n }\n}\n\nfragment ExtendedChapterDetail on ChapterNode {\n id\n title\n slug\n items {\n id\n title\n type\n info\n paidOnly\n chapterId\n prerequisites {\n id\n chapterId\n __typename\n }\n __typename\n }\n __typename\n}\n"}`
18+
19+
type Item struct {
20+
Id string
21+
Title string
22+
Question struct {
23+
QuestionId string
24+
Title string
25+
TitleSlug string
26+
}
27+
}
28+
29+
func getItem(id string) *Item {
30+
body := fmt.Sprintf(getItemTmpl, id)
31+
decoder := postRequestWith(body)
32+
wrapper := struct {
33+
Data struct {
34+
Item Item
35+
}
36+
}{}
37+
err := decoder.Decode(&wrapper)
38+
if err != nil {
39+
log.Fatal(err)
40+
}
41+
return &wrapper.Data.Item
42+
}
43+
44+
type Question struct {
45+
QuestionId string
46+
QuestionTitle string
47+
CategoryTitle string
48+
CodeDefinition string
49+
Content string
50+
TranslatedContent string
51+
}
52+
53+
func getQuestion(titleSlug string) *Question {
54+
body := fmt.Sprintf(getQuestionTmpl, titleSlug)
55+
decoder := postRequestWith(body)
56+
wrapper := struct {
57+
Data struct {
58+
Question Question
59+
}
60+
}{}
61+
err := decoder.Decode(&wrapper)
62+
if err != nil {
63+
log.Fatal(err)
64+
}
65+
return &wrapper.Data.Question
66+
}
67+
68+
type Card struct {
69+
Chapters []struct {
70+
Id string
71+
}
72+
}
73+
74+
func getCard(cardSlug string) *Card {
75+
body := fmt.Sprintf(getCardDetailTmpl, cardSlug)
76+
decoder := postRequestWith(body)
77+
wrapper := struct {
78+
Data struct {
79+
Card Card
80+
}
81+
}{}
82+
err := decoder.Decode(&wrapper)
83+
if err != nil {
84+
log.Fatal(err)
85+
}
86+
return &wrapper.Data.Card
87+
}
88+
89+
type Chapter struct {
90+
Slug string
91+
Items []struct {
92+
Id string
93+
}
94+
}
95+
96+
func getChapter(chapterId, cardSlug string) *Chapter {
97+
body := fmt.Sprintf(getChapterTmpl, chapterId, cardSlug)
98+
decoder := postRequestWith(body)
99+
wrapper := struct {
100+
Data struct {
101+
Chapter Chapter
102+
}
103+
}{}
104+
err := decoder.Decode(&wrapper)
105+
if err != nil {
106+
log.Fatal(err)
107+
}
108+
return &wrapper.Data.Chapter
109+
}
110+
111+
func postRequestWith(requestBody string) *json.Decoder {
112+
bodyReader := strings.NewReader(requestBody)
113+
resp, err := http.Post(ApiUrl, ContentType, bodyReader)
114+
if err != nil {
115+
fmt.Printf("error when query: %T:%v\n", err, err)
116+
}
117+
return json.NewDecoder(resp.Body)
118+
}

query_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package main
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
)
7+
8+
func TestGetItem(t *testing.T) {
9+
id := "99"
10+
want := &Item{
11+
Id: "99",
12+
Title: "寻找峰值",
13+
Question: struct {
14+
QuestionId string
15+
Title string
16+
TitleSlug string
17+
}{
18+
"162",
19+
"Find Peak Element",
20+
"find-peak-element",
21+
},
22+
}
23+
got := getItem(id)
24+
if !reflect.DeepEqual(got, want) {
25+
t.Errorf("with %s, got %+v, want %+v\n", id, want, got)
26+
}
27+
}
28+
29+
func TestGetQuestion(t *testing.T) {
30+
titleSlug := "3sum"
31+
want := &Question{
32+
QuestionId: "15",
33+
}
34+
got := getQuestion(titleSlug)
35+
if want.QuestionId != got.QuestionId {
36+
t.Errorf("with %s, got %+v, want %+v\n", titleSlug, want, got)
37+
}
38+
}
39+
40+
func TestGetCard(t *testing.T) {
41+
cardSlug := "top-interview-questions-medium"
42+
want := &Card{
43+
[]struct{
44+
Id string
45+
}{
46+
{"29"},
47+
{"31"},
48+
{"32"},
49+
{"49"},
50+
{"50"},
51+
{"51"},
52+
{"52"},
53+
{"53"},
54+
{"54"},
55+
},
56+
}
57+
got := getCard(cardSlug)
58+
if !reflect.DeepEqual(got, want) {
59+
t.Errorf("with %s, got %+v, want %+v\n", cardSlug, want, got)
60+
}
61+
}
62+
63+
func TestGetChapter(t *testing.T) {
64+
chapterId := "29"
65+
cardSlug := "top-interview-questions-medium"
66+
want := &Chapter{
67+
"array-and-strings",
68+
[]struct{
69+
Id string
70+
} {
71+
{"75"},
72+
{"76"},
73+
{"77"},
74+
{"78"},
75+
{"79"},
76+
{"80"},
77+
},
78+
}
79+
got := getChapter(chapterId, cardSlug)
80+
if !reflect.DeepEqual(got, want) {
81+
t.Errorf("with %s,%s, got %+v, want %+v\n", chapterId, cardSlug, want, got)
82+
}
83+
}

0 commit comments

Comments
 (0)