AtCoder_Handle == Codeforces_Handle && least 10 times↵
↵
↵
current rating↵
![ ](/predownloaded/0c/ab/0cab06170ce8a7dda3a05df69bdeb44e88cad197d9/14/d914740e32b251f2e7ca04fee9f621bfab538d0b.png)↵
↵
↵
max rating↵
![ ](/predownloaded/46f/fc/4ffc66fbc6aef64667b825e331d68fb981b0f271.png)↵
c2/6fc2e62c6a654a1354af3c8a196cacef99adfc13.png)↵
↵
<spoiler summary="Spoiler">↵
AtCoder_Handle == Codeforces_Handle && least 1 times↵
↵
↵
current rating↵
![ ](/predownloaded/08/89/0889d69f953a6f48c0091253b13e26cba78826e1.png)↵
↵
↵
max rating↵
![ ](/predownloaded/a7/f5/a7f57db09915635374008975871478accbf20b61.png)↵
↵
</spoiler>↵
↵
<spoiler summary="Spoiler">↵
↵
<spoiler summary="./pick/codeforces/download.sh">↵
~~~~~↵
#!/bin/sh↵
curl -o users.json "https://mirror.codeforces.com/api/user.ratedList?activeOnly=false"↵
~~~~~↵
</spoiler>↵
↵
↵
<spoiler summary="./pick/atcoder/download.sh">↵
~~~~~↵
#!/bin/sh↵
↵
url='https://atcoder.jp/ranking/all?page='↵
filename='ranking'↵
fileext='.html'↵
downloader='curl -o'↵
↵
echo $downloader ${filename}1$fileext "${url}1"↵
$downloader ${filename}1$fileext "${url}1"↵
↵
num=`grep -P -o 'page=\d+' ranking1.html | grep -P -o '\d+' | sort -n | tail -n 1`↵
↵
for ((i=2;i<=$num;i++)); do↵
echo $downloader $filename$i$fileext "$url$i"↵
sleep 10s↵
$downloader $filename$i$fileext "$url$i"↵
done↵
~~~~~↵
</spoiler>↵
↵
↵
<spoiler summary="./pick/atcoder/main.go">↵
~~~~~↵
package main↵
↵
import (↵
"bufio"↵
"encoding/json"↵
"encoding/xml"↵
"fmt"↵
"io"↵
"os"↵
"path/filepath"↵
"strconv"↵
"strings"↵
)↵
↵
type User struct {↵
OverallRank int `json:"overallrank"`↵
Rank int `json:"Rank"`↵
Country string `json:"country"`↵
Handle string `json:"handle"`↵
Organization string `json:"organization"`↵
Birth int `json:"birth"`↵
Rating int `json:"rating"`↵
Highest int `json:"highest"`↵
Matches int `json:"matches"`↵
Wins int `json:"wins"`↵
}↵
↵
func (u *User) String() string {↵
return fmt.Sprintf("%#v", *u)↵
}↵
↵
func main() {↵
↵
users := []*User{}↵
↵
files, err := filepath.Glob("ranking*.html")↵
if err != nil {↵
panic(err)↵
}↵
↵
for num := range files {↵
↵
file, err := os.Open(files[num])↵
if err != nil {↵
panic(err)↵
}↵
defer file.Close()↵
↵
red, wri := io.Pipe()↵
↵
go func() {↵
defer wri.Close()↵
bufRed := bufio.NewReader(file)↵
bufWri := bufio.NewWriter(wri)↵
for {↵
ch, _, err := bufRed.ReadRune()↵
if err != nil {↵
bufWri.Flush()↵
wri.CloseWithError(err)↵
return↵
}↵
if ch == 0x10 {↵
ch = '+'↵
}↵
if _, err = bufWri.WriteRune(ch); err != nil {↵
wri.CloseWithError(err)↵
return↵
}↵
}↵
}()↵
↵
dec := xml.NewDecoder(bufio.NewReader(red))↵
dec.Strict = false↵
dec.AutoClose = xml.HTMLAutoClose↵
dec.Entity = xml.HTMLEntity↵
↵
for {↵
if se, err := skipToSE(dec, "table"); err != nil {↵
panic(err)↵
} else if len(se.(xml.StartElement).Attr) == 0 {↵
continue↵
}↵
if _, err := skipToSE(dec, "tbody"); err != nil {↵
panic(err)↵
}↵
break↵
}↵
↵
for {↵
if token, err := skipNoElm(dec); err != nil {↵
panic(err)↵
} else if ee, ok := token.(xml.EndElement); ok && ee.Name.Local == "tbody" {↵
break↵
} else if se, ok := token.(xml.StartElement); !ok || se.Name.Local != "tr" {↵
continue↵
}↵
user := &User{}↵
for i := 0; i < 7; i++ {↵
if _, err := skipToSE(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
}↵
switch i {↵
case 0:↵
if ovR, err := getCharData(dec, "span"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
ovR := strings.Trim(ovR, "()")↵
if overallRank, err := strconv.Atoi(ovR); err == nil {↵
user.OverallRank = overallRank↵
}↵
}↵
if r, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if rank, err := strconv.Atoi(r); err == nil {↵
user.Rank = rank↵
}↵
}↵
case 1:↵
if se, err := skipToSE(dec, "a"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
se := se.(xml.StartElement)↵
for _, attr := range se.Attr {↵
if attr.Name.Local != "href" {↵
continue↵
}↵
if ss := strings.Split(attr.Value, "="); len(ss) == 2 {↵
user.Country = ss[1]↵
}↵
break↵
}↵
}↵
if handle, err := getCharData(dec, "span"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
user.Handle = handle↵
}↵
if org, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
user.Organization = org↵
}↵
case 2:↵
if year, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if year, err := strconv.Atoi(year); err == nil {↵
user.Birth = year↵
}↵
}↵
case 3:↵
if rate, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if rate, err := strconv.Atoi(rate); err == nil {↵
user.Rating = rate↵
}↵
}↵
case 4:↵
if maxrate, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if maxrate, err := strconv.Atoi(maxrate); err == nil {↵
user.Highest = maxrate↵
}↵
}↵
case 5:↵
if comp, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if comp, err := strconv.Atoi(comp); err == nil {↵
user.Matches = comp↵
}↵
}↵
case 6:↵
if wins, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if wins, err := strconv.Atoi(wins); err == nil {↵
user.Wins = wins↵
}↵
}↵
}↵
}↵
users = append(users, user)↵
}↵
}↵
↵
dst, err := os.Create("users.json")↵
if err != nil {↵
panic(err)↵
}↵
defer dst.Close()↵
out := bufio.NewWriter(dst)↵
defer out.Flush()↵
json.NewEncoder(out).Encode(users)↵
}↵
↵
func skipToSE(dec *xml.Decoder, tag string) (xml.Token, error) {↵
for {↵
token, err := dec.Token()↵
if err != nil {↵
return token, err↵
}↵
if se, ok := token.(xml.StartElement); ok && se.Name.Local == tag {↵
return token, nil↵
}↵
}↵
}↵
↵
func getCharData(dec *xml.Decoder, endTag string) (string, error) {↵
ret := ""↵
for {↵
token, err := dec.Token()↵
if err != nil {↵
println(fmt.Sprintf("%#v", token))↵
println(fmt.Sprintf("%#v", err))↵
tok2, err2 := dec.RawToken()↵
println(fmt.Sprintf("%#v", tok2))↵
println(fmt.Sprintf("%#v", err2))↵
return ret, err↵
}↵
switch token := token.(type) {↵
case xml.EndElement:↵
if token.Name.Local == endTag {↵
return strings.TrimSpace(ret), nil↵
}↵
case xml.CharData:↵
ret += string(token)↵
}↵
}↵
}↵
↵
func skipNoElm(dec *xml.Decoder) (xml.Token, error) {↵
for {↵
token, err := dec.Token()↵
if err != nil {↵
return nil, err↵
}↵
switch token.(type) {↵
case xml.StartElement, xml.EndElement:↵
return token, nil↵
}↵
}↵
}↵
~~~~~↵
</spoiler>↵
↵
↵
<spoiler summary="./pick/main.go">↵
~~~~~↵
package main↵
↵
import (↵
"bufio"↵
"encoding/csv"↵
"encoding/json"↵
_ "fmt"io/ioutil"↵
"net/http"↵
"net/url"↵
"os"↵
"path/filepath"↵
"sort"↵
"strconv"↵
"strings"↵
"time"↵
)↵
↵
type AtCoderUser struct {↵
OverallRank int `json:"overallrank"`↵
Rank int `json:"Rank"`↵
Country string `json:"country"`↵
Handle string `json:"handle"`↵
Organization string `json:"organization"`↵
Birth int `json:"birth"`↵
Rating int `json:"rating"`↵
Highest int `json:"highest"`↵
Matches int `json:"matches"`↵
Wins int `json:"wins"`↵
}↵
↵
type CodeforcesUser struct {↵
Handle string↵
Email string↵
VkId string↵
OpenId string↵
FirstName string↵
LastName string↵
Country string↵
City string↵
Organization string↵
Contributionfloat64int↵
Rank string↵
Ratingfloat64int↵
MaxRank string↵
MaxRatingfloat64int↵
LastOnlineTimeSecondsfloat64int↵
RegistrationTimeSecondsfloat64int↵
FriendOfCountfloat64int↵
Avatar string↵
TitlePhoto string↵
}↵
↵
type CodeforcesUserRatedList struct {↵
Status string↵
Result []*CodeforcesUser↵
}↵
↵
func LoadJson(filepath string, data interface{}) error {↵
↵
file, err := os.Open(filepath)↵
if err != nil {↵
return err↵
}↵
defer file.Close()↵
↵
dec := json.NewDecoder(bufio.NewReader(file))↵
↵
if err := dec.Decode(data); err != nil {↵
return err↵
}↵
↵
return niltype CodeforcesRatingChange struct {↵
ContestId int↵
ContestName string↵
Handle string↵
Rank int↵
RatingUpdateTimeSeconds int↵
OldRating int↵
NewRating int↵
}↵
↵
type CodeforcesUserRating struct {↵
Status string↵
Result []*CodeforcesRatingChange↵
}↵
↵
func LoadJson(filepath string, data interface{}) error {↵
↵
file, err := os.Open(filepath)↵
if err != nil {↵
return err↵
}↵
defer file.Close()↵
↵
dec := json.NewDecoder(bufio.NewReader(file))↵
↵
if err := dec.Decode(data); err != nil {↵
return err↵
}↵
↵
return nil↵
}↵
↵
func GetCfRating(handle string) (*CodeforcesUserRating, error) {↵
url := "https://mirror.codeforces.com/api/user.rating?handle=" + url.QueryEscape(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↵
}↵
rating := &CodeforcesUserRating{}↵
err = json.Unmarshal(data, rating)↵
return rating, err↵
}↵
↵
func main() {↵
↵
var atcoderUsers []*AtCoderUser↵
↵
if err := LoadJson(filepath.Join("atcoder", "users.json"), &atcoderUsers); err != nil {↵
panic(err)↵
}↵
↵
sort.Slice(atcoderUsers, func(i, j int) bool {↵
return strings.Compare(atcoderUsers[i].Handle, atcoderUsers[j].Handle) < 0↵
})↵
↵
var ratedList CodeforcesUserRatedList↵
↵
if err := LoadJson(filepath.Join("codeforces", "users.json"), &ratedList); err != nil {↵
panic(err)↵
}↵
↵
if ratedList.Status != "OK" {↵
println("failed downloading ratedList")↵
return↵
}↵
↵
dst, err := os.Create("coders.csv")↵
if err != nil {↵
panic(err)↵
}↵
defer dst.Close()↵
↵
wri := csv.NewWriter(dst)↵
↵
if err := wri.Write([]string{↵
"handle",↵
"atcoder_rating",↵
"atcoder_maxrating",↵
"atcoder_country",↵
"codeforces_rating",↵
"codeforces_maxrating",↵
"codeforces_country",↵
"codeforces_lastonline",↵
}); err != nil {↵
panic(err)↵
} "atcoder_matches",↵
"codeforces_matches",↵
}); err != nil {↵
panic(err)↵
}↵
↵
waiting := time.After(time.Second)↵
count := 0↵
↵
for _, cfUser := range ratedList.Result {↵
i := sort.Search(len(atcoderUsers), func(i int) bool {↵
return strings.Compare(atcoderUsers[i].Handle, cfUser.Handle) >= 0↵
})↵
if 0 <= i && i < len(atcoderUsers) && atcoderUsers[i].Handle == cfUser.Handle {↵
count++↵
if count%60 == 0 {↵
println("count " + strconv.Itoa(count))↵
}↵
<-waiting↵
rating, err := GetCfRating(cfUser.Handle)↵
if err != nil {↵
panic(err)↵
}↵
if rating.Status != "OK" {↵
println("failed downloading rating")↵
return↵
}↵
waiting = time.After(time.Second)↵
acUser := atcoderUsers[i]↵
wri.Write([]string{↵
cfUser.Handle,↵
strconv.Itoa(acUser.Rating),↵
strconv.Itoa(acUser.Highest),↵
acUser.Country,↵
strconv.Itoa(int(cfUser.Rating)),↵
strconv.Itoa(int(cfUser.MaxRating)),↵
cfUser.Country,↵
time.Unix(int64(cfUser.LastOnlineTimeSeconds), 0).UTC().String(),↵
strconv.Itoa(acUser.Matches),↵
strconv.Itoa(len(rating.Result)),↵
})↵
}↵
}↵
↵
wri.Flush()↵
if err := wri.Error(); err != nil {↵
panic(err)↵
}↵
}↵
~~~~~↵
</spoiler>↵
↵
↵
<spoiler summary="./graph/main.go">↵
~~~~~↵
package main↵
↵
import (↵
"bufio"↵
"encoding/csv"↵
"flag"↵
"fmt"↵
"image"↵
"image/color"↵
"image/png"↵
"os"↵
"strconv"↵
)↵
↵
var acColors = []color.RGBA{↵
color.RGBA{0x80, 0x80, 0x80, 0xff},↵
color.RGBA{0x80, 0x40, 0x00, 0xff},↵
color.RGBA{0x00, 0x80, 0x00, 0xff},↵
color.RGBA{0x00, 0xC0, 0xC0, 0xff},↵
color.RGBA{0x00, 0x00, 0xFF, 0xff},↵
color.RGBA{0xC0, 0xC0, 0x00, 0xff},↵
color.RGBA{0xFF, 0x80, 0x00, 0xff},↵
color.RGBA{0xFF, 0x00, 0x00, 0xff},↵
color.RGBA{0xC0, 0xC0, 0xC0, 0xff},↵
color.RGBA{0xFF, 0xD7, 0x00, 0xff},↵
}↵
↵
var cfColors = []color.RGBA{↵
color.RGBA{0x80, 0x80, 0x80, 0xff},↵
color.RGBA{0x00, 0x80, 0x00, 0xff},↵
color.RGBA{0x03, 0xA8, 0x9E, 0xff},↵
color.RGBA{0x00, 0x00, 0xFF, 0xff},↵
color.RGBA{0xAA, 0x00, 0xAA, 0xff},↵
color.RGBA{0xFF, 0x8C, 0x00, 0xff},↵
color.RGBA{0xFF, 0x00, 0x00, 0xff},↵
}↵
↵
var cfRatingBorders = []int{↵
1200,↵
1400,↵
1600,↵
1900,↵
2100,↵
2400,↵
3000,↵
}↵
↵
func Max(a, b int) int {↵
if a > b {↵
return a↵
}↵
return b↵
}↵
↵
func Min(a, b int) int {↵
if a < b {↵
return a↵
}↵
return b↵
}↵
↵
func main() {↵
↵
var minMatches int↵
flag.IntVar(&minMatches, "matches", 10, "min matches")↵
flag.Parse()↵
↵
file, err := os.Open("coders.csv")↵
if err != nil {↵
panic(err)↵
}↵
defer file.Close()↵
↵
rows, err := csv.NewReader(bufio.NewReader(file)).ReadAll()↵
if err != nil {↵
panic(err)↵
}↵
↵
dt := []int{-1, 0, 1, 0, -1}↵
↵
for p := 0; p < 2; p++ {↵
cnt := make(map[int]int)↵
↵
minAcRating := 9999↵
maxAcRating := -9999↵
minCfRating := 9999↵
maxCfRating := -9999↵
↵
for _, row := range rows[1:] {↵
acRating, err := strconv.Atoi(row[1+p])↵
if err != nil {↵
panic(err)↵
}↵
cfRating, err := strconv.Atoi(row[4+p])↵
if err != nil {↵
panic(err)↵
}↵
↵
acMatches, err := strconv.Atoi(row[8])↵
if err != nil {↵
panic(err)↵
}↵
cfMatches, err := strconv.Atoi(row[9])↵
if err != nil {↵
panic(err)↵
}↵
if acMatches < minMatches || cfMatches < minMatches {↵
continue↵
}↵
↵
minAcRating = Min(minAcRating, acRating)↵
maxAcRating = Max(maxAcRating, acRating)↵
minCfRating = Min(minCfRating, cfRating)↵
maxCfRating = Max(maxCfRating, cfRating)↵
↵
acRating += 1000↵
acRating /= 5↵
cfRating += 1000↵
cfRating /= 5↵
key := acRating*10000 + cfRating↵
if c, ok := cnt[key]; ok {↵
cnt[key] = c + 1↵
} else {↵
cnt[key] = 1↵
}↵
}↵
fmt.Println("minAcRating", minAcRating)↵
fmt.Println("maxAcRating", maxAcRating)↵
fmt.Println("minCfRating", minCfRating)↵
fmt.Println("maxCfRating", maxCfRating)↵
fmt.Println("uniq", len(cnt))↵
↵
max := 0↵
for _, c := range cnt {↵
if c > max {↵
max = c↵
}↵
}↵
fmt.Println("maxCount", max)↵
↵
img := image.NewRGBA(image.Rect(0, 0, 1000, 1000))↵
for k, c := range cnt {↵
acRating := k / 10000↵
cfRating := k % 10000↵
r := uint8(255 * c / max)↵
img.Set(cfRating, 999-acRating, color.RGBA{r, 0, 0, 0xff})↵
r = r * 95 / 100↵
for i := 0; i < 4; i++ {↵
ac := acRating + dt[i]↵
cf := cfRating + dt[i+1]↵
if ac < 0 || ac >= 1000 || cf < 0 || cf >= 1000 {↵
continue↵
}↵
if _, _, _, a := img.At(cf, 999-ac).RGBA(); a == 0 {↵
img.Set(cf, 999-ac, color.RGBA{r, 0, 0, 0xff})↵
}↵
}↵
}↵
↵
gray := color.RGBA{60, 60, 60, 0xff}↵
for i := 0; i < 1000; i++ {↵
if _, _, _, a := img.At(200, i).RGBA(); a == 0 {↵
img.Set(200, i, gray)↵
}↵
if _, _, _, a := img.At(i, 999-200).RGBA(); a == 0 {↵
img.Set(i, 999-200, gray)↵
}↵
}↵
↵
lightgray1 := color.RGBA{190, 190, 190, 0xff}↵
lightgray2 := color.RGBA{235, 235, 235, 0xff}↵
for i := 0; i < 800; i++ {↵
for j := 0; j < 800; j += 20 {↵
if _, _, _, a := img.At(200+i, 999-200-j).RGBA(); a == 0 {↵
if j%80 == 0 {↵
img.Set(200+i, 999-200-j, lightgray1)↵
} else {↵
img.Set(200+i, 999-200-j, lightgray2)↵
}↵
}↵
if _, _, _, a := img.At(200+j, 999-200-i).RGBA(); a == 0 {↵
col := lightgray2↵
for _, r := range cfRatingBorders {↵
if r/5 == j {↵
col = lightgray1↵
break↵
}↵
}↵
img.Set(200+j, 999-200-i, col)↵
}↵
}↵
}↵
↵
for i, col := range acColors {↵
for x := 190; x < 200; x++ {↵
for dy := 0; dy < 80; dy++ {↵
y := i*80 + dy↵
if _, _, _, a := img.At(x, 999-200-y).RGBA(); a == 0 {↵
img.Set(x, 999-200-y, col)↵
}↵
}↵
}↵
}↵
↵
for y := 190; y < 200; y++ {↵
for x := 0; x < 800; x++ {↵
if _, _, _, a := img.At(200+x, 999-y).RGBA(); a != 0 {↵
continue↵
}↵
col := color.RGBA{0, 0, 0, 0xff}↵
for i, r := range cfRatingBorders {↵
if x < r/5 {↵
col = cfColors[i]↵
break↵
}↵
}↵
img.Set(200+x, 999-y, col)↵
}↵
}↵
↵
graph, err := os.Create(fmt.Sprintf("graph%d_%d.png", minMatches, p))↵
if err != nil {↵
panic(err)↵
}↵
defer graph.Close()↵
err = png.Encode(graph, img)↵
if err != nil {↵
panic(err)↵
}↵
}↵
}↵
~~~~~↵
</spoiler>↵
↵
</spoiler>↵
↵
↵
↵
current rating↵
![ ](/predownloaded/
↵
↵
max rating↵
![ ](/predownloaded/
↵
<spoiler summary="Spoiler">↵
AtCoder_Handle == Codeforces_Handle && least 1 times↵
↵
↵
current rating↵
![ ](/predownloaded/08/89/0889d69f953a6f48c0091253b13e26cba78826e1.png)↵
↵
↵
max rating↵
![ ](/predownloaded/a7/f5/a7f57db09915635374008975871478accbf20b61.png)↵
↵
</spoiler>↵
↵
<spoiler summary="Spoiler">↵
↵
<spoiler summary="./pick/codeforces/download.sh">↵
~~~~~↵
#!/bin/sh↵
curl -o users.json "https://mirror.codeforces.com/api/user.ratedList?activeOnly=false"↵
~~~~~↵
</spoiler>↵
↵
↵
<spoiler summary="./pick/atcoder/download.sh">↵
~~~~~↵
#!/bin/sh↵
↵
url='https://atcoder.jp/ranking/all?page='↵
filename='ranking'↵
fileext='.html'↵
downloader='curl -o'↵
↵
echo $downloader ${filename}1$fileext "${url}1"↵
$downloader ${filename}1$fileext "${url}1"↵
↵
num=`grep -P -o 'page=\d+' ranking1.html | grep -P -o '\d+' | sort -n | tail -n 1`↵
↵
for ((i=2;i<=$num;i++)); do↵
echo $downloader $filename$i$fileext "$url$i"↵
sleep 10s↵
$downloader $filename$i$fileext "$url$i"↵
done↵
~~~~~↵
</spoiler>↵
↵
↵
<spoiler summary="./pick/atcoder/main.go">↵
~~~~~↵
package main↵
↵
import (↵
"bufio"↵
"encoding/json"↵
"encoding/xml"↵
"fmt"↵
"io"↵
"os"↵
"path/filepath"↵
"strconv"↵
"strings"↵
)↵
↵
type User struct {↵
OverallRank int `json:"overallrank"`↵
Rank int `json:"Rank"`↵
Country string `json:"country"`↵
Handle string `json:"handle"`↵
Organization string `json:"organization"`↵
Birth int `json:"birth"`↵
Rating int `json:"rating"`↵
Highest int `json:"highest"`↵
Matches int `json:"matches"`↵
Wins int `json:"wins"`↵
}↵
↵
func (u *User) String() string {↵
return fmt.Sprintf("%#v", *u)↵
}↵
↵
func main() {↵
↵
users := []*User{}↵
↵
files, err := filepath.Glob("ranking*.html")↵
if err != nil {↵
panic(err)↵
}↵
↵
for num := range files {↵
↵
file, err := os.Open(files[num])↵
if err != nil {↵
panic(err)↵
}↵
defer file.Close()↵
↵
red, wri := io.Pipe()↵
↵
go func() {↵
defer wri.Close()↵
bufRed := bufio.NewReader(file)↵
bufWri := bufio.NewWriter(wri)↵
for {↵
ch, _, err := bufRed.ReadRune()↵
if err != nil {↵
bufWri.Flush()↵
wri.CloseWithError(err)↵
return↵
}↵
if ch == 0x10 {↵
ch = '+'↵
}↵
if _, err = bufWri.WriteRune(ch); err != nil {↵
wri.CloseWithError(err)↵
return↵
}↵
}↵
}()↵
↵
dec := xml.NewDecoder(bufio.NewReader(red))↵
dec.Strict = false↵
dec.AutoClose = xml.HTMLAutoClose↵
dec.Entity = xml.HTMLEntity↵
↵
for {↵
if se, err := skipToSE(dec, "table"); err != nil {↵
panic(err)↵
} else if len(se.(xml.StartElement).Attr) == 0 {↵
continue↵
}↵
if _, err := skipToSE(dec, "tbody"); err != nil {↵
panic(err)↵
}↵
break↵
}↵
↵
for {↵
if token, err := skipNoElm(dec); err != nil {↵
panic(err)↵
} else if ee, ok := token.(xml.EndElement); ok && ee.Name.Local == "tbody" {↵
break↵
} else if se, ok := token.(xml.StartElement); !ok || se.Name.Local != "tr" {↵
continue↵
}↵
user := &User{}↵
for i := 0; i < 7; i++ {↵
if _, err := skipToSE(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
}↵
switch i {↵
case 0:↵
if ovR, err := getCharData(dec, "span"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
ovR := strings.Trim(ovR, "()")↵
if overallRank, err := strconv.Atoi(ovR); err == nil {↵
user.OverallRank = overallRank↵
}↵
}↵
if r, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if rank, err := strconv.Atoi(r); err == nil {↵
user.Rank = rank↵
}↵
}↵
case 1:↵
if se, err := skipToSE(dec, "a"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
se := se.(xml.StartElement)↵
for _, attr := range se.Attr {↵
if attr.Name.Local != "href" {↵
continue↵
}↵
if ss := strings.Split(attr.Value, "="); len(ss) == 2 {↵
user.Country = ss[1]↵
}↵
break↵
}↵
}↵
if handle, err := getCharData(dec, "span"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
user.Handle = handle↵
}↵
if org, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
user.Organization = org↵
}↵
case 2:↵
if year, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if year, err := strconv.Atoi(year); err == nil {↵
user.Birth = year↵
}↵
}↵
case 3:↵
if rate, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if rate, err := strconv.Atoi(rate); err == nil {↵
user.Rating = rate↵
}↵
}↵
case 4:↵
if maxrate, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if maxrate, err := strconv.Atoi(maxrate); err == nil {↵
user.Highest = maxrate↵
}↵
}↵
case 5:↵
if comp, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if comp, err := strconv.Atoi(comp); err == nil {↵
user.Matches = comp↵
}↵
}↵
case 6:↵
if wins, err := getCharData(dec, "td"); err != nil {↵
println(fmt.Sprintf("num: %d", num))↵
panic(err)↵
} else {↵
if wins, err := strconv.Atoi(wins); err == nil {↵
user.Wins = wins↵
}↵
}↵
}↵
}↵
users = append(users, user)↵
}↵
}↵
↵
dst, err := os.Create("users.json")↵
if err != nil {↵
panic(err)↵
}↵
defer dst.Close()↵
out := bufio.NewWriter(dst)↵
defer out.Flush()↵
json.NewEncoder(out).Encode(users)↵
}↵
↵
func skipToSE(dec *xml.Decoder, tag string) (xml.Token, error) {↵
for {↵
token, err := dec.Token()↵
if err != nil {↵
return token, err↵
}↵
if se, ok := token.(xml.StartElement); ok && se.Name.Local == tag {↵
return token, nil↵
}↵
}↵
}↵
↵
func getCharData(dec *xml.Decoder, endTag string) (string, error) {↵
ret := ""↵
for {↵
token, err := dec.Token()↵
if err != nil {↵
println(fmt.Sprintf("%#v", token))↵
println(fmt.Sprintf("%#v", err))↵
tok2, err2 := dec.RawToken()↵
println(fmt.Sprintf("%#v", tok2))↵
println(fmt.Sprintf("%#v", err2))↵
return ret, err↵
}↵
switch token := token.(type) {↵
case xml.EndElement:↵
if token.Name.Local == endTag {↵
return strings.TrimSpace(ret), nil↵
}↵
case xml.CharData:↵
ret += string(token)↵
}↵
}↵
}↵
↵
func skipNoElm(dec *xml.Decoder) (xml.Token, error) {↵
for {↵
token, err := dec.Token()↵
if err != nil {↵
return nil, err↵
}↵
switch token.(type) {↵
case xml.StartElement, xml.EndElement:↵
return token, nil↵
}↵
}↵
}↵
~~~~~↵
</spoiler>↵
↵
↵
<spoiler summary="./pick/main.go">↵
~~~~~↵
package main↵
↵
import (↵
"bufio"↵
"encoding/csv"↵
"encoding/json"↵
"net/http"↵
"net/url"↵
"os"↵
"path/filepath"↵
"sort"↵
"strconv"↵
"strings"↵
"time"↵
)↵
↵
type AtCoderUser struct {↵
OverallRank int `json:"overallrank"`↵
Rank int `json:"Rank"`↵
Country string `json:"country"`↵
Handle string `json:"handle"`↵
Organization string `json:"organization"`↵
Birth int `json:"birth"`↵
Rating int `json:"rating"`↵
Highest int `json:"highest"`↵
Matches int `json:"matches"`↵
Wins int `json:"wins"`↵
}↵
↵
type CodeforcesUser struct {↵
Handle string↵
Email string↵
VkId string↵
OpenId string↵
FirstName string↵
LastName string↵
Country string↵
City string↵
Organization string↵
Contribution
Rank string↵
Rating
MaxRank string↵
MaxRating
LastOnlineTimeSeconds
RegistrationTimeSeconds
FriendOfCount
Avatar string↵
TitlePhoto string↵
}↵
↵
type CodeforcesUserRatedList struct {↵
Status string↵
Result []*CodeforcesUser↵
}↵
↵
↵
file, err := os.Open(filepath)↵
if err != nil {↵
return err↵
}↵
defer file.Close()↵
↵
dec := json.NewDecoder(bufio.NewReader(file))↵
↵
if err := dec.Decode(data); err != nil {↵
return err↵
}↵
↵
return nil
ContestId int↵
ContestName string↵
Handle string↵
Rank int↵
RatingUpdateTimeSeconds int↵
OldRating int↵
NewRating int↵
}↵
↵
type CodeforcesUserRating struct {↵
Status string↵
Result []*CodeforcesRatingChange↵
}↵
↵
func LoadJson(filepath string, data interface{}) error {↵
↵
file, err := os.Open(filepath)↵
if err != nil {↵
return err↵
}↵
defer file.Close()↵
↵
dec := json.NewDecoder(bufio.NewReader(file))↵
↵
if err := dec.Decode(data); err != nil {↵
return err↵
}↵
↵
return nil↵
}↵
↵
func GetCfRating(handle string) (*CodeforcesUserRating, error) {↵
url := "https://mirror.codeforces.com/api/user.rating?handle=" + url.QueryEscape(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↵
}↵
rating := &CodeforcesUserRating{}↵
err = json.Unmarshal(data, rating)↵
return rating, err↵
}↵
↵
func main() {↵
↵
var atcoderUsers []*AtCoderUser↵
↵
if err := LoadJson(filepath.Join("atcoder", "users.json"), &atcoderUsers); err != nil {↵
panic(err)↵
}↵
↵
sort.Slice(atcoderUsers, func(i, j int) bool {↵
return strings.Compare(atcoderUsers[i].Handle, atcoderUsers[j].Handle) < 0↵
})↵
↵
var ratedList CodeforcesUserRatedList↵
↵
if err := LoadJson(filepath.Join("codeforces", "users.json"), &ratedList); err != nil {↵
panic(err)↵
}↵
↵
if ratedList.Status != "OK" {↵
println("failed downloading ratedList")↵
return↵
}↵
↵
dst, err := os.Create("coders.csv")↵
if err != nil {↵
panic(err)↵
}↵
defer dst.Close()↵
↵
wri := csv.NewWriter(dst)↵
↵
if err := wri.Write([]string{↵
"handle",↵
"atcoder_rating",↵
"atcoder_maxrating",↵
"atcoder_country",↵
"codeforces_rating",↵
"codeforces_maxrating",↵
"codeforces_country",↵
"codeforces_lastonline",↵
panic(err)↵
}
"codeforces_matches",↵
}); err != nil {↵
panic(err)↵
}↵
↵
waiting := time.After(time.Second)↵
count := 0↵
↵
for _, cfUser := range ratedList.Result {↵
i := sort.Search(len(atcoderUsers), func(i int) bool {↵
return strings.Compare(atcoderUsers[i].Handle, cfUser.Handle) >= 0↵
})↵
if 0 <= i && i < len(atcoderUsers) && atcoderUsers[i].Handle == cfUser.Handle {↵
count++↵
if count%60 == 0 {↵
println("count " + strconv.Itoa(count))↵
}↵
<-waiting↵
rating, err := GetCfRating(cfUser.Handle)↵
if err != nil {↵
panic(err)↵
}↵
if rating.Status != "OK" {↵
println("failed downloading rating")↵
return↵
}↵
waiting = time.After(time.Second)↵
acUser := atcoderUsers[i]↵
wri.Write([]string{↵
cfUser.Handle,↵
strconv.Itoa(acUser.Rating),↵
strconv.Itoa(acUser.Highest),↵
acUser.Country,↵
strconv.Itoa(int(cfUser.Rating)),↵
strconv.Itoa(int(cfUser.MaxRating)),↵
cfUser.Country,↵
time.Unix(int64(cfUser.LastOnlineTimeSeconds), 0).UTC().String(),↵
strconv.Itoa(acUser.Matches),↵
strconv.Itoa(len(rating.Result)),↵
})↵
}↵
}↵
↵
wri.Flush()↵
if err := wri.Error(); err != nil {↵
panic(err)↵
}↵
}↵
~~~~~↵
</spoiler>↵
↵
↵
<spoiler summary="./graph/main.go">↵
~~~~~↵
package main↵
↵
import (↵
"bufio"↵
"encoding/csv"↵
"flag"↵
"fmt"↵
"image"↵
"image/color"↵
"image/png"↵
"os"↵
"strconv"↵
)↵
↵
var acColors = []color.RGBA{↵
color.RGBA{0x80, 0x80, 0x80, 0xff},↵
color.RGBA{0x80, 0x40, 0x00, 0xff},↵
color.RGBA{0x00, 0x80, 0x00, 0xff},↵
color.RGBA{0x00, 0xC0, 0xC0, 0xff},↵
color.RGBA{0x00, 0x00, 0xFF, 0xff},↵
color.RGBA{0xC0, 0xC0, 0x00, 0xff},↵
color.RGBA{0xFF, 0x80, 0x00, 0xff},↵
color.RGBA{0xFF, 0x00, 0x00, 0xff},↵
color.RGBA{0xC0, 0xC0, 0xC0, 0xff},↵
color.RGBA{0xFF, 0xD7, 0x00, 0xff},↵
}↵
↵
var cfColors = []color.RGBA{↵
color.RGBA{0x80, 0x80, 0x80, 0xff},↵
color.RGBA{0x00, 0x80, 0x00, 0xff},↵
color.RGBA{0x03, 0xA8, 0x9E, 0xff},↵
color.RGBA{0x00, 0x00, 0xFF, 0xff},↵
color.RGBA{0xAA, 0x00, 0xAA, 0xff},↵
color.RGBA{0xFF, 0x8C, 0x00, 0xff},↵
color.RGBA{0xFF, 0x00, 0x00, 0xff},↵
}↵
↵
var cfRatingBorders = []int{↵
1200,↵
1400,↵
1600,↵
1900,↵
2100,↵
2400,↵
3000,↵
}↵
↵
func Max(a, b int) int {↵
if a > b {↵
return a↵
}↵
return b↵
}↵
↵
func Min(a, b int) int {↵
if a < b {↵
return a↵
}↵
return b↵
}↵
↵
func main() {↵
↵
var minMatches int↵
flag.IntVar(&minMatches, "matches", 10, "min matches")↵
flag.Parse()↵
↵
file, err := os.Open("coders.csv")↵
if err != nil {↵
panic(err)↵
}↵
defer file.Close()↵
↵
rows, err := csv.NewReader(bufio.NewReader(file)).ReadAll()↵
if err != nil {↵
panic(err)↵
}↵
↵
dt := []int{-1, 0, 1, 0, -1}↵
↵
for p := 0; p < 2; p++ {↵
cnt := make(map[int]int)↵
↵
minAcRating := 9999↵
maxAcRating := -9999↵
minCfRating := 9999↵
maxCfRating := -9999↵
↵
for _, row := range rows[1:] {↵
acRating, err := strconv.Atoi(row[1+p])↵
if err != nil {↵
panic(err)↵
}↵
cfRating, err := strconv.Atoi(row[4+p])↵
if err != nil {↵
panic(err)↵
}↵
↵
acMatches, err := strconv.Atoi(row[8])↵
if err != nil {↵
panic(err)↵
}↵
cfMatches, err := strconv.Atoi(row[9])↵
if err != nil {↵
panic(err)↵
}↵
if acMatches < minMatches || cfMatches < minMatches {↵
continue↵
}↵
↵
minAcRating = Min(minAcRating, acRating)↵
maxAcRating = Max(maxAcRating, acRating)↵
minCfRating = Min(minCfRating, cfRating)↵
maxCfRating = Max(maxCfRating, cfRating)↵
↵
acRating += 1000↵
acRating /= 5↵
cfRating += 1000↵
cfRating /= 5↵
key := acRating*10000 + cfRating↵
if c, ok := cnt[key]; ok {↵
cnt[key] = c + 1↵
} else {↵
cnt[key] = 1↵
}↵
}↵
fmt.Println("minAcRating", minAcRating)↵
fmt.Println("maxAcRating", maxAcRating)↵
fmt.Println("minCfRating", minCfRating)↵
fmt.Println("maxCfRating", maxCfRating)↵
fmt.Println("uniq", len(cnt))↵
↵
max := 0↵
for _, c := range cnt {↵
if c > max {↵
max = c↵
}↵
}↵
fmt.Println("maxCount", max)↵
↵
img := image.NewRGBA(image.Rect(0, 0, 1000, 1000))↵
for k, c := range cnt {↵
acRating := k / 10000↵
cfRating := k % 10000↵
r := uint8(255 * c / max)↵
img.Set(cfRating, 999-acRating, color.RGBA{r, 0, 0, 0xff})↵
r = r * 95 / 100↵
for i := 0; i < 4; i++ {↵
ac := acRating + dt[i]↵
cf := cfRating + dt[i+1]↵
if ac < 0 || ac >= 1000 || cf < 0 || cf >= 1000 {↵
continue↵
}↵
if _, _, _, a := img.At(cf, 999-ac).RGBA(); a == 0 {↵
img.Set(cf, 999-ac, color.RGBA{r, 0, 0, 0xff})↵
}↵
}↵
}↵
↵
gray := color.RGBA{60, 60, 60, 0xff}↵
for i := 0; i < 1000; i++ {↵
if _, _, _, a := img.At(200, i).RGBA(); a == 0 {↵
img.Set(200, i, gray)↵
}↵
if _, _, _, a := img.At(i, 999-200).RGBA(); a == 0 {↵
img.Set(i, 999-200, gray)↵
}↵
}↵
↵
lightgray1 := color.RGBA{190, 190, 190, 0xff}↵
lightgray2 := color.RGBA{235, 235, 235, 0xff}↵
for i := 0; i < 800; i++ {↵
for j := 0; j < 800; j += 20 {↵
if _, _, _, a := img.At(200+i, 999-200-j).RGBA(); a == 0 {↵
if j%80 == 0 {↵
img.Set(200+i, 999-200-j, lightgray1)↵
} else {↵
img.Set(200+i, 999-200-j, lightgray2)↵
}↵
}↵
if _, _, _, a := img.At(200+j, 999-200-i).RGBA(); a == 0 {↵
col := lightgray2↵
for _, r := range cfRatingBorders {↵
if r/5 == j {↵
col = lightgray1↵
break↵
}↵
}↵
img.Set(200+j, 999-200-i, col)↵
}↵
}↵
}↵
↵
for i, col := range acColors {↵
for x := 190; x < 200; x++ {↵
for dy := 0; dy < 80; dy++ {↵
y := i*80 + dy↵
if _, _, _, a := img.At(x, 999-200-y).RGBA(); a == 0 {↵
img.Set(x, 999-200-y, col)↵
}↵
}↵
}↵
}↵
↵
for y := 190; y < 200; y++ {↵
for x := 0; x < 800; x++ {↵
if _, _, _, a := img.At(200+x, 999-y).RGBA(); a != 0 {↵
continue↵
}↵
col := color.RGBA{0, 0, 0, 0xff}↵
for i, r := range cfRatingBorders {↵
if x < r/5 {↵
col = cfColors[i]↵
break↵
}↵
}↵
img.Set(200+x, 999-y, col)↵
}↵
}↵
↵
graph, err := os.Create(fmt.Sprintf("graph%d_%d.png", minMatches, p))↵
if err != nil {↵
panic(err)↵
}↵
defer graph.Close()↵
err = png.Encode(graph, img)↵
if err != nil {↵
panic(err)↵
}↵
}↵
}↵
~~~~~↵
</spoiler>↵
↵
</spoiler>↵
↵