diff --git a/README.md b/README.md index ad7fd8d..bed6e0b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ | ⚙️ | move json config to ini | | | 🧑‍💻 | | 👻 | list/commit problems | | 🦍 | 🤔 | | 👻 | set good random group scores | | 🦍 | 🤔 | +| 👻 | generate hasher solution for `.a` | | 🦍 | 🤔 | | 👻 | algolymp config manager | | | 🤔 | | 👻 | download/upload package | | 🦍 | 🤔 | | 👻 | import polygon problem | 🦍 | 🦍 | 🤔 | diff --git a/cmd/valeria/main.go b/cmd/valeria/main.go index 36266f2..a56974f 100644 --- a/cmd/valeria/main.go +++ b/cmd/valeria/main.go @@ -1,10 +1,12 @@ package main import ( + "fmt" "os" "github.com/Gornak40/algolymp/config" "github.com/Gornak40/algolymp/polygon" + "github.com/Gornak40/algolymp/polygon/valeria" "github.com/akamensky/argparse" "github.com/sirupsen/logrus" ) @@ -25,8 +27,12 @@ func main() { cfg := config.NewConfig() pClient := polygon.NewPolygon(&cfg.Polygon) + val := valeria.NewValeria(pClient) - if err := pClient.InformaticsValuer(*pID, *verbose); err != nil { + table := valeria.UniversalTable{} + if err := val.InformaticsValuer(*pID, &table, *verbose); err != nil { logrus.WithError(err).Fatal("failed get scoring") } + + fmt.Println(table.String()) //nolint:forbidigo // Basic functionality. } diff --git a/go.mod b/go.mod index 7f93cfe..f7b470b 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,13 @@ module github.com/Gornak40/algolymp go 1.20 require ( - github.com/PuerkitoBio/goquery v1.8.1 // indirect - github.com/akamensky/argparse v1.4.0 // indirect + github.com/PuerkitoBio/goquery v1.8.1 + github.com/akamensky/argparse v1.4.0 + github.com/sirupsen/logrus v1.9.3 +) + +require ( github.com/andybalholm/cascadia v1.3.1 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sys v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index 41e96cd..dfb1b93 100644 --- a/go.sum +++ b/go.sum @@ -5,11 +5,14 @@ github.com/akamensky/argparse v1.4.0/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -28,7 +31,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= @@ -46,4 +48,5 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/polygon/scoring.go b/polygon/scoring.go index 9ea69ee..47725a7 100644 --- a/polygon/scoring.go +++ b/polygon/scoring.go @@ -2,8 +2,6 @@ package polygon import ( "errors" - "fmt" - "strings" "github.com/sirupsen/logrus" ) @@ -18,117 +16,6 @@ var ( ErrBadTestsOrder = errors.New("bad tests order, fix in polygon required") ) -type Scoring struct { - score map[string]int - count map[string]int - first map[string]int - last map[string]int - dependencies map[string][]string - groups []string -} - -func NewScoring(tests []TestAnswer, groups []GroupAnswer) (*Scoring, error) { - scorer := Scoring{ - score: map[string]int{}, - count: map[string]int{}, - first: map[string]int{}, - last: map[string]int{}, - dependencies: map[string][]string{}, - } - for _, test := range tests { - scorer.score[test.Group] += int(test.Points) // TODO: ensure ejudge doesn't support float points - scorer.count[test.Group]++ - if val, ok := scorer.first[test.Group]; !ok || val > test.Index { - scorer.first[test.Group] = test.Index - } - if val, ok := scorer.last[test.Group]; !ok || val < test.Index { - scorer.last[test.Group] = test.Index - } - } - for _, group := range groups { - if group.PointsPolicy != "COMPLETE_GROUP" { - return nil, ErrNoTestScore - } - if scorer.last[group.Name]-scorer.first[group.Name]+1 != scorer.count[group.Name] { - return nil, ErrBadTestsOrder - } - scorer.dependencies[group.Name] = group.Dependencies - scorer.groups = append(scorer.groups, group.Name) - } - - return &scorer, nil -} - -func (s *Scoring) buildValuer() string { - res := []string{} - for _, g := range s.groups { - cur := fmt.Sprintf("group %s {\n\ttests %d-%d;\n\tscore %d;\n", - g, s.first[g], s.last[g], s.score[g]) - if len(s.dependencies[g]) != 0 { - cur += fmt.Sprintf("\trequires %s;\n", strings.Join(s.dependencies[g], ",")) - } - cur += "}\n" - res = append(res, cur) - } - - return strings.Join(res, "\n") -} - -func (s *Scoring) buildScoring() string { - ans := []string{ - "\\begin{center}", - "\\begin{tabular}{|c|c|c|c|}", - "\\hline", - "\\textbf{Подзадача} &", - "\\textbf{Баллы} &", - "\\textbf{Дополнительные ограничения} &", - "\\textbf{Необходимые подзадачи}", - "\\\\ \\hline", - } - for i, group := range s.groups { - var info string - if i == 0 { - info = "тесты из условия" - } else if i == len(s.groups)-1 { - info = "---" - } - ans = append(ans, fmt.Sprintf("%s & %d & %s & %s \\\\ \\hline", - group, s.score[group], info, strings.Join(s.dependencies[group], ", "))) - } - ans = append(ans, "\\end{tabular}", "\\end{center}") - - return strings.Join(ans, "\n") -} - -func (p *Polygon) InformaticsValuer(pID int, verbose bool) error { - groups, err := p.GetGroups(pID) - if err != nil { - return err - } - tests, err := p.GetTests(pID) - if err != nil { - return err - } - - scorer, err := NewScoring(tests, groups) - if err != nil { - return err - } - valuer := scorer.buildValuer() - if verbose { - logrus.Info("valuer.cfg\n" + valuer) - } - fr := NewFileRequest(pID, TypeResource, "valuer.cfg", valuer) - if err := p.SaveFile(fr); err != nil { - return err - } - - scoring := scorer.buildScoring() - fmt.Println(scoring) //nolint:forbidigo // Basic functionality. - - return nil -} - func (p *Polygon) IncrementalScoring(pID int, samples bool) error { if err := p.EnablePoints(pID); err != nil { return err diff --git a/polygon/valeria/universal_table.go b/polygon/valeria/universal_table.go new file mode 100644 index 0000000..99f503f --- /dev/null +++ b/polygon/valeria/universal_table.go @@ -0,0 +1,52 @@ +package valeria + +import ( + "fmt" + "strings" +) + +// Works both in HTML and PDF render. +type UniversalTable struct { + groups []string +} + +var _ TexTable = &UniversalTable{} + +func (t *UniversalTable) addGroupRow(info groupInfo, comment string) { + row := fmt.Sprintf("%s & %d & %s & %s \\\\ \\hline", + info.group, + info.score, + comment, + strings.Join(info.dependencies, ", "), + ) + t.groups = append(t.groups, row) +} + +func (t *UniversalTable) addGroup0(info groupInfo) { + t.addGroupRow(info, "тесты из условия") +} + +func (t *UniversalTable) addGroup(info groupInfo) { + t.addGroupRow(info, "") +} + +func (t *UniversalTable) addLastGroup(info groupInfo) { + t.addGroupRow(info, "---") +} + +func (t *UniversalTable) String() string { + table := []string{ + "\\begin{center}", + "\\begin{tabular}{|c|c|c|c|}", + "\\hline", + "\\textbf{Подзадача} &", + "\\textbf{Баллы} &", + "\\textbf{Дополнительные ограничения} &", + "\\textbf{Необходимые подзадачи}", + "\\\\ \\hline", + } + table = append(table, t.groups...) + table = append(table, "\\end{tabular}", "\\end{center}") + + return strings.Join(table, "\n") +} diff --git a/polygon/valeria/valeria.go b/polygon/valeria/valeria.go new file mode 100644 index 0000000..79e21ae --- /dev/null +++ b/polygon/valeria/valeria.go @@ -0,0 +1,140 @@ +package valeria + +import ( + "errors" + "fmt" + "strings" + + "github.com/Gornak40/algolymp/polygon" + "github.com/sirupsen/logrus" +) + +var ( + ErrNoTestScore = errors.New("test_score is not supported yet") + ErrBadTestsOrder = errors.New("bad tests order, fix in polygon required") +) + +type Valeria struct { + client *polygon.Polygon +} + +func NewValeria(client *polygon.Polygon) *Valeria { + return &Valeria{ + client: client, + } +} + +type groupInfo struct { + group string + score int + dependencies []string +} + +type TexTable interface { + addGroup0(info groupInfo) + addGroup(info groupInfo) + addLastGroup(info groupInfo) + + String() string +} + +type scoring struct { + score map[string]int + count map[string]int + first map[string]int + last map[string]int + dependencies map[string][]string + groups []string +} + +func newScoring(tests []polygon.TestAnswer, groups []polygon.GroupAnswer) (*scoring, error) { + scorer := scoring{ + score: map[string]int{}, + count: map[string]int{}, + first: map[string]int{}, + last: map[string]int{}, + dependencies: map[string][]string{}, + } + for _, test := range tests { + scorer.score[test.Group] += int(test.Points) // TODO: ensure ejudge doesn't support float points + scorer.count[test.Group]++ + if val, ok := scorer.first[test.Group]; !ok || val > test.Index { + scorer.first[test.Group] = test.Index + } + if val, ok := scorer.last[test.Group]; !ok || val < test.Index { + scorer.last[test.Group] = test.Index + } + } + for _, group := range groups { + if group.PointsPolicy != "COMPLETE_GROUP" { + return nil, ErrNoTestScore + } + if scorer.last[group.Name]-scorer.first[group.Name]+1 != scorer.count[group.Name] { + return nil, ErrBadTestsOrder + } + scorer.dependencies[group.Name] = group.Dependencies + scorer.groups = append(scorer.groups, group.Name) + } + + return &scorer, nil +} + +func (s *scoring) buildValuer() string { + res := []string{} + for _, g := range s.groups { + cur := fmt.Sprintf("group %s {\n\ttests %d-%d;\n\tscore %d;\n", + g, s.first[g], s.last[g], s.score[g]) + if len(s.dependencies[g]) != 0 { + cur += fmt.Sprintf("\trequires %s;\n", strings.Join(s.dependencies[g], ",")) + } + cur += "}\n" + res = append(res, cur) + } + + return strings.Join(res, "\n") +} + +func (s *scoring) buildScoring(table TexTable) { + for index, group := range s.groups { + info := groupInfo{ + group: group, + score: s.score[group], + dependencies: s.dependencies[group], + } + switch index { + case 0: + table.addGroup0(info) + case len(s.groups) - 1: + table.addLastGroup(info) + default: + table.addGroup(info) + } + } +} + +func (v *Valeria) InformaticsValuer(pID int, table TexTable, verbose bool) error { + groups, err := v.client.GetGroups(pID) + if err != nil { + return err + } + tests, err := v.client.GetTests(pID) + if err != nil { + return err + } + + scorer, err := newScoring(tests, groups) + if err != nil { + return err + } + valuer := scorer.buildValuer() + if verbose { + logrus.Info("valuer.cfg\n" + valuer) + } + fr := polygon.NewFileRequest(pID, polygon.TypeResource, "valuer.cfg", valuer) + if err := v.client.SaveFile(fr); err != nil { + return err + } + scorer.buildScoring(table) + + return nil +}