From 1d4ca249a4652341b5a0cd15e56eeff13f5a0c78 Mon Sep 17 00:00:00 2001 From: Kimiyuki Onaka Date: Sat, 4 Apr 2020 14:20:00 +0900 Subject: [PATCH 1/3] Add the module for Google Code Jam --- onlinejudge/_implementation/main.py | 1 + onlinejudge/service/__init__.py | 1 + onlinejudge/service/google.py | 138 ++++++++++++++++++++++++++++ readme.md | 1 + 4 files changed, 141 insertions(+) create mode 100644 onlinejudge/service/google.py diff --git a/onlinejudge/_implementation/main.py b/onlinejudge/_implementation/main.py index 52232fac..9525c1cc 100644 --- a/onlinejudge/_implementation/main.py +++ b/onlinejudge/_implementation/main.py @@ -48,6 +48,7 @@ def get_parser() -> argparse.ArgumentParser: Toph (Problem Archive) CodeChef Facebook Hacker Cup + Google Code Jam Library Checker (https://judge.yosupo.jp/) supported services with --system: diff --git a/onlinejudge/service/__init__.py b/onlinejudge/service/__init__.py index 592dd748..68194bb1 100644 --- a/onlinejudge/service/__init__.py +++ b/onlinejudge/service/__init__.py @@ -6,6 +6,7 @@ import onlinejudge.service.codeforces import onlinejudge.service.csacademy import onlinejudge.service.facebook +import onlinejudge.service.google import onlinejudge.service.hackerrank import onlinejudge.service.kattis import onlinejudge.service.library_checker diff --git a/onlinejudge/service/google.py b/onlinejudge/service/google.py new file mode 100644 index 00000000..f31b6637 --- /dev/null +++ b/onlinejudge/service/google.py @@ -0,0 +1,138 @@ +""" +the module for Google Code Jam and Google Kick Start (https://codingcompetitions.withgoogle.com/) + +.. versionadded:: 9.3.0 +""" + +import base64 +import json +import re +import string +import urllib.parse +from typing import * + +import bs4 +import requests + +import onlinejudge._implementation.logging as log +import onlinejudge._implementation.utils as utils +import onlinejudge.type +from onlinejudge.type import SampleParseError, TestCase + + +class GoogleCodeJamService(onlinejudge.type.Service): + def get_url(self) -> str: + return 'https://codingcompetitions.withgoogle.com/' + + def get_name(self) -> str: + return 'Google Code Jam' + + @classmethod + def from_url(cls, url: str) -> Optional['GoogleCodeJamService']: + # example: https://codingcompetitions.withgoogle.com/ + # example: https://code.google.com/codejam + result = urllib.parse.urlparse(url) + if result.scheme in ('', 'http', 'https'): + if result.netloc == 'codingcompetitions.withgoogle.com': + return cls() + if result.netloc == 'code.google.com': + dirs = utils.normpath(result.path).split('/') + if len(dirs) >= 2 and dirs[1] == 'codejam': + return cls() + return None + + +class GoogleCodeJamProblem(onlinejudge.type.Problem): + """ + :ivar domain: :py:class:`str`, this must be `codingcompetitions.withgoogle.com` or `code.google.com` + :ivar kind: :py:class:`str`, this is typically `codejam` or `kickstart` + :ivar contest_id: :py:class:`str` + :ivar problem_id: :py:class:`str` + """ + def __init__(self, *, domain: str, kind: str, contest_id: str, problem_id: str): + assert domain in ('codingcompetitions.withgoogle.com', 'code.google.com') + self.domain = domain + self.kind = kind + self.contest_id = contest_id + self.problem_id = problem_id + + def download_sample_cases(self, *, session: Optional[requests.Session] = None) -> List[TestCase]: + session = session or utils.get_default_session() + if self.domain == 'codingcompetitions.withgoogle.com': + url = 'https://codejam.googleapis.com/dashboard/{}/poll?p=e30'.format(self.contest_id) + resp = utils.request('GET', url, session=session) + data = json.loads(base64.urlsafe_b64decode(resp.content + b'=' * ((-len(resp.content)) % 3)).decode()) + log.debug('%s', data) + + # parse JSON + for task in data['challenge']['tasks']: + if task['id'] == self.problem_id: + statement = task['statement'] + break + else: + raise SampleParseError("the problem {} is not found in the challenge {}".format(repr(self.problem_id), repr(self.contest_id))) + + elif self.domain == 'code.google.com': + url = 'https://{}/{}/contest/{}/dashboard/ContestInfo'.format(self.domain, self.kind, self.contest_id) + resp = utils.request('GET', url, session=session) + data = json.loads(resp.content) + + # parse JSON + assert self.problem_id.startswith('p') + i = int(self.problem_id[1:]) + statement = data['problems'][i]['body'] + + else: + assert False + + # parse HTML + soup = bs4.BeautifulSoup(statement, utils.html_parser) + io_contents = soup.find_all('pre', class_='io-content') + if len(io_contents) != 2: + raise SampleParseError("""the number of
 is not two""")
+        if io_contents[0].text.startswith('Case #'):
+            log.warning('''the sample input starts with "Case #"''')
+        if not io_contents[1].text.startswith('Case #'):
+            log.warning('''the sample output doesn't start with "Case #"''')
+        sample = TestCase(
+            'sample',
+            'Input',
+            utils.textfile(io_contents[0].text.rstrip()).encode(),
+            'Output',
+            utils.textfile(io_contents[1].text.rstrip()).encode(),
+        )
+        return [sample]
+
+    def get_url(self) -> str:
+        if self.domain == 'codingcompetitions.withgoogle.com':
+            return 'https://{}/{}/round/{}/{}'.format(self.domain, self.kind, self.contest_id, self.problem_id)
+        elif self.domain == 'code.google.com':
+            return 'https://{}/{}/contest/{}/dashboard#s={}'.format(self.domain, self.kind, self.contest_id, self.problem_id)
+        else:
+            assert False
+
+    @classmethod
+    def from_url(cls, url: str) -> Optional['GoogleCodeJamProblem']:
+        # example: https://codingcompetitions.withgoogle.com/codejam/round/000000000019fd27/000000000020993c
+        # example: https://codingcompetitions.withgoogle.com/kickstart/round/000000000019ffc7/00000000001d3f56
+        # example: https://code.google.com/codejam/contest/7234486/dashboard
+        # example: https://code.google.com/codejam/contest/7234486/dashboard#s=p0
+        result = urllib.parse.urlparse(url)
+        if result.scheme in ('', 'http', 'https'):
+            dirs = utils.normpath(result.path).split('/')
+            if result.netloc == 'codingcompetitions.withgoogle.com':
+                if len(dirs) >= 5 and dirs[2] == 'round' and all(c in string.hexdigits for c in dirs[3]) and all(c in string.hexdigits for c in dirs[4]):
+                    return cls(domain=result.netloc, kind=dirs[1], contest_id=dirs[3], problem_id=dirs[4])
+            if result.netloc == 'code.google.com':
+                if len(dirs) >= 5 and dirs[2] == 'contest' and dirs[3].isdigit() and dirs[4] == 'dashboard':
+                    if result.fragment == '' or re.match(r'^s=p\d+$', result.fragment):
+                        problem_id = (result.fragment or 's=p0')[2:]
+                        return cls(domain=result.netloc, kind=dirs[1], contest_id=dirs[3], problem_id=problem_id)
+        return None
+
+    def get_service(self) -> GoogleCodeJamService:
+        return GoogleCodeJamService()
+
+
+onlinejudge.dispatch.services += [GoogleCodeJamService]
+onlinejudge.dispatch.problems += [GoogleCodeJamProblem]
diff --git a/readme.md b/readme.md
index fb170ae2..9c57fb17 100644
--- a/readme.md
+++ b/readme.md
@@ -30,6 +30,7 @@ Tools for online judge services. Downloading sample cases, Testing/Submitting yo
     -   CodeChef
     -   Sphere online judge
     -   Facebook Hacker Cup
+    -   Google Code Jam
     -   Library Checker ()
 -   Download system test cases
     -   yukicoder

From 242d66d4a4fab7df57e0326e3b01788fd9ea3253 Mon Sep 17 00:00:00 2001
From: Kimiyuki Onaka 
Date: Sat, 4 Apr 2020 14:32:55 +0900
Subject: [PATCH 2/3] Add tests for Google Code Jam

---
 tests/service_google.py | 128 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 128 insertions(+)
 create mode 100644 tests/service_google.py

diff --git a/tests/service_google.py b/tests/service_google.py
new file mode 100644
index 00000000..00c80150
--- /dev/null
+++ b/tests/service_google.py
@@ -0,0 +1,128 @@
+import textwrap
+import unittest
+
+from onlinejudge.service.google import GoogleCodeJamProblem, GoogleCodeJamService
+from onlinejudge.type import TestCase
+
+
+class GoogleCodeJamSerivceTest(unittest.TestCase):
+    def test_from_url(self):
+        self.assertIsInstance(GoogleCodeJamService.from_url('https://codingcompetitions.withgoogle.com/'), GoogleCodeJamService)
+        self.assertIsInstance(GoogleCodeJamService.from_url('https://code.google.com/codejam'), GoogleCodeJamService)
+        self.assertIsNone(GoogleCodeJamService.from_url('https://code.google.com/'))
+        self.assertIsNone(GoogleCodeJamService.from_url('https://www.facebook.com/hackercup'))
+
+
+class GoogleCodeJamProblemTest(unittest.TestCase):
+    def test_from_url_codejam(self):
+        problem = GoogleCodeJamProblem.from_url('https://codingcompetitions.withgoogle.com/codejam/round/000000000019fd27/000000000020993c')
+        self.assertEqual(problem.domain, 'codingcompetitions.withgoogle.com')
+        self.assertEqual(problem.kind, 'codejam')
+        self.assertEqual(problem.contest_id, '000000000019fd27')
+        self.assertEqual(problem.problem_id, '000000000020993c')
+
+    def test_from_url_kickstart(self):
+        problem = GoogleCodeJamProblem.from_url('https://codingcompetitions.withgoogle.com/kickstart/round/000000000019ffc7/00000000001d3f56')
+        self.assertEqual(problem.domain, 'codingcompetitions.withgoogle.com')
+        self.assertEqual(problem.kind, 'kickstart')
+        self.assertEqual(problem.contest_id, '000000000019ffc7')
+        self.assertEqual(problem.problem_id, '00000000001d3f56')
+
+    def test_from_url_old(self):
+        problem = GoogleCodeJamProblem.from_url('https://code.google.com/codejam/contest/7234486/dashboard#s=p2')
+        self.assertEqual(problem.domain, 'code.google.com')
+        self.assertEqual(problem.kind, 'codejam')
+        self.assertEqual(problem.contest_id, '7234486')
+        self.assertEqual(problem.problem_id, 'p2')
+
+    def test_from_url_old_no_fragment(self):
+        problem = GoogleCodeJamProblem.from_url('https://code.google.com/codejam/contest/8404486/dashboard')
+        self.assertEqual(problem.domain, 'code.google.com')
+        self.assertEqual(problem.kind, 'codejam')
+        self.assertEqual(problem.contest_id, '8404486')
+        self.assertEqual(problem.problem_id, 'p0')
+
+    def test_download_samples_codejam(self):
+        problem = GoogleCodeJamProblem.from_url('https://codingcompetitions.withgoogle.com/codejam/round/000000000019fd27/000000000020993c')
+        sample_input = textwrap.dedent("""\
+            3
+            4
+            1 2 3 4
+            2 1 4 3
+            3 4 1 2
+            4 3 2 1
+            4
+            2 2 2 2
+            2 3 2 3
+            2 2 2 3
+            2 2 2 2
+            3
+            2 1 3
+            1 3 2
+            1 2 3
+            """).encode()
+        sample_output = textwrap.dedent("""\
+            Case #1: 4 0 0
+            Case #2: 9 4 4
+            Case #3: 8 0 2
+            """).encode()
+        self.assertEqual(problem.download_sample_cases(), [
+            TestCase(
+                'sample',
+                'Input',
+                sample_input,
+                'Output',
+                sample_output,
+            ),
+        ])
+
+    def test_download_samples_kickstart(self):
+        problem = GoogleCodeJamProblem.from_url('https://codingcompetitions.withgoogle.com/kickstart/round/000000000019ffc7/00000000001d3f56')
+        sample_input = textwrap.dedent("""\
+            3
+            4 100
+            20 90 40 90
+            4 50
+            30 30 10 10
+            3 300
+            999 999 999
+            """).encode()
+        sample_output = textwrap.dedent("""\
+            Case #1: 2
+            Case #2: 3
+            Case #3: 0
+            """).encode()
+        self.assertEqual(problem.download_sample_cases(), [
+            TestCase(
+                'sample',
+                'Input',
+                sample_input,
+                'Output',
+                sample_output,
+            ),
+        ])
+
+    def test_download_samples_old(self):
+        problem = GoogleCodeJamProblem.from_url('https://code.google.com/codejam/contest/7234486/dashboard#s=p2')
+        sample_input = textwrap.dedent("""\
+            4
+            4 100000
+            4 300000
+            3 300000
+            100 499999
+            """).encode()
+        sample_output = textwrap.dedent("""\
+            Case #1: 9
+            Case #2: 7
+            Case #3: 5
+            Case #4: 3
+            """).encode()
+        self.assertEqual(problem.download_sample_cases(), [
+            TestCase(
+                'sample',
+                'Input',
+                sample_input,
+                'Output',
+                sample_output,
+            ),
+        ])

From 5eca9d64bca477bcf2127cd49a9ce191ca9928b3 Mon Sep 17 00:00:00 2001
From: Kimiyuki Onaka 
Date: Sat, 4 Apr 2020 15:01:46 +0900
Subject: [PATCH 3/3] Fix onlinejudge/service/google.py for Python 3.5

---
 onlinejudge/service/google.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/onlinejudge/service/google.py b/onlinejudge/service/google.py
index f31b6637..c1168853 100644
--- a/onlinejudge/service/google.py
+++ b/onlinejudge/service/google.py
@@ -75,7 +75,7 @@ def download_sample_cases(self, *, session: Optional[requests.Session] = None) -
         elif self.domain == 'code.google.com':
             url = 'https://{}/{}/contest/{}/dashboard/ContestInfo'.format(self.domain, self.kind, self.contest_id)
             resp = utils.request('GET', url, session=session)
-            data = json.loads(resp.content)
+            data = json.loads(resp.content.decode())
 
             # parse JSON
             assert self.problem_id.startswith('p')