Hello codeforces!
You know you are a programming competitions addict when this blog was 9 years ago, but you thought it was no more than 3...
More than month ago or, to be more precise from 15 to 19 December 2022, codeforces has five contests in five days. I participated in all of these contests and I thought how many contestants took part in all 5 contests?
TLDR: If you don't want to read why I decided to implement SDK on typescript and how I struggled with python, just scroll down to Typescript paragraph.
TLDR2: Links are below.
Python
Historically I have some python scripts to work with codeforces API, so I've created allContests.py
file, imported my old library code with method cf
and paused for a while. Which arguments does this method have? Let's look into source code. Ah, clear, def cf(method, **kwargs)
.
F**k. OK, let's go to API page. OK, then we should call:
standings = cf('contest.standings', contestId = contestId, showUnofficial = True)
But what does it return? Ah, no problem, let's run and see. {"status":"FAILED","comment":"Internal Server Error"}
. Whaaat? Codeforces returned 500
?
Ok, seams that from
is required. No problem:
standings = cf('contest.standings', contestId = contestId, showUnofficial = True, from = 1)
Fail, from
is the reserved keyword.
standings = cf('contest.standings', contestId = contestId, showUnofficial = True, **{'from' = 1})
Ok but what does this method returns?
Return value: Returns object with three fields: "contest", "problems" and "rows". Field "contest" contains a Contest object. Field "problems" contains a list of Problem objects. Field "rows" contains a list of RanklistRow objects.
Ok, seams that we need rows
:
>>> print(standings.rows)
KeyError: 'rows'
Ah wait, I don't unwrap result
in my lib:
>>> print(standings.result.rows)
[{...correct result...}]
Ok, seams legit, almost solved:
name = participant.party.members[0].handle
t = participant.party.participantType
rank = participant.rank
points = participant.points
Ok, which values can have t
? Once more time to go to documentation
Enum: CONTESTANT, PRACTICE, VIRTUAL, MANAGER, OUT_OF_COMPETITION
Ok, filtered participants that everybody solved at least one bugaboo, sorted by cumulative rank, run!
503 Service Temporarily Unavailable
API may be requested at most 1 time per two seconds
Yes, I forgot to sleep
between API calls:
time.sleep(2)
I don't remember how much time did it take, but it was really bad experience during at least one hour. Fortunately I have dotdict
wrapper so I don't need to write obejct['field']
access, I can use object.field
. Seams that my solution is really bad.
We need SDK. Let's google existing, there should be many of them!
First one doesn't have any types, so can't help me in my struggle. Second one is really good. But
assert isinstance(from_, int), 'from_ should be of type int, not {}'.format(type(from_))
Of course it's not bad, it's python's feature and library solves this issue really well. But stop, why on earth do I use python?
Typescript!
Unlike javascript and python typescript has static typing. And types really help! We use c++ on codeforces for almost every problem, we know everything about types, yes.
So I decided to implement API wrapper on typescript.
With my sdk I've implemented typescript analog of bugaboo above within 10 minutes without looking at my python code and in documentation: autocomplete did this work for me.
So, why typescript? I use typescript during couple of years and know that it has some pros:
Type system
First of all, its type system really helps. It catches many-many-many errors in compile time whilst python throws it in runtime.
Let's look into example: SDK constructor can accept three arguments: lang
, key
and secret
.
Python (let's take CodeforcesAPI as model solution):
def __init__(self, lang=CodeforcesLanguage.en, key=None, secret=None):
Ok, seams that we can use it as CodeforcesAPI(key=key)
. Or CodeforcesAPI(secret=secret)
. It's definetely not what we want. And library will fail in runtime or just say nothing at all.
Typescript:
type DefaultApiOptions = {
lang?: AvailableLanguages
} & ({} | {
secret: string
key: string
})
It explicitly says, that it accepts optional lang
and one of (nothing more) and (both key
and secret
). So if we try to call method with argument of this type with {key: key}
it just won't compile.
It's just small example and type system allows you many convenient things.
Aliases
Secondly, typescript allows creating implicitlty unconvertible type aliases.
type First = number & { __unique__: 'first' }
type Second = number & { __unique__: 'second' }
const x: number = 5
const f = (arg: Second) => {}
//f(5) // <-- CE
//f(x) // <-- CE
//f(x as First) // <-- CE
f(x as Second) // <-- AC
Note that types exist ONLY in compile time and don't exists in runtime!
Why is it really cool feature? You can't imaging how many times I messed up function arguments in different order or different object field of same type.
As far as I know nor python neither c++ has this feature (without runtime overhead). Seams that some modern c++
-killer language doesn't have this feature too.
Constructing new types
For example you want to query standings of multiple contests:
const multipleStandings = async (
options: Omit<StandingsOptions, 'contestId'>,
...contestIds: Array<ContestId>
) => {
for (const contestId of contestIds) {
// const contest = await standings(options) // <-- CE, no contestId field
const contest = await standings({...options, contestId})
// ...
}
}
We just proxyed our options to other method and deleted contestId
field from it to ensure that caller won't fill this field. And typescript ensures that we filled removed field ourselves.
Strict null check
Imaging you are calculating some function f(rating: number)
. You got users with user.info
method and call f(user.rating)
. CE.
Argument of type 'number | undefined' is not assignable to parameter of type 'number'
Why? Because unrated users don't have rating, they have undefined
instead. If you are sure, that user has rating, e.g. you filtered unrated users you can add !
: f(user.rating!)
. Otherwise you can add default value if rating is undefined
: f(user.rating ?? 0)
. Anyway you explicitly say typescript that you know what you do.
Modules
Almost everyting you can import
in python you can easily import
(or at least require
) in typescript. For all other things there is mastercard ffi.
Performance
According to Mike's heap benchmark js is only three times slower than c++. But we implement our scripts on python, nobody cares!
Lazyness
If your type is too complicated or you want something forbidden (from typescript's point of view) you can just use any
or unknown
types. Or even add @ts-ignore
annotation to silent compilation error.
Does somebody still needs python?
As for me, I will use python for:
- testgens;
- one-liners;
- calculator;
import yyy
$$$\implies$$$ solved;- legacy.
Can I use typescript in contests?
Yes, but no.
- It doesn't have
long long
. - It doesn't have stl. You should implement it yourself.
- Stack size is not set for javascript, I tried to solve this task and failed.
Links
If you want use it or want to see some examples, just follow links below.
NPM
Github
PS: I know that typescript transpiles, not compiles but in this context it doesn't matter.
PPS: ironycally, almost at the same time this library was published (6th January), but mine was created on 20 December though in private repo. It has really similar implementation, but doesn't use type aliases and enums for enums.
PPPS: I know that according to pep8 there is no space here: f(param = param)
.
PPPPS: There were 1290 participants who took part in all 5 contests.