mirror of
https://github.com/halfrost/LeetCode-Go.git
synced 2025-07-05 08:27:30 +08:00
Add person data statistic
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.toml
|
@ -1,43 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type LeetCodeProblemAll struct {
|
|
||||||
UserName string `json:"user_name"`
|
|
||||||
NumSolved int32 `json:"num_solved"`
|
|
||||||
NumTotal int32 `json:"num_total"`
|
|
||||||
AcEasy int32 `json:"ac_easy"`
|
|
||||||
AcMedium int32 `json:"ac_medium"`
|
|
||||||
AcHard int32 `json:"ac_hard"`
|
|
||||||
StatStatusPairs []StatStatusPairs `json:"stat_status_pairs"`
|
|
||||||
FrequencyHigh int32 `json:"frequency_high"`
|
|
||||||
FrequencyMid int32 `json:"frequency_mid"`
|
|
||||||
CategorySlug string `json:"category_slug"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type StatStatusPairs struct {
|
|
||||||
Stat Stat `json:"stat"`
|
|
||||||
Difficulty Difficulty `json:"difficulty"`
|
|
||||||
PaidOnly bool `json:"paid_only"`
|
|
||||||
IsFavor bool `json:"is_favor"`
|
|
||||||
Frequency float64 `json:"frequency"`
|
|
||||||
Progress float64 `json:"progress"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Stat struct {
|
|
||||||
QuestionTitle string `json:"question__title"`
|
|
||||||
QuestionTitleSlug string `json:"question__title_slug"`
|
|
||||||
TotalAcs float64 `json:"total_acs"`
|
|
||||||
TotalSubmitted float64 `json:"total_submitted"`
|
|
||||||
Acceptance string
|
|
||||||
Difficulty string
|
|
||||||
FrontendQuestionId int32 `json:"frontend_question_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Difficulty struct {
|
|
||||||
Level int32 `json:"level"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var DifficultyMap = map[int32]string{
|
|
||||||
1: "Easy",
|
|
||||||
2: "Medium",
|
|
||||||
3: "Hard",
|
|
||||||
}
|
|
@ -1,190 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
m "github.com/halfrost/LeetCode-Go/automation/models"
|
|
||||||
"html/template"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var try int
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var (
|
|
||||||
result []m.StatStatusPairs
|
|
||||||
lpa m.LeetCodeProblemAll
|
|
||||||
)
|
|
||||||
|
|
||||||
body := getProblemAllList()
|
|
||||||
err := json.Unmarshal(body, &lpa)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result = lpa.StatStatusPairs
|
|
||||||
mdrows := []m.Mdrow{}
|
|
||||||
for i := 0; i < len(result); i++ {
|
|
||||||
mdrows = append(mdrows, convertModel(result[i]))
|
|
||||||
}
|
|
||||||
sort.Sort(m.SortByQuestionId(mdrows))
|
|
||||||
solutionIds := loadSolutionsDir()
|
|
||||||
generateMdRows(solutionIds, mdrows)
|
|
||||||
// res, _ := json.Marshal(mdrows)
|
|
||||||
//writeFile("leetcode_problem", res)
|
|
||||||
mds := m.Mdrows{Mdrows: mdrows}
|
|
||||||
res, err := readFile("./template.markdown", "{{.AvailableTable}}", len(solutionIds), try, mds)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
writeFile("../README.md", res)
|
|
||||||
//makeReadmeFile(mds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getProblemAllList() []byte {
|
|
||||||
resp, err := http.Get("https://leetcode.com/api/problems/all/")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
if resp.StatusCode == 200 {
|
|
||||||
fmt.Println("ok")
|
|
||||||
}
|
|
||||||
return body
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeFile(fileName string, content []byte) {
|
|
||||||
file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0777)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
_, err = file.Write(content)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
fmt.Println("write file successful")
|
|
||||||
}
|
|
||||||
|
|
||||||
func convertModel(ssp m.StatStatusPairs) m.Mdrow {
|
|
||||||
res := m.Mdrow{}
|
|
||||||
res.FrontendQuestionId = ssp.Stat.FrontendQuestionId
|
|
||||||
res.QuestionTitle = ssp.Stat.QuestionTitle
|
|
||||||
res.QuestionTitleSlug = ssp.Stat.QuestionTitleSlug
|
|
||||||
res.Acceptance = fmt.Sprintf("%.1f%%", (ssp.Stat.TotalAcs/ssp.Stat.TotalSubmitted)*100)
|
|
||||||
res.Difficulty = m.DifficultyMap[ssp.Difficulty.Level]
|
|
||||||
res.Frequency = fmt.Sprintf("%f", ssp.Frequency)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadSolutionsDir() []int {
|
|
||||||
files, err := ioutil.ReadDir("../leetcode/")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
solutionIds := []int{}
|
|
||||||
for _, f := range files {
|
|
||||||
if f.Name()[4] == '.' {
|
|
||||||
tmp, err := strconv.Atoi(f.Name()[:4])
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
solutionIds = append(solutionIds, tmp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Ints(solutionIds)
|
|
||||||
try = len(files) - len(solutionIds)
|
|
||||||
fmt.Printf("读取了 %v 道题的题解,当前目录下有 %v 个文件(可能包含 .DS_Store),有 %v 道题在尝试中\n", len(solutionIds), len(files), len(files)-len(solutionIds))
|
|
||||||
return solutionIds
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateMdRows(solutionIds []int, mdrows []m.Mdrow) {
|
|
||||||
for i := 0; i < len(solutionIds); i++ {
|
|
||||||
id := mdrows[solutionIds[i]-1].FrontendQuestionId
|
|
||||||
if solutionIds[i] == int(id) {
|
|
||||||
//fmt.Printf("id = %v i = %v solutionIds = %v\n", id, i, solutionIds[i])
|
|
||||||
mdrows[id-1].SolutionPath = fmt.Sprintf("[Go](https://github.com/halfrost/LeetCode-Go/tree/master/leetcode/%v)", fmt.Sprintf("%04d.%v", id, strings.Replace(mdrows[id-1].QuestionTitle, " ", "-", -1)))
|
|
||||||
} else {
|
|
||||||
fmt.Printf("序号出错了 solutionIds = %v id = %v\n", solutionIds[i], id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("")
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeReadmeFile(mdrows m.Mdrows) {
|
|
||||||
file := "./README.md"
|
|
||||||
os.Remove(file)
|
|
||||||
var b bytes.Buffer
|
|
||||||
tmpl := template.Must(template.New("readme").Parse(readTMPL("template.markdown")))
|
|
||||||
err := tmpl.Execute(&b, mdrows)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
// 保存 README.md 文件
|
|
||||||
writeFile(file, b.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
func readTMPL(path string) string {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
data, err := ioutil.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFile(filePath, template string, total, try int, mdrows m.Mdrows) ([]byte, error) {
|
|
||||||
f, err := os.OpenFile(filePath, os.O_RDONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
reader, output := bufio.NewReader(f), []byte{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
line, _, err := reader.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return output, nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ok, _ := regexp.Match(template, line); ok {
|
|
||||||
reg := regexp.MustCompile(template)
|
|
||||||
newByte := reg.ReplaceAll(line, []byte(mdrows.AvailableTable()))
|
|
||||||
output = append(output, newByte...)
|
|
||||||
output = append(output, []byte("\n")...)
|
|
||||||
} else if ok, _ := regexp.Match("{{.TotalNum}}", line); ok {
|
|
||||||
reg := regexp.MustCompile("{{.TotalNum}}")
|
|
||||||
newByte := reg.ReplaceAll(line, []byte(fmt.Sprintf("以下已经收录了 %v 道题的题解,还有 %v 道题在尝试优化到 beats 100%%", total, try)))
|
|
||||||
output = append(output, newByte...)
|
|
||||||
output = append(output, []byte("\n")...)
|
|
||||||
} else {
|
|
||||||
output = append(output, line...)
|
|
||||||
output = append(output, []byte("\n")...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
31
ctl/config.go
Normal file
31
ctl/config.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configTOML = "config.toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
Cookie string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c config) String() string {
|
||||||
|
return fmt.Sprintf("Username: %s, Password: %s", c.Username, c.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfig() *config {
|
||||||
|
cfg := new(config)
|
||||||
|
if _, err := toml.DecodeFile(configTOML, &cfg); err != nil {
|
||||||
|
log.Panicf(err.Error())
|
||||||
|
}
|
||||||
|
// log.Printf("get config: %s", cfg)
|
||||||
|
return cfg
|
||||||
|
}
|
103
ctl/models/lcproblems.go
Normal file
103
ctl/models/lcproblems.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LeetCodeProblemAll define
|
||||||
|
type LeetCodeProblemAll struct {
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
NumSolved int32 `json:"num_solved"`
|
||||||
|
NumTotal int32 `json:"num_total"`
|
||||||
|
AcEasy int32 `json:"ac_easy"`
|
||||||
|
AcMedium int32 `json:"ac_medium"`
|
||||||
|
AcHard int32 `json:"ac_hard"`
|
||||||
|
StatStatusPairs []StatStatusPairs `json:"stat_status_pairs"`
|
||||||
|
FrequencyHigh int32 `json:"frequency_high"`
|
||||||
|
FrequencyMid int32 `json:"frequency_mid"`
|
||||||
|
CategorySlug string `json:"category_slug"`
|
||||||
|
AcEasyTotal int32
|
||||||
|
AcMediumTotal int32
|
||||||
|
AcHardTotal int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertUserInfoModel define
|
||||||
|
func ConvertUserInfoModel(lpa LeetCodeProblemAll) UserInfo {
|
||||||
|
info := UserInfo{}
|
||||||
|
info.UserName = lpa.UserName
|
||||||
|
info.NumSolved = lpa.NumSolved
|
||||||
|
info.NumTotal = lpa.NumTotal
|
||||||
|
info.AcEasy = lpa.AcEasy
|
||||||
|
info.AcMedium = lpa.AcMedium
|
||||||
|
info.AcHard = lpa.AcHard
|
||||||
|
info.FrequencyHigh = lpa.FrequencyHigh
|
||||||
|
info.FrequencyMid = lpa.FrequencyMid
|
||||||
|
info.CategorySlug = lpa.CategorySlug
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatStatusPairs define
|
||||||
|
type StatStatusPairs struct {
|
||||||
|
Stat Stat `json:"stat"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Difficulty Difficulty `json:"difficulty"`
|
||||||
|
PaidOnly bool `json:"paid_only"`
|
||||||
|
IsFavor bool `json:"is_favor"`
|
||||||
|
Frequency float64 `json:"frequency"`
|
||||||
|
Progress float64 `json:"progress"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertMdModel define
|
||||||
|
func ConvertMdModel(problems []StatStatusPairs) []Mdrow {
|
||||||
|
mdrows := []Mdrow{}
|
||||||
|
for _, problem := range problems {
|
||||||
|
res := Mdrow{}
|
||||||
|
res.FrontendQuestionID = problem.Stat.FrontendQuestionID
|
||||||
|
res.QuestionTitle = problem.Stat.QuestionTitle
|
||||||
|
res.QuestionTitleSlug = problem.Stat.QuestionTitleSlug
|
||||||
|
res.Acceptance = fmt.Sprintf("%.1f%%", (problem.Stat.TotalAcs/problem.Stat.TotalSubmitted)*100)
|
||||||
|
res.Difficulty = DifficultyMap[problem.Difficulty.Level]
|
||||||
|
res.Frequency = fmt.Sprintf("%f", problem.Frequency)
|
||||||
|
mdrows = append(mdrows, res)
|
||||||
|
}
|
||||||
|
return mdrows
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertMdModelFromIds define
|
||||||
|
func ConvertMdModelFromIds(problemsMap map[int]StatStatusPairs, ids []int) []Mdrow {
|
||||||
|
mdrows := []Mdrow{}
|
||||||
|
for _, v := range ids {
|
||||||
|
res, problem := Mdrow{}, problemsMap[v]
|
||||||
|
res.FrontendQuestionID = problem.Stat.FrontendQuestionID
|
||||||
|
res.QuestionTitle = problem.Stat.QuestionTitle
|
||||||
|
res.QuestionTitleSlug = problem.Stat.QuestionTitleSlug
|
||||||
|
res.Acceptance = fmt.Sprintf("%.1f%%", (problem.Stat.TotalAcs/problem.Stat.TotalSubmitted)*100)
|
||||||
|
res.Difficulty = DifficultyMap[problem.Difficulty.Level]
|
||||||
|
res.Frequency = fmt.Sprintf("%f", problem.Frequency)
|
||||||
|
mdrows = append(mdrows, res)
|
||||||
|
}
|
||||||
|
return mdrows
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat define
|
||||||
|
type Stat struct {
|
||||||
|
QuestionTitle string `json:"question__title"`
|
||||||
|
QuestionTitleSlug string `json:"question__title_slug"`
|
||||||
|
TotalAcs float64 `json:"total_acs"`
|
||||||
|
TotalSubmitted float64 `json:"total_submitted"`
|
||||||
|
Acceptance string
|
||||||
|
Difficulty string
|
||||||
|
FrontendQuestionID int32 `json:"frontend_question_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Difficulty define
|
||||||
|
type Difficulty struct {
|
||||||
|
Level int32 `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DifficultyMap define
|
||||||
|
var DifficultyMap = map[int32]string{
|
||||||
|
1: "Easy",
|
||||||
|
2: "Medium",
|
||||||
|
3: "Hard",
|
||||||
|
}
|
@ -4,8 +4,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Mdrow define
|
||||||
type Mdrow struct {
|
type Mdrow struct {
|
||||||
FrontendQuestionId int32 `json:"question_id"`
|
FrontendQuestionID int32 `json:"question_id"`
|
||||||
QuestionTitle string `json:"question__title"`
|
QuestionTitle string `json:"question__title"`
|
||||||
QuestionTitleSlug string `json:"question__title_slug"`
|
QuestionTitleSlug string `json:"question__title_slug"`
|
||||||
SolutionPath string `json:"solution_path"`
|
SolutionPath string `json:"solution_path"`
|
||||||
@ -16,18 +17,19 @@ type Mdrow struct {
|
|||||||
|
|
||||||
// | 0001 | Two Sum | [Go](https://github.com/halfrost/LeetCode-Go/tree/master/leetcode/0001.Two-Sum)| 45.6% | Easy | |
|
// | 0001 | Two Sum | [Go](https://github.com/halfrost/LeetCode-Go/tree/master/leetcode/0001.Two-Sum)| 45.6% | Easy | |
|
||||||
func (m Mdrow) tableLine() string {
|
func (m Mdrow) tableLine() string {
|
||||||
return fmt.Sprintf("|%04d|%v|%v|%v|%v||\n", m.FrontendQuestionId, m.QuestionTitle, m.SolutionPath, m.Acceptance, m.Difficulty)
|
return fmt.Sprintf("|%04d|%v|%v|%v|%v||\n", m.FrontendQuestionID, m.QuestionTitle, m.SolutionPath, m.Acceptance, m.Difficulty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortByQuestionId define
|
// SortByQuestionID define
|
||||||
type SortByQuestionId []Mdrow
|
type SortByQuestionID []Mdrow
|
||||||
|
|
||||||
func (a SortByQuestionId) Len() int { return len(a) }
|
func (a SortByQuestionID) Len() int { return len(a) }
|
||||||
func (a SortByQuestionId) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
func (a SortByQuestionID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
func (a SortByQuestionId) Less(i, j int) bool {
|
func (a SortByQuestionID) Less(i, j int) bool {
|
||||||
return a[i].FrontendQuestionId < a[j].FrontendQuestionId
|
return a[i].FrontendQuestionID < a[j].FrontendQuestionID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mdrows define
|
||||||
type Mdrows struct {
|
type Mdrows struct {
|
||||||
Mdrows []Mdrow
|
Mdrows []Mdrow
|
||||||
}
|
}
|
||||||
@ -45,6 +47,7 @@ func (mds Mdrows) table() string {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AvailableTable define
|
||||||
func (mds Mdrows) AvailableTable() string {
|
func (mds Mdrows) AvailableTable() string {
|
||||||
return mds.table()
|
return mds.table()
|
||||||
}
|
}
|
44
ctl/models/user.go
Normal file
44
ctl/models/user.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserInfo define
|
||||||
|
type UserInfo struct {
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
NumSolved int32 `json:"num_solved"`
|
||||||
|
NumTotal int32 `json:"num_total"`
|
||||||
|
AcEasy int32 `json:"ac_easy"`
|
||||||
|
AcMedium int32 `json:"ac_medium"`
|
||||||
|
AcHard int32 `json:"ac_hard"`
|
||||||
|
EasyTotal int32
|
||||||
|
MediumTotal int32
|
||||||
|
HardTotal int32
|
||||||
|
OptimizingEasy int32
|
||||||
|
OptimizingMedium int32
|
||||||
|
OptimizingHard int32
|
||||||
|
FrequencyHigh int32 `json:"frequency_high"`
|
||||||
|
FrequencyMid int32 `json:"frequency_mid"`
|
||||||
|
CategorySlug string `json:"category_slug"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// | | Easy | Medium | Hard | Total | optimizing |
|
||||||
|
// |:--------:|:--------------------------------------------------------------|:--------:|:--------:|:--------:|:--------:|
|
||||||
|
func (ui UserInfo) table() string {
|
||||||
|
res := "| | Easy | Medium | Hard | Total |\n"
|
||||||
|
res += "|:--------:|:--------:|:--------:|:--------:|:--------:|\n"
|
||||||
|
res += fmt.Sprintf("|Optimizing|%v|%v|%v|%v|\n", ui.OptimizingEasy, ui.OptimizingMedium, ui.OptimizingHard, ui.OptimizingEasy+ui.OptimizingMedium+ui.OptimizingHard)
|
||||||
|
res += fmt.Sprintf("|Accepted|**%v**|**%v**|**%v**|**%v**|\n", ui.AcEasy, ui.AcMedium, ui.AcHard, ui.AcEasy+ui.AcMedium+ui.AcHard)
|
||||||
|
res += fmt.Sprintf("|Total|%v|%v|%v|%v|\n", ui.EasyTotal, ui.MediumTotal, ui.HardTotal, ui.EasyTotal+ui.MediumTotal+ui.HardTotal)
|
||||||
|
res += fmt.Sprintf("|Perfection Rate|%.1f%%|%.1f%%|%.1f%%|%.1f%%|\n", (1-float64(ui.OptimizingEasy)/float64(ui.AcEasy))*100, (1-float64(ui.OptimizingMedium)/float64(ui.AcMedium))*100, (1-float64(ui.OptimizingHard)/float64(ui.AcHard))*100, (1-float64(ui.OptimizingEasy+ui.OptimizingMedium+ui.OptimizingHard)/float64(ui.AcEasy+ui.AcMedium+ui.AcHard))*100)
|
||||||
|
res += fmt.Sprintf("|Completion Rate|%.1f%%|%.1f%%|%.1f%%|%.1f%%|\n", float64(ui.AcEasy)/float64(ui.EasyTotal)*100, float64(ui.AcMedium)/float64(ui.MediumTotal)*100, float64(ui.AcHard)/float64(ui.HardTotal)*100, float64(ui.AcEasy+ui.AcMedium+ui.AcHard)/float64(ui.EasyTotal+ui.MediumTotal+ui.HardTotal)*100)
|
||||||
|
// 加这一行是为了撑开整个表格
|
||||||
|
res += "|------------|----------------------------|----------------------------|----------------------------|----------------------------|"
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// PersonalData define
|
||||||
|
func (ui UserInfo) PersonalData() string {
|
||||||
|
return ui.table()
|
||||||
|
}
|
36
ctl/rangking.go
Normal file
36
ctl/rangking.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getRanking 让这个方法优雅一点
|
||||||
|
func getRanking() int {
|
||||||
|
// 获取网页数据
|
||||||
|
URL := fmt.Sprintf("https://leetcode.com/%s/", getConfig().Username)
|
||||||
|
data := getRaw(URL)
|
||||||
|
str := string(data)
|
||||||
|
// 通过不断裁剪 str 获取排名信息
|
||||||
|
fmt.Println(str)
|
||||||
|
i := strings.Index(str, "ng-init")
|
||||||
|
j := i + strings.Index(str[i:], "ng-cloak")
|
||||||
|
str = str[i:j]
|
||||||
|
i = strings.Index(str, "(")
|
||||||
|
j = strings.Index(str, ")")
|
||||||
|
str = str[i:j]
|
||||||
|
// fmt.Println("2\n", str)
|
||||||
|
strs := strings.Split(str, ",")
|
||||||
|
str = strs[6]
|
||||||
|
// fmt.Println("1\n", str)
|
||||||
|
i = strings.Index(str, "'")
|
||||||
|
j = 2 + strings.Index(str[2:], "'")
|
||||||
|
// fmt.Println("0\n", str)
|
||||||
|
str = str[i+1 : j]
|
||||||
|
r, err := strconv.Atoi(str)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("无法把 %s 转换成数字Ranking", str)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
108
ctl/render.go
Normal file
108
ctl/render.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
m "github.com/halfrost/LeetCode-Go/ctl/models"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var (
|
||||||
|
problems []m.StatStatusPairs
|
||||||
|
lpa m.LeetCodeProblemAll
|
||||||
|
info m.UserInfo
|
||||||
|
)
|
||||||
|
// 请求所有题目信息
|
||||||
|
body := getProblemAllList(AllProblemURL)
|
||||||
|
problemsMap, optimizingIds := map[int]m.StatStatusPairs{}, []int{}
|
||||||
|
err := json.Unmarshal(body, &lpa)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//writeFile("leetcode_problem.json", body)
|
||||||
|
|
||||||
|
// 拼凑 README 需要渲染的数据
|
||||||
|
problems = lpa.StatStatusPairs
|
||||||
|
info = m.ConvertUserInfoModel(lpa)
|
||||||
|
for _, v := range problems {
|
||||||
|
problemsMap[int(v.Stat.FrontendQuestionID)] = v
|
||||||
|
}
|
||||||
|
mdrows := m.ConvertMdModel(problems)
|
||||||
|
sort.Sort(m.SortByQuestionID(mdrows))
|
||||||
|
solutionIds, try := loadSolutionsDir()
|
||||||
|
generateMdRows(solutionIds, mdrows)
|
||||||
|
info.EasyTotal, info.MediumTotal, info.HardTotal, info.OptimizingEasy, info.OptimizingMedium, info.OptimizingHard, optimizingIds = statisticalData(problemsMap, solutionIds)
|
||||||
|
omdrows := m.ConvertMdModelFromIds(problemsMap, optimizingIds)
|
||||||
|
sort.Sort(m.SortByQuestionID(omdrows))
|
||||||
|
|
||||||
|
// 按照模板渲染 README
|
||||||
|
res, err := renderReadme("./template.markdown", len(solutionIds), try, m.Mdrows{Mdrows: mdrows}, m.Mdrows{Mdrows: omdrows}, info)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeFile("../README.md", res)
|
||||||
|
//makeReadmeFile(mds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateMdRows(solutionIds []int, mdrows []m.Mdrow) {
|
||||||
|
for i := 0; i < len(solutionIds); i++ {
|
||||||
|
id := mdrows[solutionIds[i]-1].FrontendQuestionID
|
||||||
|
if solutionIds[i] == int(id) {
|
||||||
|
//fmt.Printf("id = %v i = %v solutionIds = %v\n", id, i, solutionIds[i])
|
||||||
|
mdrows[id-1].SolutionPath = fmt.Sprintf("[Go](https://github.com/halfrost/LeetCode-Go/tree/master/leetcode/%v)", fmt.Sprintf("%04d.%v", id, strings.Replace(mdrows[id-1].QuestionTitle, " ", "-", -1)))
|
||||||
|
} else {
|
||||||
|
fmt.Printf("序号出错了 solutionIds = %v id = %v\n", solutionIds[i], id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderReadme(filePath string, total, try int, mdrows, omdrows m.Mdrows, user m.UserInfo) ([]byte, error) {
|
||||||
|
f, err := os.OpenFile(filePath, os.O_RDONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
reader, output := bufio.NewReader(f), []byte{}
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, _, err := reader.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if ok, _ := regexp.Match("{{.AvailableTable}}", line); ok {
|
||||||
|
reg := regexp.MustCompile("{{.AvailableTable}}")
|
||||||
|
newByte := reg.ReplaceAll(line, []byte(mdrows.AvailableTable()))
|
||||||
|
output = append(output, newByte...)
|
||||||
|
output = append(output, []byte("\n")...)
|
||||||
|
} else if ok, _ := regexp.Match("{{.TotalNum}}", line); ok {
|
||||||
|
reg := regexp.MustCompile("{{.TotalNum}}")
|
||||||
|
newByte := reg.ReplaceAll(line, []byte(fmt.Sprintf("以下已经收录了 %v 道题的题解,还有 %v 道题在尝试优化到 beats 100%%", total, try)))
|
||||||
|
output = append(output, newByte...)
|
||||||
|
output = append(output, []byte("\n")...)
|
||||||
|
} else if ok, _ := regexp.Match("{{.PersonalData}}", line); ok {
|
||||||
|
reg := regexp.MustCompile("{{.PersonalData}}")
|
||||||
|
newByte := reg.ReplaceAll(line, []byte(user.PersonalData()))
|
||||||
|
output = append(output, newByte...)
|
||||||
|
output = append(output, []byte("\n")...)
|
||||||
|
} else if ok, _ := regexp.Match("{{.OptimizingTable}}", line); ok {
|
||||||
|
reg := regexp.MustCompile("{{.OptimizingTable}}")
|
||||||
|
newByte := reg.ReplaceAll(line, []byte(fmt.Sprintf("以下 %v 道题还需要优化到 100%% 的题目列表\n\n%v", (user.OptimizingEasy+user.OptimizingMedium+user.OptimizingHard), omdrows.AvailableTable())))
|
||||||
|
output = append(output, newByte...)
|
||||||
|
output = append(output, []byte("\n")...)
|
||||||
|
} else {
|
||||||
|
output = append(output, line...)
|
||||||
|
output = append(output, []byte("\n")...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
72
ctl/request.go
Normal file
72
ctl/request.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mozillazg/request"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AllProblemURL define
|
||||||
|
AllProblemURL = "https://leetcode.com/api/problems/all/"
|
||||||
|
// LoginPageURL define
|
||||||
|
LoginPageURL = "https://leetcode.com/accounts/login/"
|
||||||
|
// AlgorithmsURL define
|
||||||
|
AlgorithmsURL = "https://leetcode.com/api/problems/Algorithms/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var req *request.Request
|
||||||
|
|
||||||
|
func newReq() *request.Request {
|
||||||
|
if req == nil {
|
||||||
|
req = signin()
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func signin() *request.Request {
|
||||||
|
cfg := getConfig()
|
||||||
|
req := request.NewRequest(new(http.Client))
|
||||||
|
req.Headers = map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept-Encoding": "",
|
||||||
|
"cookie": cfg.Cookie,
|
||||||
|
"Referer": "https://leetcode.com/accounts/login/",
|
||||||
|
"origin": "https://leetcode.com",
|
||||||
|
}
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRaw(URL string) []byte {
|
||||||
|
req := newReq()
|
||||||
|
resp, err := req.Get(URL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("getRaw: Get Error: " + err.Error())
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("getRaw: Read Error: " + err.Error())
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProblemAllList(URL string) []byte {
|
||||||
|
req := newReq()
|
||||||
|
resp, err := req.Get(URL)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
if resp.StatusCode == 200 {
|
||||||
|
fmt.Println("ok")
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
40
ctl/statistic.go
Normal file
40
ctl/statistic.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
m "github.com/halfrost/LeetCode-Go/ctl/models"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func statisticalData(problemsMap map[int]m.StatStatusPairs, solutionIds []int) (easyTotal, mediumTotal, hardTotal, optimizingEasy, optimizingMedium, optimizingHard int32, optimizingIds []int) {
|
||||||
|
easyTotal, mediumTotal, hardTotal, optimizingEasy, optimizingMedium, optimizingHard, optimizingIds = 0, 0, 0, 0, 0, 0, []int{}
|
||||||
|
for _, v := range problemsMap {
|
||||||
|
switch m.DifficultyMap[v.Difficulty.Level] {
|
||||||
|
case "Easy":
|
||||||
|
{
|
||||||
|
easyTotal++
|
||||||
|
if v.Status == "ac" && binarySearch(solutionIds, int(v.Stat.FrontendQuestionID)) == -1 {
|
||||||
|
optimizingEasy++
|
||||||
|
optimizingIds = append(optimizingIds, int(v.Stat.FrontendQuestionID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Medium":
|
||||||
|
{
|
||||||
|
mediumTotal++
|
||||||
|
if v.Status == "ac" && binarySearch(solutionIds, int(v.Stat.FrontendQuestionID)) == -1 {
|
||||||
|
optimizingMedium++
|
||||||
|
optimizingIds = append(optimizingIds, int(v.Stat.FrontendQuestionID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "Hard":
|
||||||
|
{
|
||||||
|
hardTotal++
|
||||||
|
if v.Status == "ac" && binarySearch(solutionIds, int(v.Stat.FrontendQuestionID)) == -1 {
|
||||||
|
optimizingHard++
|
||||||
|
optimizingIds = append(optimizingIds, int(v.Stat.FrontendQuestionID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Ints(optimizingIds)
|
||||||
|
return easyTotal, mediumTotal, hardTotal, optimizingEasy, optimizingMedium, optimizingHard, optimizingIds
|
||||||
|
}
|
@ -82,6 +82,9 @@
|
|||||||
* [✅ Segment Tree](#segment-tree)
|
* [✅ Segment Tree](#segment-tree)
|
||||||
* [✅ Binary Indexed Tree](#binary-indexed-tree)
|
* [✅ Binary Indexed Tree](#binary-indexed-tree)
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
| 数据结构 | 变种 | 相关题目 | 讲解文章 |
|
| 数据结构 | 变种 | 相关题目 | 讲解文章 |
|
||||||
|:-------:|:-------|:------|:------|
|
|:-------:|:-------|:------|:------|
|
||||||
|顺序线性表:向量||||
|
|顺序线性表:向量||||
|
||||||
@ -118,7 +121,11 @@
|
|||||||
|
|
||||||
## LeetCode Problems
|
## LeetCode Problems
|
||||||
|
|
||||||
## 一. 目录
|
## 一. 个人数据
|
||||||
|
|
||||||
|
{{.PersonalData}}
|
||||||
|
|
||||||
|
## 二. 目录
|
||||||
|
|
||||||
{{.TotalNum}}
|
{{.TotalNum}}
|
||||||
|
|
||||||
@ -133,7 +140,7 @@
|
|||||||
------------------------------------------------------------------
|
------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
## 二.分类
|
## 三.分类
|
||||||
|
|
||||||
## Array
|
## Array
|
||||||
|
|
37
ctl/template_render.go
Normal file
37
ctl/template_render.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
m "github.com/halfrost/LeetCode-Go/ctl/models"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeReadmeFile(mdrows m.Mdrows) {
|
||||||
|
file := "./README.md"
|
||||||
|
os.Remove(file)
|
||||||
|
var b bytes.Buffer
|
||||||
|
tmpl := template.Must(template.New("readme").Parse(readTMPL("template.markdown")))
|
||||||
|
err := tmpl.Execute(&b, mdrows)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
// 保存 README.md 文件
|
||||||
|
writeFile(file, b.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTMPL(path string) string {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
data, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
58
ctl/util.go
Normal file
58
ctl/util.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadSolutionsDir() ([]int, int) {
|
||||||
|
files, err := ioutil.ReadDir("../leetcode/")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
solutionIds := []int{}
|
||||||
|
for _, f := range files {
|
||||||
|
if f.Name()[4] == '.' {
|
||||||
|
tmp, err := strconv.Atoi(f.Name()[:4])
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
solutionIds = append(solutionIds, tmp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Ints(solutionIds)
|
||||||
|
fmt.Printf("读取了 %v 道题的题解,当前目录下有 %v 个文件(可能包含 .DS_Store),目录中有 %v 道题在尝试中\n", len(solutionIds), len(files), len(files)-len(solutionIds))
|
||||||
|
return solutionIds, len(files) - len(solutionIds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFile(fileName string, content []byte) {
|
||||||
|
file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0777)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write(content)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
fmt.Println("write file successful")
|
||||||
|
}
|
||||||
|
|
||||||
|
func binarySearch(nums []int, target int) int {
|
||||||
|
low, high := 0, len(nums)-1
|
||||||
|
for low <= high {
|
||||||
|
mid := low + (high-low)>>1
|
||||||
|
if nums[mid] == target {
|
||||||
|
return mid
|
||||||
|
} else if nums[mid] > target {
|
||||||
|
high = mid - 1
|
||||||
|
} else {
|
||||||
|
low = mid + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
Reference in New Issue
Block a user