feat(tests): basic tests

This commit is contained in:
Barrett Ruth 2025-10-05 21:58:43 -04:00
parent a7eb731730
commit c509102b37
22 changed files with 17879 additions and 93 deletions

View file

@ -31,7 +31,7 @@ jobs:
python: python:
- 'scripts/**' - 'scripts/**'
- 'scrapers/**' - 'scrapers/**'
- 'tests/scrapers/**' - 'tests/**'
- 'pyproject.toml' - 'pyproject.toml'
- 'uv.lock' - 'uv.lock'
@ -64,4 +64,4 @@ jobs:
- name: Fetch camoufox data - name: Fetch camoufox data
run: uv run camoufox fetch run: uv run camoufox fetch
- name: Run Python tests - name: Run Python tests
run: uv run pytest tests/scrapers/ -v run: uv run pytest tests/ -v

View file

@ -6,7 +6,6 @@ repos:
- id: stylua-github - id: stylua-github
name: stylua (Lua formatter) name: stylua (Lua formatter)
args: ["."] args: ["."]
files: ^(lua/|spec/|plugin/|after/|ftdetect/|.*\.lua$)
additional_dependencies: [] additional_dependencies: []
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9 rev: v0.6.9
@ -16,16 +15,14 @@ repos:
files: ^(scrapers/|tests/scrapers/|.*\.py$) files: ^(scrapers/|tests/scrapers/|.*\.py$)
- id: ruff - id: ruff
name: ruff (lint) name: ruff (lint)
args: ["--fix", "--select=I"] args: ["--fix", "--select=I", "."]
files: ^(scrapers/|tests/scrapers/|.*\.py$)
- repo: local - repo: local
hooks: hooks:
- id: mypy - id: mypy
name: mypy (type check) name: mypy (type check)
entry: uv run mypy entry: uv run mypy
language: system language: system
args: ["scrapers/", "tests/scrapers/"] args: ["."]
files: ^(scrapers/|tests/scrapers/|.*\.py$)
pass_filenames: false pass_filenames: false
- repo: https://github.com/pre-commit/mirrors-prettier - repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0 rev: v3.1.0

View file

@ -10,6 +10,7 @@ dependencies = [
"curl-cffi>=0.13.0", "curl-cffi>=0.13.0",
"httpx>=0.28.1", "httpx>=0.28.1",
"ndjson>=0.3.1", "ndjson>=0.3.1",
"pydantic>=2.11.10",
"requests>=2.32.5", "requests>=2.32.5",
"scrapling[fetchers]>=0.3.5", "scrapling[fetchers]>=0.3.5",
] ]

View file

