package main
// TopCoder MM DataFeeds
// community.topcoder.com/longcontest/?module=Static&d1=support&d2=dataFeed
// TopCoder API
// tcapi.docs.apiary.io/
import (
"bufio"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"log"
"math"
"net/http"
"os"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
const (
Protocol = "http"
Server = "www.topcoder.com"
RoundListPath = "/tc?module=BasicData&c=dd_marathon_round_list"
RoundResultsPath = "/tc?module=BasicData&c=dd_marathon_round_results&rd=%d"
IndividualResultsPath = "/longcontest/stats/?module=IndividualResultsFeed&rd=%d&cr=%d"
ApiServer = "api.topcoder.com"
PublicProfileApi = "/v2/users/%s"
)
func MakeRoundListUrl() string {
return Protocol + "://" + Server + RoundListPath
}
func MakeRoundResultsUrl(rd int64) string {
return Protocol + "://" + Server + fmt.Sprintf(RoundResultsPath, rd)
}
func MakeIndividualResultsUrl(rd, cr int64) string {
return Protocol + "://" + Server + fmt.Sprintf(IndividualResultsPath, rd, cr)
}
func MakePublicProfileApiUrl(handle string) string {
return Protocol + "://" + ApiServer + fmt.Sprintf(PublicProfileApi, handle)
}
type Round struct {
RoundId int64 `xml:"round_id"`
FullName string `xml:"full_name"`
ShortName string `xml:"short_name"`
Date string `xml:"date"`
RoundType string `xml:"round_type"`
}
type RoundList struct {
XMLName xml.Name `xml:"dd_marathon_round_list"`
Rounds []*Round `xml:"row"`
}
type Result struct {
RoundId int64 `xml:"round_id"`
CoderId int64 `xml:"coder_id"`
Handle string `xml:"handle"`
OldRating int `xml:"old_rating"`
NewRating int `xml:"new_rating"`
OldVolatility int `xml:"old_volatility"`
NewVolatility int `xml:"new_volatility"`
NumRatings int `xml:"num_ratings"`
Placed int `xml:"placed"`
Advanced string `xml:"advanced"`
ProvisionalScore float64 `xml:"provisional_score"`
FinalScore float64 `xml:"final_score"`
NumSubmissions int `xml:"num_submissions"`
RatedFlag int `xml:"rated_flag"`
}
type RoundResults struct {
XMLName xml.Name `xml:"dd_marathon_round_results"`
Results []*Result `xml:"row"`
}
type Submission struct {
Number int `xml:"number"`
Score float64 `xml:"score"`
Language string `xml:"language"`
Time string `xml:"time"`
}
type TestCase struct {
TestCaseId int64 `xml:"test_case_id"`
Score float64 `xml:"score"`
ProcessingTime int `xml:"processing_time"`
FatalErrorInd int `xml:"fatal_error_ind"`
}
type IndividualResults struct {
XMLName xml.Name `xml:"marathon_individual_results"`
RoundId int64 `xml:"round_id"`
CoderId int64 `xml:"coder_id"`
Handle string `xml:"handle"`
Submissions []*Submission `xml:"submissions>submission"`
TestCases []*TestCase `xml:"testcases>testcase"`
}
type PublicProfile map[string]interface{}
type Detail struct {
PublicProfile
*IndividualResults
ProvisionalPlaced, AverageTime, Bests, Uniques, Zeros int
}
func (rr RoundResults) ProvisionalStandings() []int {
standings := make([]int, len(rr.Results))
indexes := make([]int, len(rr.Results))
for i := range indexes {
indexes[i] = i
}
sort.Slice(indexes, func(i, j int) bool {
return rr.Results[indexes[i]].ProvisionalScore > rr.Results[indexes[j]].ProvisionalScore
})
place := 1
for p, i := range indexes {
if p > 0 && rr.Results[indexes[p-1]].ProvisionalScore > rr.Results[i].ProvisionalScore {
place = p + 1
}
standings[i] = place
}
return standings
}
func (rr RoundResults) Details() ([]*Detail, error) {
details := make([]*Detail, len(rr.Results))
pstandings := rr.ProvisionalStandings()
for i, r := range rr.Results {
log.
Println(i
+1, "/", len
(details
)) pf, err := GetPublicProfile(r.Handle)
if err != nil {
return nil, err
}
ir, err := GetIndividualResults(r.RoundId, r.CoderId)
if err != nil {
return nil, err
}
at := ir.AverageTime()
zr := ir.Zeros()
details[i] = &Detail{pf, ir, pstandings[i], at, 0, 0, zr}
}
var update func(a, b float64) bool
testCaseCount := len(details[0].TestCases)
e := len(details) / 2
less := 0
for i := 0; i < testCaseCount; i++ {
if details[0].TestCases[i].Score < details[e].TestCases[i].Score {
less++
}
}
bestScores := make([]float64, testCaseCount)
bestCounts := make([]int, testCaseCount)
if less > testCaseCount-less {
update = func(a, b float64) bool { return a > 0.0 && a < b }
for i := range bestScores {
bestScores[i] = math.MaxFloat64
}
} else {
update = func(a, b float64) bool { return a > b }
}
for _, dt := range details {
for i, tc := range dt.TestCases {
if update(tc.Score, bestScores[i]) {
bestScores[i] = tc.Score
bestCounts[i] = 1
} else if tc.Score == bestScores[i] {
bestCounts[i]++
}
}
}
for _, dt := range details {
for i, tc := range dt.TestCases {
if tc.Score == bestScores[i] {
dt.Bests++
if bestCounts[i] == 1 {
dt.Uniques++
}
}
}
}
return details, nil
}
func (ir *IndividualResults) AverageTime() int {
if ir == nil {
return 0
}
sum := 0
count := 0
for _, tc := range ir.TestCases {
if tc.ProcessingTime > 0 {
sum += tc.ProcessingTime
count++
}
}
if count > 0 {
return (sum + count - 1) / count
} else {
return 0
}
}
func (ir *IndividualResults) Zeros() int {
if ir == nil {
return 0
}
count := 0
for _, tc := range ir.TestCases {
if tc.Score == 0.0 || tc.Score < 0.0 {
count++
}
}
return count
}
func (p PublicProfile) Country() string {
if p == nil {
return ""
}
if value, ok := p["country"]; ok {
switch country := value.(type) {
case string:
return country
default:
return ""
}
} else {
return ""
}
}
func CharsetReader(charset string, input io.Reader) (io.Reader, error) {
// change charset to UTF-8
// no implements (almost, datafeeds have only ascii chars)
return input, nil
}
func GetDataFeed(url string, v interface{}) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
decoder := xml.NewDecoder(resp.Body)
decoder.CharsetReader = CharsetReader
err = decoder.Decode(v)
if err != nil {
return err
}
return nil
}
func GetRoundList() (*RoundList, error) {
log.
Println("download RoundList") url := MakeRoundListUrl()
v := &RoundList{}
if err := GetDataFeed(url, v); err != nil {
return nil, err
}
sort.Slice(v.Rounds, func(i, j int) bool {
return v.Rounds[i].Date > v.Rounds[j].Date
})
return v, nil
}
func GetRoundResults(rd int64) (*RoundResults, error) {
log.
Println("download RoundResults", rd
) url := MakeRoundResultsUrl(rd)
v := &RoundResults{}
if err := GetDataFeed(url, v); err != nil {
return nil, err
}
sort.Slice(v.Results, func(i, j int) bool {
return v.Results[i].Placed < v.Results[j].Placed
})
return v, nil
}
func GetIndividualResults(rd, cr int64) (*IndividualResults, error) {
log.
Println("download IndividualResults", rd
, cr
) url := MakeIndividualResultsUrl(rd, cr)
v := &IndividualResults{}
if err := GetDataFeed(url, v); err != nil {
return nil, err
}
sort.Slice(v.Submissions, func(i, j int) bool {
return v.Submissions[i].Number < v.Submissions[j].Number
})
sort.Slice(v.TestCases, func(i, j int) bool {
return v.TestCases[i].TestCaseId < v.TestCases[j].TestCaseId
})
return v, nil
}
func GetPublicProfile(handle string) (PublicProfile, error) {
log.
Println("download PubicProfile", handle
) url := MakePublicProfileApiUrl(handle)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var f interface{}
err = json.Unmarshal(data, &f)
if err != nil {
return nil, err
}
return PublicProfile(f.(map[string]interface{})), nil
}
func FilterByRegexp(dst *[]*Round, src []*Round, expr string) error {
list := (*dst)[:0]
if len(expr) == 0 {
return nil
}
re, err := regexp.Compile(expr)
if err != nil {
return err
}
for _, r := range src {
if re.MatchString(r.ShortName) || re.MatchString(r.FullName) {
list = append(list, r)
}
}
(*dst) = list
return nil
}
func FilterBySubstr(dst *[]*Round, src []*Round, substr string) {
list := (*dst)[:0]
if len(substr) == 0 {
return
}
for _, r := range src {
if strings.Contains(r.ShortName, substr) || strings.Contains(r.FullName, substr) {
list = append(list, r)
}
}
(*dst) = list
}
func SelectRound() (*Round, error) {
roundList, err := GetRoundList()
if err != nil {
return nil, err
}
list := []*Round{}
list = append(list, roundList.Rounds[:15]...)
index := -1
scanner := bufio.NewScanner(os.Stdin)
showFullName := false
for index < 0 {
for i, r := range list {
if showFullName {
fmt.Println(fmt.Sprintf("%6d: %s", i+1, r.FullName))
} else {
fmt.Println(fmt.Sprintf("%6d: %s", i+1, r.ShortName))
}
}
fmt.Print("index or command: ")
scanner.Scan()
input := scanner.Text()
if strings.HasPrefix(input, "r:") {
expr := strings.TrimPrefix(input, "r:")
err = FilterByRegexp(&list, roundList.Rounds, expr)
if err != nil {
}
} else if strings.HasPrefix(input, "s:") {
substr := strings.TrimPrefix(input, "s:")
FilterBySubstr(&list, roundList.Rounds, substr)
} else if input == "exit" {
return nil, nil
} else if input == "full" {
showFullName = true
} else if input == "short" {
showFullName = false
} else {
temp, err := strconv.Atoi(input)
if err != nil {
} else if temp < 1 || len(list) < temp {
log.
Println("invalid index: ", temp
) } else {
index = temp
}
}
}
return roundList.Rounds[index-1], nil
}
func main() {
round, err := SelectRound()
if err != nil {
}
if round == nil {
return
}
filename := "statistics.txt"
file, err := os.Create(filename)
if err != nil {
}
defer file.Close()
fmt.Println("Contest: ", round.FullName)
fmt.Println("RoundId: ", round.RoundId)
fmt.Println("Date: ", round.Date)
fmt.Fprintln(file, "Contest: ", round.FullName)
fmt.Fprintln(file, "RoundId: ", round.RoundId)
fmt.Fprintln(file, "Date: ", round.Date)
fmt.Fprintln(file, "Statistics")
fmt.Fprintln(file, `<pre><font size="3">`)
results, err := GetRoundResults(round.RoundId)
if err != nil {
}
details, err := results.Details()
if err != nil {
}
fmt.Fprintln(file, fmt.Sprintf(
"<b>%-3s %-24s %-15s %-9s %-3s %-9s %-4s %-4s %-4s %-4s ▲▼</b>",
"POS", "HANDLE", "COUNTRY", "SCORE", "PRP", "PRSCORE",
"BEST", "UNIQ", "ZERO", "TIME",
))
changes := func(a, b int) string {
if a < b {
return fmt.Sprintf(`<font color="green">▲%d</font>`, b-a)
} else if a > b {
return fmt.Sprintf(`<font color="red">▼%d</font>`, a-b)
} else {
return "▪0"
}
}
for i, r := range results.Results {
dt := details[i]
pf := dt.PublicProfile
fmt.Fprintln(file, fmt.Sprintf(
"%3d %-27s %-15s %9.2f %3d %9.2f %4d %4d %4d %4d %s",
r.Placed, "[h]"+r.Handle+"[/h]", pf.Country(), r.FinalScore,
dt.ProvisionalPlaced, r.ProvisionalScore,
dt.Bests, dt.Uniques, dt.Zeros, dt.AverageTime,
changes(r.Placed, dt.ProvisionalPlaced),
))
}
fmt.Fprintln(file, "</font></pre>")
log.
Println("saved", filename
) }