Блог пользователя Igorjan94

Автор Igorjan94, 22 месяца назад, По-английски

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.

  • Проголосовать: нравится
  • +10
  • Проголосовать: не нравится