@ -5,7 +5,6 @@ import json
import re import re
import sys import sys
import time import time
from dataclasses import asdict
from typing import Any from typing import Any
import backoff import backoff
@ -231,16 +230,12 @@ def _scrape_problem_page_sync(contest_id: str, slug: str) -> dict[str, Any]:
def _to_problem_summaries(rows: list[dict[str, str]]) -> list[ProblemSummary]: def _to_problem_summaries(rows: list[dict[str, str]]) -> list[ProblemSummary]:
out: list[ProblemSummary] = [] out: list[ProblemSummary] = []
seen: set[str] = set()
for r in rows: for r in rows:
letter = (r.get("letter") or "").strip().upper() letter = (r.get("letter") or "").strip().upper()
title = r.get("title") or "" title = r.get("title") or ""
if not letter: if not letter:
continue continue
pid = letter.lower() pid = letter.lower()
if pid in seen:
continue
seen.add(pid)
out.append(ProblemSummary(id=pid, name=title)) out.append(ProblemSummary(id=pid, name=title))
return out return out
@ -341,7 +336,7 @@ async def main_async() -> int:
success=False, success=False,
error="Usage: atcoder.py metadata <contest_id> OR atcoder.py tests <contest_id> OR atcoder.py contests", error="Usage: atcoder.py metadata <contest_id> OR atcoder.py tests <contest_id> OR atcoder.py contests",
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1
mode: str = sys.argv[1] mode: str = sys.argv[1]
@ -352,11 +347,11 @@ async def main_async() -> int:
result = MetadataResult( result = MetadataResult(
success=False, error="Usage: atcoder.py metadata <contest_id>" success=False, error="Usage: atcoder.py metadata <contest_id>"
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1
contest_id = sys.argv[2] contest_id = sys.argv[2]
result = await scraper.scrape_contest_metadata(contest_id) result = await scraper.scrape_contest_metadata(contest_id)
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 0 if result.success else 1 return 0 if result.success else 1
if mode == "tests": if mode == "tests":
@ -370,7 +365,7 @@ async def main_async() -> int:
timeout_ms=0, timeout_ms=0,
memory_mb=0, memory_mb=0,
) )
print(json.dumps(asdict(tests_result))) print(tests_result.model_dump_json())
return 1 return 1
contest_id = sys.argv[2] contest_id = sys.argv[2]
await scraper.stream_tests_for_category_async(contest_id) await scraper.stream_tests_for_category_async(contest_id)
@ -381,17 +376,17 @@ async def main_async() -> int:
contest_result = ContestListResult( contest_result = ContestListResult(
success=False, error="Usage: atcoder.py contests" success=False, error="Usage: atcoder.py contests"
) )
print(json.dumps(asdict(contest_result))) print(contest_result.model_dump_json())
return 1 return 1
contest_result = await scraper.scrape_contest_list() contest_result = await scraper.scrape_contest_list()
print(json.dumps(asdict(contest_result))) print(contest_result.model_dump_json())
return 0 if contest_result.success else 1 return 0 if contest_result.success else 1
result = MetadataResult( result = MetadataResult(
success=False, success=False,
error="Unknown mode. Use 'metadata <contest_id>', 'tests <contest_id>', or 'contests'", error="Unknown mode. Use 'metadata <contest_id>', 'tests <contest_id>', or 'contests'",
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1

View file

@ -1,20 +1,9 @@
from __future__ import annotations
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Awaitable, Callable, ParamSpec, cast from typing import Any, Awaitable, Callable, ParamSpec, cast
from .models import ContestListResult, MetadataResult, TestsResult
P = ParamSpec("P") P = ParamSpec("P")
from .models import ContestListResult, MetadataResult, TestsResult
@dataclass
class ScraperConfig:
timeout_seconds: int = 30
max_retries: int = 3
backoff_base: float = 2.0
rate_limit_delay: float = 1.0
class BaseScraper(ABC): class BaseScraper(ABC):
@ -38,6 +27,7 @@ class BaseScraper(ABC):
success=False, success=False,
error=f"{self.platform_name}: {error_msg}", error=f"{self.platform_name}: {error_msg}",
contest_id=contest_id, contest_id=contest_id,
problems=[],
) )
def _create_tests_error( def _create_tests_error(
@ -51,11 +41,14 @@ class BaseScraper(ABC):
tests=[], tests=[],
timeout_ms=0, timeout_ms=0,
memory_mb=0, memory_mb=0,
interactive=False,
) )
def _create_contests_error(self, error_msg: str) -> ContestListResult: def _create_contests_error(self, error_msg: str) -> ContestListResult:
return ContestListResult( return ContestListResult(
success=False, error=f"{self.platform_name}: {error_msg}" success=False,
error=f"{self.platform_name}: {error_msg}",
contests=[],
) )
async def _safe_execute( async def _safe_execute(

View file

@ -5,7 +5,6 @@ import json
import logging import logging
import re import re
import sys import sys
from dataclasses import asdict
from typing import Any from typing import Any
import requests import requests
@ -63,8 +62,6 @@ def _extract_limits(block: Tag) -> tuple[int, float]:
def _group_lines_by_id(pre: Tag) -> dict[int, list[str]]: def _group_lines_by_id(pre: Tag) -> dict[int, list[str]]:
groups: dict[int, list[str]] = {} groups: dict[int, list[str]] = {}
if not isinstance(pre, Tag):
return groups
for div in pre.find_all("div", class_="test-example-line"): for div in pre.find_all("div", class_="test-example-line"):
cls = " ".join(div.get("class", [])) cls = " ".join(div.get("class", []))
m = re.search(r"\btest-example-line-(\d+)\b", cls) m = re.search(r"\btest-example-line-(\d+)\b", cls)
@ -182,12 +179,8 @@ def _scrape_contest_problems_sync(contest_id: str) -> list[ProblemSummary]:
html = _fetch_problems_html(contest_id) html = _fetch_problems_html(contest_id)
blocks = _parse_all_blocks(html) blocks = _parse_all_blocks(html)
problems: list[ProblemSummary] = [] problems: list[ProblemSummary] = []
seen: set[str] = set()
for b in blocks: for b in blocks:
pid = b["letter"].upper() pid = b["letter"].upper()
if pid in seen:
continue
seen.add(pid)
problems.append(ProblemSummary(id=pid.lower(), name=b["name"])) problems.append(ProblemSummary(id=pid.lower(), name=b["name"]))
return problems return problems
@ -267,7 +260,7 @@ async def main_async() -> int:
success=False, success=False,
error="Usage: codeforces.py metadata <contest_id> OR codeforces.py tests <contest_id> OR codeforces.py contests", error="Usage: codeforces.py metadata <contest_id> OR codeforces.py tests <contest_id> OR codeforces.py contests",
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1
mode: str = sys.argv[1] mode: str = sys.argv[1]
@ -278,11 +271,11 @@ async def main_async() -> int:
result = MetadataResult( result = MetadataResult(
success=False, error="Usage: codeforces.py metadata <contest_id>" success=False, error="Usage: codeforces.py metadata <contest_id>"
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1
contest_id = sys.argv[2] contest_id = sys.argv[2]
result = await scraper.scrape_contest_metadata(contest_id) result = await scraper.scrape_contest_metadata(contest_id)
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 0 if result.success else 1 return 0 if result.success else 1
if mode == "tests": if mode == "tests":
@ -296,7 +289,7 @@ async def main_async() -> int:
timeout_ms=0, timeout_ms=0,
memory_mb=0, memory_mb=0,
) )
print(json.dumps(asdict(tests_result))) print(tests_result.model_dump_json())
return 1 return 1
contest_id = sys.argv[2] contest_id = sys.argv[2]
await scraper.stream_tests_for_category_async(contest_id) await scraper.stream_tests_for_category_async(contest_id)
@ -307,17 +300,17 @@ async def main_async() -> int:
contest_result = ContestListResult( contest_result = ContestListResult(
success=False, error="Usage: codeforces.py contests" success=False, error="Usage: codeforces.py contests"
) )
print(json.dumps(asdict(contest_result))) print(contest_result.model_dump_json())
return 1 return 1
contest_result = await scraper.scrape_contest_list() contest_result = await scraper.scrape_contest_list()
print(json.dumps(asdict(contest_result))) print(contest_result.model_dump_json())
return 0 if contest_result.success else 1 return 0 if contest_result.success else 1
result = MetadataResult( result = MetadataResult(
success=False, success=False,
error="Unknown mode. Use 'metadata <contest_id>', 'tests <contest_id>', or 'contests'", error="Unknown mode. Use 'metadata <contest_id>', 'tests <contest_id>', or 'contests'",
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1

View file

@ -4,7 +4,6 @@ import asyncio
import json import json
import re import re
import sys import sys
from dataclasses import asdict
from typing import Any from typing import Any
import httpx import httpx
@ -251,7 +250,7 @@ async def main_async() -> int:
success=False, success=False,
error="Usage: cses.py metadata <category_id> OR cses.py tests <category> OR cses.py contests", error="Usage: cses.py metadata <category_id> OR cses.py tests <category> OR cses.py contests",
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1
mode: str = sys.argv[1] mode: str = sys.argv[1]
@ -262,11 +261,11 @@ async def main_async() -> int:
result = MetadataResult( result = MetadataResult(
success=False, error="Usage: cses.py metadata <category_id>" success=False, error="Usage: cses.py metadata <category_id>"
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1
category_id = sys.argv[2] category_id = sys.argv[2]
result = await scraper.scrape_contest_metadata(category_id) result = await scraper.scrape_contest_metadata(category_id)
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 0 if result.success else 1 return 0 if result.success else 1
if mode == "tests": if mode == "tests":
@ -280,7 +279,7 @@ async def main_async() -> int:
timeout_ms=0, timeout_ms=0,
memory_mb=0, memory_mb=0,
) )
print(json.dumps(asdict(tests_result))) print(tests_result.model_dump_json())
return 1 return 1
category = sys.argv[2] category = sys.argv[2]
await scraper.stream_tests_for_category_async(category) await scraper.stream_tests_for_category_async(category)
@ -291,17 +290,17 @@ async def main_async() -> int:
contest_result = ContestListResult( contest_result = ContestListResult(
success=False, error="Usage: cses.py contests" success=False, error="Usage: cses.py contests"
) )
print(json.dumps(asdict(contest_result))) print(contest_result.model_dump_json())
return 1 return 1
contest_result = await scraper.scrape_contest_list() contest_result = await scraper.scrape_contest_list()
print(json.dumps(asdict(contest_result))) print(contest_result.model_dump_json())
return 0 if contest_result.success else 1 return 0 if contest_result.success else 1
result = MetadataResult( result = MetadataResult(
success=False, success=False,
error=f"Unknown mode: {mode}. Use 'metadata <category>', 'tests <category>', or 'contests'", error=f"Unknown mode: {mode}. Use 'metadata <category>', 'tests <category>', or 'contests'",
) )
print(json.dumps(asdict(result))) print(result.model_dump_json())
return 1 return 1

View file

@ -1,47 +1,71 @@
from dataclasses import dataclass, field from pydantic import BaseModel, Field
@dataclass class TestCase(BaseModel):
class TestCase:
input: str input: str
expected: str expected: str
class Config:
extra = "forbid"
@dataclass
class ProblemSummary: class ProblemSummary(BaseModel):
id: str id: str
name: str name: str
class Config:
extra = "forbid"
@dataclass
class ContestSummary: class ContestSummary(BaseModel):
id: str id: str
name: str name: str
display_name: str display_name: str | None = None
class Config:
extra = "forbid"
@dataclass class ScrapingResult(BaseModel):
class ScrapingResult:
success: bool success: bool
error: str error: str
class Config:
extra = "forbid"
@dataclass
class MetadataResult(ScrapingResult): class MetadataResult(ScrapingResult):
contest_id: str = "" contest_id: str = ""
problems: list[ProblemSummary] = field(default_factory=list) problems: list[ProblemSummary] = Field(default_factory=list)
class Config:
extra = "forbid"
@dataclass
class ContestListResult(ScrapingResult): class ContestListResult(ScrapingResult):
contests: list[ContestSummary] = field(default_factory=list) contests: list[ContestSummary] = Field(default_factory=list)
class Config:
extra = "forbid"
@dataclass
class TestsResult(ScrapingResult): class TestsResult(ScrapingResult):
problem_id: str problem_id: str
url: str url: str
tests: list[TestCase] tests: list[TestCase] = Field(default_factory=list)
timeout_ms: int timeout_ms: int
memory_mb: float memory_mb: float
interactive: bool = False interactive: bool = False
class Config:
extra = "forbid"
class ScraperConfig(BaseModel):
timeout_seconds: int = 30
max_retries: int = 3
backoff_base: float = 2.0
rate_limit_delay: float = 1.0
class Config:
extra = "forbid"

View file

@ -1,43 +1,171 @@
import io
import json
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Callable
import pytest import pytest
ROOT = Path(__file__).resolve().parent.parent ROOT = Path(__file__).resolve().parent.parent
FIXTURES = Path(__file__).resolve().parent / "fixtures" FIX = Path(__file__).resolve().parent / "fixtures"
@pytest.fixture @pytest.fixture
def fixture_text(): def fixture_text():
"""Load HTML fixture by filename."""
def _load(name: str) -> str: def _load(name: str) -> str:
p = FIXTURES / name p = FIX / name
return p.read_text(encoding="utf-8") return p.read_text(encoding="utf-8")
return _load return _load
def _compile_and_exec_module(
module_path: Path, offline_fetch_impls: dict[str, Callable]
):
src = module_path.read_text(encoding="utf-8")
replacements: list[tuple[str, str]] = [
("def _fetch(", "def _orig_fetch("),
("def fetch_text(", "def _orig_fetch_text("),
("async def _get_async(", "async def _orig_get_async("),
]
for old, new in replacements:
src = src.replace(old, new)
stub_lines = []
if " _orig_fetch(" in src or "def _orig_fetch(" in src:
stub_lines.append("_fetch = __offline_fetch_sync")
if " _orig_fetch_text(" in src or "def _orig_fetch_text(" in src:
stub_lines.append("fetch_text = __offline_fetch_text")
if " _orig_get_async(" in src or "async def _orig_get_async(" in src:
stub_lines.append("_get_async = __offline_fetch_async")
src += "\n" + "\n".join(stub_lines) + "\n"
ns = {}
ns.update(offline_fetch_impls)
exec(compile(src, str(module_path), "exec"), ns)
return ns
def _capture_stdout(coro):
import asyncio
buf = io.StringIO()
old = sys.stdout
sys.stdout = buf
try:
rc = asyncio.run(coro)
out = buf.getvalue()
finally:
sys.stdout = old
return rc, out
@pytest.fixture @pytest.fixture
def run_scraper(monkeypatch): def run_scraper_offline(fixture_text):
def _run(name: str, mode: str, *args, replace_fetch=None) -> dict: def _router_cses(*, path: str | None = None, url: str | None = None) -> str:
scraper_path = ROOT / "scrapers" / f"{name}.py" if path == "/problemset/list":
ns = {} return fixture_text("cses_contests.html")
code = scraper_path.read_text(encoding="utf-8") if path and path.startswith("/problemset/task/"):
if replace_fetch: pid = path.rsplit("/", 1)[-1]
code = code.replace("def _fetch", "def _fixture_fetch") return fixture_text(f"cses_task_{pid}.html")
code += f"\n_fetch = _fixture_fetch\nfetch_text = _fixture_fetch\n" raise AssertionError(f"No fixture for CSES path={path!r}")
ns.update(replace_fetch)
exec(compile(code, str(scraper_path), "exec"), ns) def _router_atcoder(*, path: str | None = None, url: str | None = None) -> str:
if not url:
raise AssertionError("AtCoder expects url routing")
if "/contests/archive" in url:
return fixture_text("atcoder_contests.html")
if url.endswith("/tasks"):
return fixture_text("atcoder_abc100_tasks.html")
if "/tasks/" in url:
slug = url.rsplit("/", 1)[-1]
return fixture_text(f"atcoder_task_{slug}.html")
raise AssertionError(f"No fixture for AtCoder url={url!r}")
def _router_codeforces(*, path: str | None = None, url: str | None = None) -> str:
if not url:
raise AssertionError("Codeforces expects url routing")
if "/contests" in url and "/problem/" not in url:
return fixture_text("codeforces_contests.html")
if "/problem/" in url:
parts = url.rstrip("/").split("/")
contest_id, index = parts[-3], parts[-1]
return fixture_text(f"codeforces_{contest_id}_{index}.html")
if "/problemset/problem/" in url:
parts = url.rstrip("/").split("/")
contest_id, index = parts[-2], parts[-1]
return fixture_text(f"codeforces_{contest_id}_{index}.html")
raise AssertionError(f"No fixture for Codeforces url={url!r}")
def _make_offline_fetches(scraper_name: str):
if scraper_name == "cses":
def __offline_fetch_text(client, path: str) -> str:
return _router_cses(path=path)
return {
"__offline_fetch_text": __offline_fetch_text,
"__offline_fetch_sync": lambda url: (_ for _ in ()).throw(
AssertionError("CSES doesn't use _fetch")
),
"__offline_fetch_async": lambda client, url: (_ for _ in ()).throw(
AssertionError("CSES doesn't use _get_async")
),
}
if scraper_name == "atcoder":
async def __offline_fetch_async(client, url: str) -> str:
return _router_atcoder(url=url)
def __offline_fetch_sync(url: str) -> str:
return _router_atcoder(url=url)
return {
"__offline_fetch_text": lambda client, path: (_ for _ in ()).throw(
AssertionError("AtCoder doesn't use fetch_text")
),
"__offline_fetch_sync": __offline_fetch_sync,
"__offline_fetch_async": __offline_fetch_async,
}
if scraper_name == "codeforces":
def __offline_fetch_sync(url: str) -> str:
return _router_codeforces(url=url)
return {
"__offline_fetch_text": lambda client, path: (_ for _ in ()).throw(
AssertionError("Codeforces doesn't use fetch_text")
),
"__offline_fetch_sync": __offline_fetch_sync,
"__offline_fetch_async": lambda client, url: (_ for _ in ()).throw(
AssertionError("Codeforces doesn't use _get_async")
),
}
raise AssertionError(f"Unknown scraper: {scraper_name}")
def _run(scraper_name: str, mode: str, *args: str):
mod_path = ROOT / "scrapers" / f"{scraper_name}.py"
ns = _compile_and_exec_module(mod_path, _make_offline_fetches(scraper_name))
main_async = ns.get("main_async") main_async = ns.get("main_async")
if not main_async: assert callable(main_async), f"main_async not found in {scraper_name}"
raise RuntimeError(f"Could not load main_async from {name}.py")
import asyncio
async def wrapper(): argv = [str(mod_path), mode, *args]
sys.argv = [str(scraper_path), mode, *args] old_argv = sys.argv
return await main_async() sys.argv = argv
try:
rc, out = _capture_stdout(main_async())
finally:
sys.argv = old_argv
return asyncio.run(wrapper()) json_lines = []
for line in (l for l in out.splitlines() if l.strip()):
try:
json_lines.append(json.loads(line))
except json.JSONDecodeError as e:
raise AssertionError(
f"Invalid JSON from {scraper_name} {mode}: {line}"
) from e
return rc, json_lines
return _run return _run

519
tests/fixtures/atcoder_abc100_tasks.html vendored Normal file
View file

@ -0,0 +1,519 @@
<!doctype html>
<html>
<head>
<title>Tasks - AtCoder Beginner Contest 100</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="format-detection" content="telephone=no" />
<meta
name="google-site-verification"
content="nXGC_JxO0yoP1qBzMnYD_xgufO6leSLw1kyNo2HZltM"
/>
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-RC512FD18N"
></script>
<script>
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
gtag('set', 'user_properties', {
login_status: 'logged_out'
})
gtag('config', 'G-RC512FD18N')
</script>
<meta
name="description"
content="AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online."
/>
<meta name="author" content="AtCoder Inc." />
<meta property="og:site_name" content="AtCoder" />
<meta property="og:title" content="Tasks - AtCoder Beginner Contest 100" />
<meta
property="og:description"
content="AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online."
/>
<meta property="og:type" content="website" />
<meta
property="og:url"
content="https://atcoder.jp/contests/abc100/tasks"
/>
<meta
property="og:image"
content="https://img.atcoder.jp/assets/atcoder.png"
/>
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@atcoder" />
<meta
property="twitter:title"
content="Tasks - AtCoder Beginner Contest 100"
/>
<link
href="//fonts.googleapis.com/css?family=Lato:400,700"
rel="stylesheet"
type="text/css"
/>
<link
rel="stylesheet"
type="text/css"
href="//img.atcoder.jp/public/6372bb3/css/bootstrap.min.css"
/>
<link
rel="stylesheet"
type="text/css"
href="//img.atcoder.jp/public/6372bb3/css/base.css"
/>
<link
rel="shortcut icon"
type="image/png"
href="//img.atcoder.jp/assets/favicon.png"
/>
<link rel="apple-touch-icon" href="//img.atcoder.jp/assets/atcoder.png" />
<script src="//img.atcoder.jp/public/6372bb3/js/lib/jquery-1.9.1.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/lib/bootstrap.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/js.cookie.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/moment.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/moment_js-ja.js"></script>
<script>
var LANG = 'en'
var userScreenName = ''
var csrfToken = 'q+4tZ4tLQh/4nobcpVAuiml6OVEZOdZDZURhPenxPbc='
</script>
<script src="//img.atcoder.jp/public/6372bb3/js/utils.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/contest.js"></script>
<link
href="//img.atcoder.jp/public/6372bb3/css/contest.css"
rel="stylesheet"
/>
<script>
var contestScreenName = 'abc100'
var remainingText = 'Remaining Time'
var countDownText = 'Contest begins in'
var startTime = moment('2018-06-16T21:00:00+09:00')
var endTime = moment('2018-06-16T22:40:00+09:00')
</script>
<style></style>
<script src="//img.atcoder.jp/public/6372bb3/js/base.js"></script>
</head>
<body>
<script type="text/javascript">
var __pParams = __pParams || []
__pParams.push({
client_id: '468',
c_1: 'atcodercontest',
c_2: 'ClientSite'
})
</script>
<script
type="text/javascript"
src="https://cdn.d2-apps.net/js/tr.js"
async
></script>
<div
id="modal-contest-start"
class="modal fade"
tabindex="-1"
role="dialog"
>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Contest started</h4>
</div>
<div class="modal-body">
<p>AtCoder Beginner Contest 100 has begun.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<div id="modal-contest-end" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Contest is over</h4>
</div>
<div class="modal-body">
<p>AtCoder Beginner Contest 100 has ended.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<div id="main-div" class="float-container">
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button
type="button"
class="navbar-toggle collapsed"
data-toggle="collapse"
data-target="#navbar-collapse"
aria-expanded="false"
>
<span class="icon-bar"></span><span class="icon-bar"></span
><span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/home"></a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav">
<li>
<a class="contest-title" href="/contests/abc100"
>AtCoder Beginner Contest 100</a
>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a
class="dropdown-toggle"
data-toggle="dropdown"
href="#"
role="button"
aria-haspopup="true"
aria-expanded="false"
>
<img src="//img.atcoder.jp/assets/top/img/flag-lang/en.png" />
English <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<a href="/contests/abc100/tasks?lang=ja"
><img
src="//img.atcoder.jp/assets/top/img/flag-lang/ja.png"
/>
日本語</a
>
</li>
<li>
<a href="/contests/abc100/tasks?lang=en"
><img
src="//img.atcoder.jp/assets/top/img/flag-lang/en.png"
/>
English</a
>
</li>
</ul>
</li>
<li>
<a
href="/register?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks"
>Sign Up</a
>
</li>
<li>
<a
href="/login?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks"
>Sign In</a
>
</li>
</ul>
</div>
</div>
</nav>
<form
method="POST"
name="form_logout"
action="/logout?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks"
>
<input
type="hidden"
name="csrf_token"
value="q&#43;4tZ4tLQh/4nobcpVAuiml6OVEZOdZDZURhPenxPbc="
/>
</form>
<div id="main-container" class="container" style="padding-top: 50px">
<div class="row">
<div id="contest-nav-tabs" class="col-sm-12 mb-2 cnvtb-fixed">
<div>
<small class="contest-duration">
Contest Duration:
<a
href="http://www.timeanddate.com/worldclock/fixedtime.html?iso=20180616T2100&p1=248"
target="blank"
><time class="fixtime fixtime-full"
>2018-06-16 21:00:00+0900</time
></a
>
-
<a
href="http://www.timeanddate.com/worldclock/fixedtime.html?iso=20180616T2240&p1=248"
target="blank"
><time class="fixtime fixtime-full"
>2018-06-16 22:40:00+0900</time
></a
>
(local time) (100 minutes)
</small>
<small class="back-to-home pull-right"
><a href="/home">Back to Home</a></small
>
</div>
<ul class="nav nav-tabs">
<li>
<a href="/contests/abc100"
><span
class="glyphicon glyphicon-home"
aria-hidden="true"
></span>
Top</a
>
</li>
<li class="active">
<a href="/contests/abc100/tasks"
><span
class="glyphicon glyphicon-tasks"
aria-hidden="true"
></span>
Tasks</a
>
</li>
<li>
<a href="/contests/abc100/clarifications"
><span
class="glyphicon glyphicon-question-sign"
aria-hidden="true"
></span>
Clarifications <span id="clar-badge" class="badge"></span
></a>
</li>
<li>
<a
class="dropdown-toggle"
data-toggle="dropdown"
href="#"
role="button"
aria-haspopup="true"
aria-expanded="false"
><span
class="glyphicon glyphicon-list"
aria-hidden="true"
></span>
Results<span class="caret"></span
></a>
<ul class="dropdown-menu">
<li>
<a href="/contests/abc100/submissions"
><span
class="glyphicon glyphicon-globe"
aria-hidden="true"
></span>
All Submissions</a
>
</li>
</ul>
</li>
<li>
<a href="/contests/abc100/standings"
><span
class="glyphicon glyphicon-sort-by-attributes-alt"
aria-hidden="true"
></span>
Standings</a
>
</li>
<li>
<a href="/contests/abc100/standings/virtual"
><span
class="glyphicon glyphicon-sort-by-attributes-alt"
aria-hidden="true"
></span>
Virtual Standings</a
>
</li>
<li>
<a href="/contests/abc100/editorial"
><span
class="glyphicon glyphicon-book"
aria-hidden="true"
></span>
Editorial</a
>
</li>
<li class="pull-right">
<a id="fix-cnvtb" href="javascript:void(0)"
><span
class="glyphicon glyphicon-pushpin"
aria-hidden="true"
></span
></a>
</li>
</ul>
</div>
<div class="col-sm-12">
<h2>Tasks</h2>
<hr />
<div class="panel panel-default table-responsive">
<table class="table table-bordered table-striped">
<thead>
<tr>
<th width="3%" class="text-center"></th>
<th>Task Name</th>
<th width="10%" class="text-right no-break">Time Limit</th>
<th width="10%" class="text-right no-break">
Memory Limit
</th>
</tr>
</thead>
<tbody>
<tr>
<td class="text-center no-break">
<a href="/contests/abc100/tasks/abc100_a">A</a>
</td>
<td>
<a href="/contests/abc100/tasks/abc100_a"
>Happy Birthday!</a
>
</td>
<td class="text-right">2 sec</td>
<td class="text-right">976 MiB</td>
</tr>
<tr>
<td class="text-center no-break">
<a href="/contests/abc100/tasks/abc100_b">B</a>
</td>
<td>
<a href="/contests/abc100/tasks/abc100_b"
>Ringo&#39;s Favorite Numbers</a
>
</td>
<td class="text-right">2 sec</td>
<td class="text-right">976 MiB</td>
</tr>
<tr>
<td class="text-center no-break">
<a href="/contests/abc100/tasks/abc100_c">C</a>
</td>
<td>
<a href="/contests/abc100/tasks/abc100_c">*3 or /2</a>
</td>
<td class="text-right">2 sec</td>
<td class="text-right">976 MiB</td>
</tr>
<tr>
<td class="text-center no-break">
<a href="/contests/abc100/tasks/abc100_d">D</a>
</td>
<td>
<a href="/contests/abc100/tasks/abc100_d"
>Patisserie ABC</a
>
</td>
<td class="text-right">2 sec</td>
<td class="text-right">976 MiB</td>
</tr>
</tbody>
</table>
</div>
<p class="btn-text-group">
<a class="btn-text" href="/contests/abc100/tasks_print"
>Tasks for printing</a
>
</p>
</div>
</div>
<hr />
<div
class="a2a_kit a2a_kit_size_20 a2a_default_style pull-right"
data-a2a-url="https://atcoder.jp/contests/abc100/tasks?lang=en"
data-a2a-title="Tasks - AtCoder Beginner Contest 100"
>
<a class="a2a_button_facebook"></a>
<a class="a2a_button_twitter"></a>
<a class="a2a_button_telegram"></a>
<a class="a2a_dd" href="https://www.addtoany.com/share"></a>
</div>
<script async src="//static.addtoany.com/menu/page.js"></script>
</div>
<hr />
</div>
<div class="container" style="margin-bottom: 80px">
<footer class="footer">
<ul>
<li><a href="/contests/abc100/rules">Rule</a></li>
<li><a href="/contests/abc100/glossary">Glossary</a></li>
</ul>
<ul>
<li><a href="/tos">Terms of service</a></li>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/personal">Information Protection Policy</a></li>
<li><a href="/company">Company</a></li>
<li><a href="/faq">FAQ</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
<div class="text-center">
<small id="copyright"
>Copyright Since 2012 &copy;<a href="http://atcoder.co.jp"
>AtCoder Inc.</a
>
All rights reserved.</small
>
</div>
</footer>
</div>
<p id="fixed-server-timer" class="contest-timer"></p>
<div id="scroll-page-top" style="display: none">
<span class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span> Page
Top
</div>
</body>
</html>

1902
tests/fixtures/atcoder_contests.html vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,885 @@
<!doctype html>
<html>
<head>
<title>A - Happy Birthday!</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="format-detection" content="telephone=no" />
<meta
name="google-site-verification"
content="nXGC_JxO0yoP1qBzMnYD_xgufO6leSLw1kyNo2HZltM"
/>
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-RC512FD18N"
></script>
<script>
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
gtag('set', 'user_properties', {
login_status: 'logged_out'
})
gtag('config', 'G-RC512FD18N')
</script>
<meta
name="description"
content="AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online."
/>
<meta name="author" content="AtCoder Inc." />
<meta property="og:site_name" content="AtCoder" />
<meta property="og:title" content="A - Happy Birthday!" />
<meta
property="og:description"
content="AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online."
/>
<meta property="og:type" content="website" />
<meta
property="og:url"
content="https://atcoder.jp/contests/abc100/tasks/abc100_a"
/>
<meta
property="og:image"
content="https://img.atcoder.jp/assets/atcoder.png"
/>
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@atcoder" />
<meta property="twitter:title" content="A - Happy Birthday!" />
<link
href="//fonts.googleapis.com/css?family=Lato:400,700"
rel="stylesheet"
type="text/css"
/>
<link
rel="stylesheet"
type="text/css"
href="//img.atcoder.jp/public/6372bb3/css/bootstrap.min.css"
/>
<link
rel="stylesheet"
type="text/css"
href="//img.atcoder.jp/public/6372bb3/css/base.css"
/>
<link
rel="shortcut icon"
type="image/png"
href="//img.atcoder.jp/assets/favicon.png"
/>
<link rel="apple-touch-icon" href="//img.atcoder.jp/assets/atcoder.png" />
<script src="//img.atcoder.jp/public/6372bb3/js/lib/jquery-1.9.1.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/lib/bootstrap.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/js.cookie.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/moment.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/moment_js-ja.js"></script>
<script>
var LANG = 'en'
var userScreenName = ''
var csrfToken = 'IHvNJXcA2LTFdl1UlqkJVdhRBg/6RvcHG1jLWfRmR44='
</script>
<script src="//img.atcoder.jp/public/6372bb3/js/utils.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/contest.js"></script>
<link
href="//img.atcoder.jp/public/6372bb3/css/contest.css"
rel="stylesheet"
/>
<script>
var contestScreenName = 'abc100'
var remainingText = 'Remaining Time'
var countDownText = 'Contest begins in'
var startTime = moment('2018-06-16T21:00:00+09:00')
var endTime = moment('2018-06-16T22:40:00+09:00')
</script>
<style></style>
<link
href="//img.atcoder.jp/public/6372bb3/css/cdn/select2.min.css"
rel="stylesheet"
/>
<link
href="//img.atcoder.jp/public/6372bb3/css/cdn/select2-bootstrap.min.css"
rel="stylesheet"
/>
<script src="//img.atcoder.jp/public/6372bb3/js/lib/select2.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/ace/ace.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/ace/ext-language_tools.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/run_prettify.js"></script>
<link
rel="stylesheet"
href="//img.atcoder.jp/public/6372bb3/css/cdn/katex.min.css"
/>
<script
defer
src="//img.atcoder.jp/public/6372bb3/js/cdn/katex.min.js"
></script>
<script
defer
src="//img.atcoder.jp/public/6372bb3/js/cdn/auto-render.min.js"
></script>
<script>
$(function () {
$('var').each(function () {
var html = $(this)
.html()
.replace(/<sub>/g, '_{')
.replace(/<\/sub>/g, '}')
$(this).html('\\(' + html + '\\)')
})
})
</script>
<script>
var katexOptions = {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: true }
],
ignoredTags: [
'script',
'noscript',
'style',
'textarea',
'code',
'option'
],
ignoredClasses: ['prettyprint', 'source-code-for-copy'],
throwOnError: false
}
document.addEventListener('DOMContentLoaded', function () {
renderMathInElement(document.body, katexOptions)
})
</script>
<script src="//img.atcoder.jp/public/6372bb3/js/base.js"></script>
</head>
<body>
<script type="text/javascript">
var __pParams = __pParams || []
__pParams.push({
client_id: '468',
c_1: 'atcodercontest',
c_2: 'ClientSite'
})
</script>
<script
type="text/javascript"
src="https://cdn.d2-apps.net/js/tr.js"
async
></script>
<div
id="modal-contest-start"
class="modal fade"
tabindex="-1"
role="dialog"
>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Contest started</h4>
</div>
<div class="modal-body">
<p>AtCoder Beginner Contest 100 has begun.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<div id="modal-contest-end" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Contest is over</h4>
</div>
<div class="modal-body">
<p>AtCoder Beginner Contest 100 has ended.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<div id="main-div" class="float-container">
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button
type="button"
class="navbar-toggle collapsed"
data-toggle="collapse"
data-target="#navbar-collapse"
aria-expanded="false"
>
<span class="icon-bar"></span><span class="icon-bar"></span
><span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/home"></a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav">
<li>
<a class="contest-title" href="/contests/abc100"
>AtCoder Beginner Contest 100</a
>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a
class="dropdown-toggle"
data-toggle="dropdown"
href="#"
role="button"
aria-haspopup="true"
aria-expanded="false"
>
<img src="//img.atcoder.jp/assets/top/img/flag-lang/en.png" />
English <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<a href="/contests/abc100/tasks/abc100_a?lang=ja"
><img
src="//img.atcoder.jp/assets/top/img/flag-lang/ja.png"
/>
日本語</a
>
</li>
<li>
<a href="/contests/abc100/tasks/abc100_a?lang=en"
><img
src="//img.atcoder.jp/assets/top/img/flag-lang/en.png"
/>
English</a
>
</li>
</ul>
</li>
<li>
<a
href="/register?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks%2Fabc100_a"
>Sign Up</a
>
</li>
<li>
<a
href="/login?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks%2Fabc100_a"
>Sign In</a
>
</li>
</ul>
</div>
</div>
</nav>
<form
method="POST"
name="form_logout"
action="/logout?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks%2Fabc100_a"
>
<input
type="hidden"
name="csrf_token"
value="IHvNJXcA2LTFdl1UlqkJVdhRBg/6RvcHG1jLWfRmR44="
/>
</form>
<div id="main-container" class="container" style="padding-top: 50px">
<div class="row">
<div id="contest-nav-tabs" class="col-sm-12 mb-2 cnvtb-fixed">
<div>
<small class="contest-duration">
Contest Duration:
<a
href="http://www.timeanddate.com/worldclock/fixedtime.html?iso=20180616T2100&p1=248"
target="blank"
><time class="fixtime fixtime-full"
>2018-06-16 21:00:00+0900</time
></a
>
-
<a
href="http://www.timeanddate.com/worldclock/fixedtime.html?iso=20180616T2240&p1=248"
target="blank"
><time class="fixtime fixtime-full"
>2018-06-16 22:40:00+0900</time
></a
>
(local time) (100 minutes)
</small>
<small class="back-to-home pull-right"
><a href="/home">Back to Home</a></small
>
</div>
<ul class="nav nav-tabs">
<li>
<a href="/contests/abc100"
><span
class="glyphicon glyphicon-home"
aria-hidden="true"
></span>
Top</a
>
</li>
<li class="active">
<a href="/contests/abc100/tasks"
><span
class="glyphicon glyphicon-tasks"
aria-hidden="true"
></span>
Tasks</a
>
</li>
<li>
<a href="/contests/abc100/clarifications"
><span
class="glyphicon glyphicon-question-sign"
aria-hidden="true"
></span>
Clarifications <span id="clar-badge" class="badge"></span
></a>
</li>
<li>
<a
class="dropdown-toggle"
data-toggle="dropdown"
href="#"
role="button"
aria-haspopup="true"
aria-expanded="false"
><span
class="glyphicon glyphicon-list"
aria-hidden="true"
></span>
Results<span class="caret"></span
></a>
<ul class="dropdown-menu">
<li>
<a href="/contests/abc100/submissions"
><span
class="glyphicon glyphicon-globe"
aria-hidden="true"
></span>
All Submissions</a
>
</li>
</ul>
</li>
<li>
<a href="/contests/abc100/standings"
><span
class="glyphicon glyphicon-sort-by-attributes-alt"
aria-hidden="true"
></span>
Standings</a
>
</li>
<li>
<a href="/contests/abc100/standings/virtual"
><span
class="glyphicon glyphicon-sort-by-attributes-alt"
aria-hidden="true"
></span>
Virtual Standings</a
>
</li>
<li>
<a href="/contests/abc100/editorial"
><span
class="glyphicon glyphicon-book"
aria-hidden="true"
></span>
Editorial</a
>
</li>
<li class="pull-right">
<a id="fix-cnvtb" href="javascript:void(0)"
><span
class="glyphicon glyphicon-pushpin"
aria-hidden="true"
></span
></a>
</li>
</ul>
</div>
<div class="col-sm-12">
<span class="h2">
A - Happy Birthday!
<a
class="btn btn-default btn-sm"
href="/contests/abc100/tasks/abc100_a/editorial"
>Editorial</a
>
</span>
<span id="task-lang-btn" class="pull-right"
><span data-lang="ja"
><img src="//img.atcoder.jp/assets/top/img/flag-lang/ja.png"
/></span>
/
<span data-lang="en"
><img
src="//img.atcoder.jp/assets/top/img/flag-lang/en.png" /></span
></span>
<script>
$(function () {
var ts = $('#task-statement span.lang')
if (ts.children('span').size() <= 1) {
$('#task-lang-btn').hide()
ts.children('span').show()
return
}
var REMEMBER_LB = 5
var LS_KEY = 'task_lang'
var taskLang = getLS(LS_KEY) || ''
function isTaskLangSet(taskLang) {
return taskLang === 'ja' || taskLang === 'en'
}
if (isTaskLangSet(taskLang)) {
const params = new URLSearchParams(window.location.search)
if (params.get('lang')) {
setLS(LS_KEY, REMEMBER_LB)
taskLang = LANG
}
} else {
taskLang = LANG
}
ts.children('span.lang-' + taskLang).show()
$('#task-lang-btn span').click(function () {
var l = $(this).data('lang')
ts.children('span').hide()
ts.children('span.lang-' + l).show()
taskLang = getLS(LS_KEY) || ''
let changeTimes = 0
if (isTaskLangSet(taskLang)) {
changeTimes = REMEMBER_LB
} else {
changeTimes = parseInt(taskLang, 10)
if (isNaN(changeTimes)) changeTimes = 0
changeTimes++
}
if (changeTimes < REMEMBER_LB) setLS(LS_KEY, changeTimes)
else setLS(LS_KEY, l)
})
})
</script>
<hr />
<p>Time Limit: 2 sec / Memory Limit: 976 MiB</p>
<div id="task-statement">
<span class="lang">
<span class="lang-ja">
<p>配点: <var>100</var></p>
<div class="part">
<section>
<h3>問題文</h3>
<p>
もうすぐ E869120 君と square1001 君の
<var>16</var> 才の誕生日が来る.<br />
そこで, AtCoder 王国の高橋君は, 円形のケーキ
<var>1</var> 個に放射状に切れ目を入れ
<var>16</var> 等分したものを, 彼らにプレゼントした.
</p>
<p>
E869120 君はそのうち <var>A</var> 切れ、square1001 君は
<var>B</var> 切れを食べようとした.<br />
しかし, ケーキと一緒についていた紙を見ると,
「同じ人が隣り合う
<var>2</var>
切れのケーキを両方取ってはならない」と書かれていた.
</p>
<p>
さて、彼らは紙に書かれたことを守って、<var>2</var>
人とも食べたい数のケーキを取ることができるだろうか?
</p>
</section>
</div>
<div class="part">
<section>
<h3>制約</h3>
<ul>
<li>
<var>A, B</var><var>1</var> 以上
<var>16</var> 以下の整数
</li>
<li><var>A+B</var><var>16</var> 以下である.</li>
</ul>
</section>
</div>
<hr />
<div class="io-style">
<div class="part">
<section>
<h3>入力</h3>
<p>入力は以下の形式で標準入力から与えられる.</p>
<pre><var>A</var> <var>B</var>
</pre>
</section>
</div>
<div class="part">
<section>
<h3>出力</h3>
<p>
紙に書かれたことを守って, E869120 君と square1001
君両方が, 食べたい数のケーキを取ることができるならば
<code>Yay!</code>, そうでなければ
<code>:(</code> と出力しなさい.
</p>
</section>
</div>
</div>
<hr />
<div class="part">
<section>
<h3>入力例 1</h3>
<pre>
5 4
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>出力例 1</h3>
<pre>
Yay!
</pre
>
<p>
下の図のようにケーキを取れば、<var>2</var>
人とも目標を達成することができる.<br />
<img
alt=" "
src="https://img.atcoder.jp/abc100/e87fa456a900ac9ae36671ae8bd5eeea.png"
/>
</p>
</section>
</div>
<hr />
<div class="part">
<section>
<h3>入力例 2</h3>
<pre>
8 8
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>出力例 2</h3>
<pre>
Yay!
</pre
>
<p>
下の図のようにケーキを取れば、<var>2</var>
人とも目標を達成することができる.<br />
<img
alt=" "
src="https://img.atcoder.jp/abc100/a7989ac033e6ba86e14078864c20d9c5.png"
/>
</p>
</section>
</div>
<hr />
<div class="part">
<section>
<h3>入力例 3</h3>
<pre>
11 4
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>出力例 3</h3>
<pre>
:(
</pre
>
<p>
この場合, 残念ながら目標を達成する方法は
<var>1</var> つもない.
</p>
</section>
</div>
</span>
<span class="lang-en">
<p>Score: <var>100</var> points</p>
<div class="part">
<section>
<h3>Problem Statement</h3>
<p>
E869120's and square1001's <var>16</var>-th birthday is
coming soon.<br />
Takahashi from AtCoder Kingdom gave them a round cake
cut into <var>16</var> equal fan-shaped pieces.
</p>
<p>
E869120 and square1001 were just about to eat
<var>A</var> and <var>B</var> of those pieces,
respectively,<br />
when they found a note attached to the cake saying that
"the same person should not take two adjacent pieces of
cake".
</p>
<p>
Can both of them obey the instruction in the note and
take desired numbers of pieces of cake?
</p>
</section>
</div>
<div class="part">
<section>
<h3>Constraints</h3>
<ul>
<li>
<var>A</var> and <var>B</var> are integers between
<var>1</var> and <var>16</var> (inclusive).
</li>
<li><var>A+B</var> is at most <var>16</var>.</li>
</ul>
</section>
</div>
<hr />
<div class="io-style">
<div class="part">
<section>
<h3>Input</h3>
<p>
Input is given from Standard Input in the following
format:
</p>
<pre><var>A</var> <var>B</var>
</pre>
</section>
</div>
<div class="part">
<section>
<h3>Output</h3>
<p>
If both E869120 and square1001 can obey the
instruction in the note and take desired numbers of
pieces of cake, print <code>Yay!</code>; otherwise,
print <code>:(</code>.
</p>
</section>
</div>
</div>
<hr />
<div class="part">
<section>
<h3>Sample Input 1</h3>
<pre>
5 4
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>Sample Output 1</h3>
<pre>
Yay!
</pre
>
<p>
Both of them can take desired number of pieces as
follows:
<img
alt=" "
src="https://img.atcoder.jp/abc100/e87fa456a900ac9ae36671ae8bd5eeea.png"
/>
</p>
</section>
</div>
<hr />
<div class="part">
<section>
<h3>Sample Input 2</h3>
<pre>
8 8
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>Sample Output 2</h3>
<pre>
Yay!
</pre
>
<p>
Both of them can take desired number of pieces as
follows:
<img
alt=" "
src="https://img.atcoder.jp/abc100/a7989ac033e6ba86e14078864c20d9c5.png"
/>
</p>
</section>
</div>
<hr />
<div class="part">
<section>
<h3>Sample Input 3</h3>
<pre>
11 4
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>Sample Output 3</h3>
<pre>
:(
</pre
>
<p>
In this case, there is no way for them to take desired
number of pieces, unfortunately.
</p>
</section>
</div>
</span>
</span>
</div>
</div>
</div>
<hr />
<div
class="a2a_kit a2a_kit_size_20 a2a_default_style pull-right"
data-a2a-url="https://atcoder.jp/contests/abc100/tasks/abc100_a?lang=en"
data-a2a-title="A - Happy Birthday!"
>
<a class="a2a_button_facebook"></a>
<a class="a2a_button_twitter"></a>
<a class="a2a_button_telegram"></a>
<a class="a2a_dd" href="https://www.addtoany.com/share"></a>
</div>
<script async src="//static.addtoany.com/menu/page.js"></script>
</div>
<hr />
</div>
<div class="container" style="margin-bottom: 80px">
<footer class="footer">
<ul>
<li><a href="/contests/abc100/rules">Rule</a></li>
<li><a href="/contests/abc100/glossary">Glossary</a></li>
</ul>
<ul>
<li><a href="/tos">Terms of service</a></li>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/personal">Information Protection Policy</a></li>
<li><a href="/company">Company</a></li>
<li><a href="/faq">FAQ</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
<div class="text-center">
<small id="copyright"
>Copyright Since 2012 &copy;<a href="http://atcoder.co.jp"
>AtCoder Inc.</a
>
All rights reserved.</small
>
</div>
</footer>
</div>
<p id="fixed-server-timer" class="contest-timer"></p>
<div id="scroll-page-top" style="display: none">
<span class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span> Page
Top
</div>
</body>
</html>

View file

@ -0,0 +1,887 @@
<!doctype html>
<html>
<head>
<title>B - Ringo&#39;s Favorite Numbers</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<meta name="format-detection" content="telephone=no" />
<meta
name="google-site-verification"
content="nXGC_JxO0yoP1qBzMnYD_xgufO6leSLw1kyNo2HZltM"
/>
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-RC512FD18N"
></script>
<script>
window.dataLayer = window.dataLayer || []
function gtag() {
dataLayer.push(arguments)
}
gtag('js', new Date())
gtag('set', 'user_properties', {
login_status: 'logged_out'
})
gtag('config', 'G-RC512FD18N')
</script>
<meta
name="description"
content="AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online."
/>
<meta name="author" content="AtCoder Inc." />
<meta property="og:site_name" content="AtCoder" />
<meta property="og:title" content="B - Ringo&#39;s Favorite Numbers" />
<meta
property="og:description"
content="AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online."
/>
<meta property="og:type" content="website" />
<meta
property="og:url"
content="https://atcoder.jp/contests/abc100/tasks/abc100_b"
/>
<meta
property="og:image"
content="https://img.atcoder.jp/assets/atcoder.png"
/>
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@atcoder" />
<meta property="twitter:title" content="B - Ringo&#39;s Favorite Numbers" />
<link
href="//fonts.googleapis.com/css?family=Lato:400,700"
rel="stylesheet"
type="text/css"
/>
<link
rel="stylesheet"
type="text/css"
href="//img.atcoder.jp/public/6372bb3/css/bootstrap.min.css"
/>
<link
rel="stylesheet"
type="text/css"
href="//img.atcoder.jp/public/6372bb3/css/base.css"
/>
<link
rel="shortcut icon"
type="image/png"
href="//img.atcoder.jp/assets/favicon.png"
/>
<link rel="apple-touch-icon" href="//img.atcoder.jp/assets/atcoder.png" />
<script src="//img.atcoder.jp/public/6372bb3/js/lib/jquery-1.9.1.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/lib/bootstrap.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/js.cookie.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/moment.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/moment_js-ja.js"></script>
<script>
var LANG = 'en'
var userScreenName = ''
var csrfToken = 'o2ayF7Lv34NtRIbcb0Ixvog2CrZtUEVLZNKuIuHQKH8='
</script>
<script src="//img.atcoder.jp/public/6372bb3/js/utils.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/contest.js"></script>
<link
href="//img.atcoder.jp/public/6372bb3/css/contest.css"
rel="stylesheet"
/>
<script>
var contestScreenName = 'abc100'
var remainingText = 'Remaining Time'
var countDownText = 'Contest begins in'
var startTime = moment('2018-06-16T21:00:00+09:00')
var endTime = moment('2018-06-16T22:40:00+09:00')
</script>
<style></style>
<link
href="//img.atcoder.jp/public/6372bb3/css/cdn/select2.min.css"
rel="stylesheet"
/>
<link
href="//img.atcoder.jp/public/6372bb3/css/cdn/select2-bootstrap.min.css"
rel="stylesheet"
/>
<script src="//img.atcoder.jp/public/6372bb3/js/lib/select2.min.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/ace/ace.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/ace/ext-language_tools.js"></script>
<script src="//img.atcoder.jp/public/6372bb3/js/cdn/run_prettify.js"></script>
<link
rel="stylesheet"
href="//img.atcoder.jp/public/6372bb3/css/cdn/katex.min.css"
/>
<script
defer
src="//img.atcoder.jp/public/6372bb3/js/cdn/katex.min.js"
></script>
<script
defer
src="//img.atcoder.jp/public/6372bb3/js/cdn/auto-render.min.js"
></script>
<script>
$(function () {
$('var').each(function () {
var html = $(this)
.html()
.replace(/<sub>/g, '_{')
.replace(/<\/sub>/g, '}')
$(this).html('\\(' + html + '\\)')
})
})
</script>
<script>
var katexOptions = {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: true }
],
ignoredTags: [
'script',
'noscript',
'style',
'textarea',
'code',
'option'
],
ignoredClasses: ['prettyprint', 'source-code-for-copy'],
throwOnError: false
}
document.addEventListener('DOMContentLoaded', function () {
renderMathInElement(document.body, katexOptions)
})
</script>
<script src="//img.atcoder.jp/public/6372bb3/js/base.js"></script>
</head>
<body>
<script type="text/javascript">
var __pParams = __pParams || []
__pParams.push({
client_id: '468',
c_1: 'atcodercontest',
c_2: 'ClientSite'
})
</script>
<script
type="text/javascript"
src="https://cdn.d2-apps.net/js/tr.js"
async
></script>
<div
id="modal-contest-start"
class="modal fade"
tabindex="-1"
role="dialog"
>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Contest started</h4>
</div>
<div class="modal-body">
<p>AtCoder Beginner Contest 100 has begun.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<div id="modal-contest-end" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
>
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Contest is over</h4>
</div>
<div class="modal-body">
<p>AtCoder Beginner Contest 100 has ended.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
Close
</button>
</div>
</div>
</div>
</div>
<div id="main-div" class="float-container">
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button
type="button"
class="navbar-toggle collapsed"
data-toggle="collapse"
data-target="#navbar-collapse"
aria-expanded="false"
>
<span class="icon-bar"></span><span class="icon-bar"></span
><span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/home"></a>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse">
<ul class="nav navbar-nav">
<li>
<a class="contest-title" href="/contests/abc100"
>AtCoder Beginner Contest 100</a
>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a
class="dropdown-toggle"
data-toggle="dropdown"
href="#"
role="button"
aria-haspopup="true"
aria-expanded="false"
>
<img src="//img.atcoder.jp/assets/top/img/flag-lang/en.png" />
English <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li>
<a href="/contests/abc100/tasks/abc100_b?lang=ja"
><img
src="//img.atcoder.jp/assets/top/img/flag-lang/ja.png"
/>
日本語</a
>
</li>
<li>
<a href="/contests/abc100/tasks/abc100_b?lang=en"
><img
src="//img.atcoder.jp/assets/top/img/flag-lang/en.png"
/>
English</a
>
</li>
</ul>
</li>
<li>
<a
href="/register?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks%2Fabc100_b"
>Sign Up</a
>
</li>
<li>
<a
href="/login?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks%2Fabc100_b"
>Sign In</a
>
</li>
</ul>
</div>
</div>
</nav>
<form
method="POST"
name="form_logout"
action="/logout?continue=https%3A%2F%2Fatcoder.jp%2Fcontests%2Fabc100%2Ftasks%2Fabc100_b"
>
<input
type="hidden"
name="csrf_token"
value="o2ayF7Lv34NtRIbcb0Ixvog2CrZtUEVLZNKuIuHQKH8="
/>
</form>
<div id="main-container" class="container" style="padding-top: 50px">
<div class="row">
<div id="contest-nav-tabs" class="col-sm-12 mb-2 cnvtb-fixed">
<div>
<small class="contest-duration">
Contest Duration:
<a
href="http://www.timeanddate.com/worldclock/fixedtime.html?iso=20180616T2100&p1=248"
target="blank"
><time class="fixtime fixtime-full"
>2018-06-16 21:00:00+0900</time
></a
>
-
<a
href="http://www.timeanddate.com/worldclock/fixedtime.html?iso=20180616T2240&p1=248"
target="blank"
><time class="fixtime fixtime-full"
>2018-06-16 22:40:00+0900</time
></a
>
(local time) (100 minutes)
</small>
<small class="back-to-home pull-right"
><a href="/home">Back to Home</a></small
>
</div>
<ul class="nav nav-tabs">
<li>
<a href="/contests/abc100"
><span
class="glyphicon glyphicon-home"
aria-hidden="true"
></span>
Top</a
>
</li>
<li class="active">
<a href="/contests/abc100/tasks"
><span
class="glyphicon glyphicon-tasks"
aria-hidden="true"
></span>
Tasks</a
>
</li>
<li>
<a href="/contests/abc100/clarifications"
><span
class="glyphicon glyphicon-question-sign"
aria-hidden="true"
></span>
Clarifications <span id="clar-badge" class="badge"></span
></a>
</li>
<li>
<a
class="dropdown-toggle"
data-toggle="dropdown"
href="#"
role="button"
aria-haspopup="true"
aria-expanded="false"
><span
class="glyphicon glyphicon-list"
aria-hidden="true"
></span>
Results<span class="caret"></span
></a>
<ul class="dropdown-menu">
<li>
<a href="/contests/abc100/submissions"
><span
class="glyphicon glyphicon-globe"
aria-hidden="true"
></span>
All Submissions</a
>
</li>
</ul>
</li>
<li>
<a href="/contests/abc100/standings"
><span
class="glyphicon glyphicon-sort-by-attributes-alt"
aria-hidden="true"
></span>
Standings</a
>
</li>
<li>
<a href="/contests/abc100/standings/virtual"
><span
class="glyphicon glyphicon-sort-by-attributes-alt"
aria-hidden="true"
></span>
Virtual Standings</a
>
</li>
<li>
<a href="/contests/abc100/editorial"
><span
class="glyphicon glyphicon-book"
aria-hidden="true"
></span>
Editorial</a
>
</li>
<li class="pull-right">
<a id="fix-cnvtb" href="javascript:void(0)"
><span
class="glyphicon glyphicon-pushpin"
aria-hidden="true"
></span
></a>
</li>
</ul>
</div>
<div class="col-sm-12">
<span class="h2">
B - Ringo&#39;s Favorite Numbers
<a
class="btn btn-default btn-sm"
href="/contests/abc100/tasks/abc100_b/editorial"
>Editorial</a
>
</span>
<span id="task-lang-btn" class="pull-right"
><span data-lang="ja"
><img src="//img.atcoder.jp/assets/top/img/flag-lang/ja.png"
/></span>
/
<span data-lang="en"
><img
src="//img.atcoder.jp/assets/top/img/flag-lang/en.png" /></span
></span>
<script>
$(function () {
var ts = $('#task-statement span.lang')
if (ts.children('span').size() <= 1) {
$('#task-lang-btn').hide()
ts.children('span').show()
return
}
var REMEMBER_LB = 5
var LS_KEY = 'task_lang'
var taskLang = getLS(LS_KEY) || ''
function isTaskLangSet(taskLang) {
return taskLang === 'ja' || taskLang === 'en'
}
if (isTaskLangSet(taskLang)) {
const params = new URLSearchParams(window.location.search)
if (params.get('lang')) {
setLS(LS_KEY, REMEMBER_LB)
taskLang = LANG
}
} else {
taskLang = LANG
}
ts.children('span.lang-' + taskLang).show()
$('#task-lang-btn span').click(function () {
var l = $(this).data('lang')
ts.children('span').hide()
ts.children('span.lang-' + l).show()
taskLang = getLS(LS_KEY) || ''
let changeTimes = 0
if (isTaskLangSet(taskLang)) {
changeTimes = REMEMBER_LB
} else {
changeTimes = parseInt(taskLang, 10)
if (isNaN(changeTimes)) changeTimes = 0
changeTimes++
}
if (changeTimes < REMEMBER_LB) setLS(LS_KEY, changeTimes)
else setLS(LS_KEY, l)
})
})
</script>
<hr />
<p>Time Limit: 2 sec / Memory Limit: 976 MiB</p>
<div id="task-statement">
<span class="lang">
<span class="lang-ja">
<p>配点: <var>200</var></p>
<div class="part">
<section>
<h3>問題文</h3>
<p>
今日は, 記念すべき AtCoder Beginner Contest 100
が開催される. そのため, 高橋君はりんごさんに,
ある整数をプレゼントしようと思った.<br />
今日のコンテストは「AtCoder Beginner Contest
100」なので, りんごさんは <var>100</var>
<strong>ちょうど</strong>
<var>D</var>
回割りきれる正の整数をプレゼントされると喜ぶ.
</p>
<p>
さて, りんごさんがプレゼントされると喜ぶような整数のうち
<var>N</var> 番目に小さいものを求めなさい.
</p>
</section>
</div>
<div class="part">
<section>
<h3>制約</h3>
<ul>
<li>
<var>D</var><var>0, 1, 2</var> のいずれかである
</li>
<li>
<var>N</var><var>1</var> 以上
<var>100</var> 以下の整数
</li>
</ul>
</section>
</div>
<hr />
<div class="io-style">
<div class="part">
<section>
<h3>入力</h3>
<p>入力は以下の形式で標準入力から与えられる.</p>
<pre><var>D</var> <var>N</var>
</pre>
</section>
</div>
<div class="part">
<section>
<h3>出力</h3>
<p>
<var>100</var> でちょうど
<var>D</var> 回割りきれる正の整数の中で
<var>N</var> 番目に小さいものを出力しなさい.
</p>
</section>
</div>
</div>
<hr />
<div class="part">
<section>
<h3>入力例 1</h3>
<pre>
0 5
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>出力例 1</h3>
<pre>
5
</pre
>
<p>
<var>100</var> でちょうど
<var>0</var> 回割り切れる(すなわち,
<var>100</var> で割り切れない)整数は, <var>1</var>,
<var>2</var>, <var>3</var>, <var>4</var>, <var>5</var>,
<var>6</var>, <var>7</var>, ... と続く.<br />
よって, <var>5</var> 番目に小さいりんごさんが喜ぶ整数は
<var>5</var> である.
</p>
</section>
</div>
<hr />
<div class="part">
<section>
<h3>入力例 2</h3>
<pre>
1 11
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>出力例 2</h3>
<pre>
1100
</pre
>
<p>
<var>100</var> でちょうど
<var>1</var> 回割り切れる整数は, <var>100</var>,
<var>200</var>, <var>300</var>, <var>400</var>,
<var>500</var>, <var>600</var>, <var>700</var>,
<var>800</var>, <var>900</var>, <var>1 \ 000</var>,
<var>1 \ 100</var>, ... と続く.<br />
よって, 求めたい整数は <var>1 \ 100</var> である.
</p>
</section>
</div>
<hr />
<div class="part">
<section>
<h3>入力例 3</h3>
<pre>
2 85
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>出力例 3</h3>
<pre>
850000
</pre
>
<p>
<var>100</var> でちょうど
<var>2</var> 回割り切れる整数は, <var>10 \ 000</var>,
<var>20 \ 000</var>, <var>30 \ 000</var>, ... と続く.<br />
よって, 求めたい整数は <var>850 \ 000</var> である.
</p>
</section>
</div>
</span>
<span class="lang-en">
<p>Score: <var>200</var> points</p>
<div class="part">
<section>
<h3>Problem Statement</h3>
<p>
Today, the memorable AtCoder Beginner Contest 100 takes
place. On this occasion, Takahashi would like to give an
integer to Ringo.<br />
As the name of the contest is AtCoder Beginner Contest
100, Ringo would be happy if he is given a positive
integer that can be divided by <var>100</var>
<strong>exactly</strong> <var>D</var> times.
</p>
<p>
Find the <var>N</var>-th smallest integer that would
make Ringo happy.
</p>
</section>
</div>
<div class="part">
<section>
<h3>Constraints</h3>
<ul>
<li>
<var>D</var> is <var>0</var>, <var>1</var> or
<var>2</var>.
</li>
<li>
<var>N</var> is an integer between <var>1</var> and
<var>100</var> (inclusive).
</li>
</ul>
</section>
</div>
<hr />
<div class="io-style">
<div class="part">
<section>
<h3>Input</h3>
<p>
Input is given from Standard Input in the following
format:
</p>
<pre><var>D</var> <var>N</var>
</pre>
</section>
</div>
<div class="part">
<section>
<h3>Output</h3>
<p>
Print the <var>N</var>-th smallest integer that can be
divided by <var>100</var> exactly <var>D</var> times.
</p>
</section>
</div>
</div>
<hr />
<div class="part">
<section>
<h3>Sample Input 1</h3>
<pre>
0 5
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>Sample Output 1</h3>
<pre>
5
</pre
>
<p>
The integers that can be divided by
<var>100</var> exactly <var>0</var> times (that is, not
divisible by <var>100</var>) are as follows:
<var>1</var>, <var>2</var>, <var>3</var>, <var>4</var>,
<var>5</var>, <var>6</var>, <var>7</var>, ...<br />
Thus, the <var>5</var>-th smallest integer that would
make Ringo happy is <var>5</var>.
</p>
</section>
</div>
<hr />
<div class="part">
<section>
<h3>Sample Input 2</h3>
<pre>
1 11
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>Sample Output 2</h3>
<pre>
1100
</pre
>
<p>
The integers that can be divided by
<var>100</var> exactly once are as follows:
<var>100</var>, <var>200</var>, <var>300</var>,
<var>400</var>, <var>500</var>, <var>600</var>,
<var>700</var>, <var>800</var>, <var>900</var>,
<var>1 \ 000</var>, <var>1 \ 100</var>, ...<br />
Thus, the integer we are seeking is <var>1 \ 100</var>.
</p>
</section>
</div>
<hr />
<div class="part">
<section>
<h3>Sample Input 3</h3>
<pre>
2 85
</pre
>
</section>
</div>
<div class="part">
<section>
<h3>Sample Output 3</h3>
<pre>
850000
</pre
>
<p>
The integers that can be divided by
<var>100</var> exactly twice are as follows:
<var>10 \ 000</var>, <var>20 \ 000</var>,
<var>30 \ 000</var>, ...<br />
Thus, the integer we are seeking is
<var>850 \ 000</var>.
</p>
</section>
</div>
</span>
</span>
</div>
</div>
</div>
<hr />
<div
class="a2a_kit a2a_kit_size_20 a2a_default_style pull-right"
data-a2a-url="https://atcoder.jp/contests/abc100/tasks/abc100_b?lang=en"
data-a2a-title="B - Ringo&#39;s Favorite Numbers"
>
<a class="a2a_button_facebook"></a>
<a class="a2a_button_twitter"></a>
<a class="a2a_button_telegram"></a>
<a class="a2a_dd" href="https://www.addtoany.com/share"></a>
</div>
<script async src="//static.addtoany.com/menu/page.js"></script>
</div>
<hr />
</div>
<div class="container" style="margin-bottom: 80px">
<footer class="footer">
<ul>
<li><a href="/contests/abc100/rules">Rule</a></li>
<li><a href="/contests/abc100/glossary">Glossary</a></li>
</ul>
<ul>
<li><a href="/tos">Terms of service</a></li>
<li><a href="/privacy">Privacy Policy</a></li>
<li><a href="/personal">Information Protection Policy</a></li>
<li><a href="/company">Company</a></li>
<li><a href="/faq">FAQ</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
<div class="text-center">
<small id="copyright"
>Copyright Since 2012 &copy;<a href="http://atcoder.co.jp"
>AtCoder Inc.</a
>
All rights reserved.</small
>
</div>
</footer>
</div>
<p id="fixed-server-timer" class="contest-timer"></p>
<div id="scroll-page-top" style="display: none">
<span class="glyphicon glyphicon-arrow-up" aria-hidden="true"></span> Page
Top
</div>
</body>
</html>

8210
tests/fixtures/codeforces_1550_A.html vendored Normal file

File diff suppressed because it is too large Load diff

4724
tests/fixtures/codeforces_1550_B.html vendored Normal file

File diff suppressed because it is too large Load diff

10
tests/fixtures/codeforces_contests.html vendored Normal file

File diff suppressed because one or more lines are too long

43
tests/fixtures/cses_contests.html vendored Normal file

File diff suppressed because one or more lines are too long

156
tests/fixtures/cses_task_1068.html vendored Normal file
View file

@ -0,0 +1,156 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet " type="text/css" href="/cses.css?0" id="styles" />
<link
rel="stylesheet alternate"
type="text/css"
href="/cses-dark.css?0"
id="styles-dark"
/>
<meta name="theme-color" content="white" id="theme-color" />
<script type="application/json" id="darkmode-enabled">
false
</script>
<script src="/ui.js"></script>
<link
rel="stylesheet"
type="text/css"
href="/lib/fontawesome/css/all.min.css"
/>
</head>
<body class="with-sidebar">
<div class="header">
<div>
<a href="/" class="logo"><img src="/logo.png?1" alt="CSES" /></a>
<a
class="menu-toggle"
onclick="document.body.classList.toggle('menu-open');"
>
<i class="fas fa-bars"></i>
</a>
<div class="controls">
<a class="account" href="/login">Login</a>
<span>&mdash;</span>
<a
href="/darkmode"
title="Toggle dark mode"
onclick="return toggle_theme()"
><i aria-label="Dark mode" class="fas fa-adjust"></i
><span>Dark mode</span></a
>
</div>
</div>
</div>
<div class="skeleton">
<div class="navigation">
<div class="title-block">
<h3><a href="/problemset/list/">CSES Problem Set</a></h3>
<h1>Weird Algorithm</h1>
<ul class="nav">
<li><a href="/problemset/task/1068/" class="current">Task</a></li>
<li><a href="/problemset/stats/1068/">Statistics</a></li>
</ul>
</div>
</div>
<div class="content-wrapper">
<div class="content">
<title>CSES - Weird Algorithm</title
><link rel="stylesheet" href="/lib/katex/katex.min.css" />
<script defer src="/lib/katex/katex.min.js"></script>
<script defer src="/lib/katex/contrib/copy-tex.min.js"></script>
<script
defer
src="/lib/google-code-prettify/run_prettify.js"
></script>
<script>
addEventListener('DOMContentLoaded', function (e) {
const mathElements = document.getElementsByClassName('math')
const macros = {}
for (let element of mathElements) {
katex.render(element.textContent, element, {
displayMode: element.classList.contains('math-display'),
throwOnError: false,
globalGroup: true,
macros
})
}
})
</script>
<ul class="task-constraints">
<li><b>Time limit:</b> 1.00 s</li>
<li><b>Memory limit:</b> 512 MB</li>
</ul>
<div class="md">
<p>
Consider an algorithm that takes as input a positive integer
<span class="math math-inline">n</span>. If
<span class="math math-inline">n</span> is even, the algorithm
divides it by two, and if
<span class="math math-inline">n</span> is odd, the algorithm
multiplies it by three and adds one. The algorithm repeats this,
until <span class="math math-inline">n</span> is one. For example,
the sequence for <span class="math math-inline">n=3</span> is as
follows:
<span class="math math-display">
3 \rightarrow 10 \rightarrow 5 \rightarrow 16 \rightarrow 8
\rightarrow 4 \rightarrow 2 \rightarrow 1</span
>
Your task is to simulate the execution of the algorithm for a
given value of <span class="math math-inline">n</span>.
</p>
<h1 id="input">Input</h1>
<p>
The only input line contains an integer
<span class="math math-inline">n</span>.
</p>
<h1 id="output">Output</h1>
<p>
Print a line that contains all values of
<span class="math math-inline">n</span> during the algorithm.
</p>
<h1 id="constraints">Constraints</h1>
<ul>
<li><span class="math math-inline">1 \le n \le 10^6</span></li>
</ul>
<h1 id="example">Example</h1>
<p>Input:</p>
<pre>
3
</pre
>
<p>Output:</p>
<pre>
3 10 5 16 8 4 2 1
</pre
>
</div>
</div>
<div class="nav sidebar">
<h4>Introductory Problems</h4>
<a class="current" href="/problemset/task/1068"
>Weird Algorithm<span class="task-score icon"></span></a
><a href="/problemset/task/1083"
>Missing Number<span class="task-score icon"></span></a
><a href="/problemset/task/1069"
>Repetitions<span class="task-score icon"></span></a
><a href="/problemset/task/1094"
>Increasing Array<span class="task-score icon"></span></a
><a href="/problemset/task/1070"
>Permutations<span class="task-score icon"></span></a
><a href="/problemset/task/1071"
>Number Spiral<span class="task-score icon"></span></a
><a href="/problemset/task/1072"
>Two Knights<span class="task-score icon"></span></a
><a href="/problemset/task/1092"
>Two Sets<span class="task-score icon"></span></a
>...
<hr />
</div>
</div>
</div>
</body>
</html>

150
tests/fixtures/cses_task_1621.html vendored Normal file
View file

@ -0,0 +1,150 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet " type="text/css" href="/cses.css?0" id="styles" />
<link
rel="stylesheet alternate"
type="text/css"
href="/cses-dark.css?0"
id="styles-dark"
/>
<meta name="theme-color" content="white" id="theme-color" />
<script type="application/json" id="darkmode-enabled">
false
</script>
<script src="/ui.js"></script>
<link
rel="stylesheet"
type="text/css"
href="/lib/fontawesome/css/all.min.css"
/>
</head>
<body class="with-sidebar">
<div class="header">
<div>
<a href="/" class="logo"><img src="/logo.png?1" alt="CSES" /></a>
<a
class="menu-toggle"
onclick="document.body.classList.toggle('menu-open');"
>
<i class="fas fa-bars"></i>
</a>
<div class="controls">
<a class="account" href="/login">Login</a>
<span>&mdash;</span>
<a
href="/darkmode"
title="Toggle dark mode"
onclick="return toggle_theme()"
><i aria-label="Dark mode" class="fas fa-adjust"></i
><span>Dark mode</span></a
>
</div>
</div>
</div>
<div class="skeleton">
<div class="navigation">
<div class="title-block">
<h3><a href="/problemset/list/">CSES Problem Set</a></h3>
<h1>Distinct Numbers</h1>
<ul class="nav">
<li><a href="/problemset/task/1621/" class="current">Task</a></li>
<li><a href="/problemset/stats/1621/">Statistics</a></li>
</ul>
</div>
</div>
<div class="content-wrapper">
<div class="content">
<title>CSES - Distinct Numbers</title
><link rel="stylesheet" href="/lib/katex/katex.min.css" />
<script defer src="/lib/katex/katex.min.js"></script>
<script defer src="/lib/katex/contrib/copy-tex.min.js"></script>
<script
defer
src="/lib/google-code-prettify/run_prettify.js"
></script>
<script>
addEventListener('DOMContentLoaded', function (e) {
const mathElements = document.getElementsByClassName('math')
const macros = {}
for (let element of mathElements) {
katex.render(element.textContent, element, {
displayMode: element.classList.contains('math-display'),
throwOnError: false,
globalGroup: true,
macros
})
}
})
</script>
<ul class="task-constraints">
<li><b>Time limit:</b> 1.00 s</li>
<li><b>Memory limit:</b> 512 MB</li>
</ul>
<div class="md">
<p>
You are given a list of
<span class="math math-inline">n</span> integers, and your task is
to calculate the number of <em>distinct</em> values in the list.
</p>
<h1 id="input">Input</h1>
<p>
The first input line has an integer
<span class="math math-inline">n</span>: the number of values.
</p>
<p>
The second line has
<span class="math math-inline">n</span> integers
<span class="math math-inline">x_1,x_2,\dots,x_n</span>.
</p>
<h1 id="output">Output</h1>
<p>Print one integers: the number of distinct values.</p>
<h1 id="constraints">Constraints</h1>
<ul>
<li>
<span class="math math-inline">1 \le n \le 2 \cdot 10^5</span>
</li>
<li><span class="math math-inline">1 \le x_i \le 10^9</span></li>
</ul>
<h1 id="example">Example</h1>
<p>Input:</p>
<pre>
5
2 3 2 2 3
</pre
>
<p>Output:</p>
<pre>
2
</pre
>
</div>
</div>
<div class="nav sidebar">
<h4>Sorting and Searching</h4>
<a class="current" href="/problemset/task/1621"
>Distinct Numbers<span class="task-score icon"></span></a
><a href="/problemset/task/1084"
>Apartments<span class="task-score icon"></span></a
><a href="/problemset/task/1090"
>Ferris Wheel<span class="task-score icon"></span></a
><a href="/problemset/task/1091"
>Concert Tickets<span class="task-score icon"></span></a
><a href="/problemset/task/1619"
>Restaurant Customers<span class="task-score icon"></span></a
><a href="/problemset/task/1629"
>Movie Festival<span class="task-score icon"></span></a
><a href="/problemset/task/1640"
>Sum of Two Values<span class="task-score icon"></span></a
><a href="/problemset/task/1643"
>Maximum Subarray Sum<span class="task-score icon"></span></a
>...
<hr />
</div>
</div>
</div>
</body>
</html>

View file

@ -1,2 +0,0 @@
def test():
assert 5 == 5

69
tests/test_scrapers.py Normal file
View file

@ -0,0 +1,69 @@
import pytest
from scrapers.models import (
ContestListResult,
MetadataResult,
TestsResult,
)
MODEL_FOR_MODE = {
"metadata": MetadataResult,
"contests": ContestListResult,
}
MATRIX = {
"cses": {
"metadata": ("introductory_problems",),
"tests": ("introductory_problems",),
"contests": tuple(),
},
"atcoder": {
"metadata": ("abc100",),
"tests": ("abc100",),
"contests": tuple(),
},
"codeforces": {
"metadata": ("1550",),
"tests": ("1550",),
"contests": tuple(),
},
}
@pytest.mark.parametrize("scraper", MATRIX.keys())
@pytest.mark.parametrize("mode", ["metadata", "tests", "contests"])
def test_scraper_offline_fixture_matrix(run_scraper_offline, scraper, mode):
args = MATRIX[scraper][mode]
rc, objs = run_scraper_offline(scraper, mode, *args)
assert rc in (0, 1), f"Bad exit code {rc}"
assert objs, f"No JSON output for {scraper}:{mode}"
if mode in ("metadata", "contests"):
Model = MODEL_FOR_MODE[mode]
model = Model.model_validate(objs[-1])
assert model is not None
if mode == "metadata":
assert model.success in (True, False)
if model.success:
assert len(model.problems) >= 1
assert all(isinstance(p.id, str) and p.id for p in model.problems)
else:
assert model.success in (True, False)
if model.success:
assert len(model.contests) >= 1
else:
validated_any = False
for obj in objs:
if "success" in obj and "tests" in obj and "problem_id" in obj:
tr = TestsResult.model_validate(obj)
assert tr.problem_id != ""
assert isinstance(tr.tests, list)
validated_any = True
else:
assert "problem_id" in obj
assert "tests" in obj and isinstance(obj["tests"], list)
assert (
"timeout_ms" in obj and "memory_mb" in obj and "interactive" in obj
)
validated_any = True
assert validated_any, "No valid tests payloads validated"

103
uv.lock generated
View file

@ -92,6 +92,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" },
] ]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]] [[package]]
name = "anyio" name = "anyio"
version = "4.11.0" version = "4.11.0"
@ -1299,6 +1308,86 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" },
] ]
[[package]]
name = "pydantic"
version = "2.11.10"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ae/54/ecab642b3bed45f7d5f59b38443dcb36ef50f85af192e6ece103dbfe9587/pydantic-2.11.10.tar.gz", hash = "sha256:dc280f0982fbda6c38fada4e476dc0a4f3aeaf9c6ad4c28df68a666ec3c61423", size = 788494, upload-time = "2025-10-04T10:40:41.338Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/bd/1f/73c53fcbfb0b5a78f91176df41945ca466e71e9d9d836e5c522abda39ee7/pydantic-2.11.10-py3-none-any.whl", hash = "sha256:802a655709d49bd004c31e865ef37da30b540786a46bfce02333e0e24b5fe29a", size = 444823, upload-time = "2025-10-04T10:40:39.055Z" },
]
[[package]]
name = "pydantic-core"
version = "2.33.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584, upload-time = "2025-04-23T18:31:03.106Z" },
{ url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071, upload-time = "2025-04-23T18:31:04.621Z" },
{ url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823, upload-time = "2025-04-23T18:31:06.377Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792, upload-time = "2025-04-23T18:31:07.93Z" },
{ url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338, upload-time = "2025-04-23T18:31:09.283Z" },
{ url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998, upload-time = "2025-04-23T18:31:11.7Z" },
{ url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200, upload-time = "2025-04-23T18:31:13.536Z" },
{ url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890, upload-time = "2025-04-23T18:31:15.011Z" },
{ url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359, upload-time = "2025-04-23T18:31:16.393Z" },
{ url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883, upload-time = "2025-04-23T18:31:17.892Z" },
{ url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074, upload-time = "2025-04-23T18:31:19.205Z" },
{ url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538, upload-time = "2025-04-23T18:31:20.541Z" },
{ url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909, upload-time = "2025-04-23T18:31:22.371Z" },
{ url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786, upload-time = "2025-04-23T18:31:24.161Z" },
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
{ url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" },
{ url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" },
{ url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" },
{ url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" },
{ url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" },
{ url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" },
{ url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" },
{ url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" },
{ url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" },
{ url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" },
{ url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" },
{ url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" },
{ url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" },
{ url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" },
{ url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" },
{ url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" },
{ url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200, upload-time = "2025-04-23T18:33:14.199Z" },
{ url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123, upload-time = "2025-04-23T18:33:16.555Z" },
{ url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852, upload-time = "2025-04-23T18:33:18.513Z" },
{ url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484, upload-time = "2025-04-23T18:33:20.475Z" },
{ url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896, upload-time = "2025-04-23T18:33:22.501Z" },
{ url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475, upload-time = "2025-04-23T18:33:24.528Z" },
{ url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013, upload-time = "2025-04-23T18:33:26.621Z" },
{ url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715, upload-time = "2025-04-23T18:33:28.656Z" },
{ url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757, upload-time = "2025-04-23T18:33:30.645Z" },
]
[[package]] [[package]]
name = "pyee" name = "pyee"
version = "13.0.0" version = "13.0.0"
@ -1460,6 +1549,7 @@ dependencies = [
{ name = "curl-cffi" }, { name = "curl-cffi" },
{ name = "httpx" }, { name = "httpx" },
{ name = "ndjson" }, { name = "ndjson" },
{ name = "pydantic" },
{ name = "requests" }, { name = "requests" },
{ name = "scrapling", extra = ["fetchers"] }, { name = "scrapling", extra = ["fetchers"] },
] ]
@ -1482,6 +1572,7 @@ requires-dist = [
{ name = "curl-cffi", specifier = ">=0.13.0" }, { name = "curl-cffi", specifier = ">=0.13.0" },
{ name = "httpx", specifier = ">=0.28.1" }, { name = "httpx", specifier = ">=0.28.1" },
{ name = "ndjson", specifier = ">=0.3.1" }, { name = "ndjson", specifier = ">=0.3.1" },
{ name = "pydantic", specifier = ">=2.11.10" },
{ name = "requests", specifier = ">=2.32.5" }, { name = "requests", specifier = ">=2.32.5" },
{ name = "scrapling", extras = ["fetchers"], specifier = ">=0.3.5" }, { name = "scrapling", extras = ["fetchers"], specifier = ">=0.3.5" },
] ]
@ -1623,6 +1714,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
] ]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
[[package]] [[package]]
name = "ua-parser" name = "ua-parser"
version = "1.0.1" version = "1.0.1"