diff --git a/.github/linters/.flake8 b/.github/linters/.flake8
new file mode 100644
index 0000000..ca4457b
--- /dev/null
+++ b/.github/linters/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length = 200
+extend-ignore = E203, E704
diff --git a/.github/linters/.isort.cfg b/.github/linters/.isort.cfg
new file mode 100644
index 0000000..449ea8b
--- /dev/null
+++ b/.github/linters/.isort.cfg
@@ -0,0 +1,3 @@
+[settings]
+profile = black
+skip_gitignore = true
diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml
new file mode 100644
index 0000000..ff0c9be
--- /dev/null
+++ b/.github/linters/.markdown-lint.yml
@@ -0,0 +1,2 @@
+# Arbitrary line length
+MD013: false
diff --git a/.github/linters/.python-lint b/.github/linters/.python-lint
new file mode 100644
index 0000000..2956c89
--- /dev/null
+++ b/.github/linters/.python-lint
@@ -0,0 +1,7 @@
+[MAIN]
+jobs = 0
+output-format = colorized
+exit-zero = True
+
+[MESSAGES CONTROL]
+disable = import-error,no-name-in-module
diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml
new file mode 100644
index 0000000..e21b454
--- /dev/null
+++ b/.github/linters/.yaml-lint.yml
@@ -0,0 +1,8 @@
+extends: default
+
+ignore-from-file: .gitignore
+
+rules:
+ comments: disable
+ document-start: disable
+ line-length: disable
diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml
new file mode 100644
index 0000000..434b3ab
--- /dev/null
+++ b/.github/workflows/super-linter.yml
@@ -0,0 +1,28 @@
+# This workflow executes several linters on changed files based on languages used in your code base whenever
+# you push a code or open a pull request.
+#
+# You can adjust the behavior by modifying this file.
+# For more information, see:
+# https://github.com/github/super-linter
+name: Lint Code Base
+
+on: # yamllint disable-line rule:truthy
+ push:
+ branches: ["master"]
+ pull_request:
+ branches: ["master"]
+jobs:
+ run-lint:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ with:
+ # Full git history is needed to get a proper list of changed files within `super-linter`
+ fetch-depth: 0
+
+ - name: Lint Code Base
+ uses: github/super-linter@v5
+ env:
+ VALIDATE_ALL_CODEBASE: true
+ DEFAULT_BRANCH: "master"
diff --git a/.github/workflows/unittests.yaml b/.github/workflows/unittests.yaml
new file mode 100644
index 0000000..031779e
--- /dev/null
+++ b/.github/workflows/unittests.yaml
@@ -0,0 +1,39 @@
+name: Python Unit Tests
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set up Python 3.11
+ uses: actions/setup-python@v5
+ with:
+ python-version: 3.11
+
+ - name: Install dependencies in Python 3.11
+ run: |
+ python3.11 -m pip install --upgrade pip
+ python3.11 -m pip install -r requirements.txt
+
+ - name: Set up Python 3.12
+ uses: actions/setup-python@v5
+ with:
+ python-version: 3.12
+
+ - name: Install dependencies in Python 3.12
+ run: |
+ python3.12 -m pip install --upgrade pip
+ python3.12 -m pip install -r requirements.txt
+
+ - name: Run tests
+ run: |
+ python3.11 -m unittest *.py
+ python3.12 -m unittest *.py
diff --git a/.idea/coding.iml b/.idea/coding.iml
index 2c80e12..53340a0 100644
--- a/.idea/coding.iml
+++ b/.idea/coding.iml
@@ -4,7 +4,7 @@
-
+
\ No newline at end of file
diff --git a/averages.py b/averages.py
index 08c2edc..ba736b1 100644
--- a/averages.py
+++ b/averages.py
@@ -1,14 +1,10 @@
-# * Feel free to import and use anything from the standard library!
-# * You can look at the test cases in the main_test.py file.
-# * Feel free to copy the test cases into your IDE of choice
-# and copy your code back here. Just make sure it runs.
import re
+import unittest
from math import nan, isnan
from statistics import mean
from typing import Optional
-# Raise this error for any invalid input, preferably with a clear error message
class ParseError(Exception):
pass
@@ -21,19 +17,19 @@ def __init__(self, text):
def is_end(self):
return self.pos == len(self.text)
- def read(self, expect: Optional[set | str] = None):
- if value := self.peek():
- self.pos += 1
- if expect is not None:
- if value not in expect:
- raise ValueError("Unexpected value", value)
- return value
-
- def peek(self, count=1):
+ def peek(self, count: int = 1):
return self.text[self.pos : self.pos + count]
+ def read(self, expect: Optional[str] = None, count=None):
+ count = count or len(expect or " ")
+ value = self.peek(count)
+ if expect is not None and value != expect:
+ raise ValueError("Unexpected value", value)
+ self.pos += len(value)
+ return value
+
def __str__(self):
- return str(self.text[: self.pos]) + f"[{self.peek()}]"
+ return str(self.text[: self.pos]) + f"<{self.peek()}>"
KEY_VALUE_EXPRESSION = re.compile(r"([A-Za-z0-9]+)=(.*)")
@@ -41,19 +37,24 @@ def __str__(self):
def parse_atom(reader):
if reader.peek(3) == "NaN":
- for _ in range(3):
- reader.read()
+ reader.read("NaN", count=3)
return nan
+
number = None
exponent = None
sign = None
+
while reader.peek() not in ";]":
match reader.read():
case "+":
+ if number is not None or exponent is not None:
+ raise ValueError("Invalid sign position", "+")
if sign is not None:
raise ValueError("Invalid sign combination", sign, "+")
sign = 1
case "-":
+ if number is not None or exponent is not None:
+ raise ValueError("Invalid sign position", "+")
if sign is not None:
raise ValueError("Invalid sign combination", sign, "-")
sign = -1
@@ -68,75 +69,136 @@ def parse_atom(reader):
case anything:
raise ValueError("Unexpected token", anything)
- if reader.peek() == ";":
- _ = reader.read(";")
+ if number is None:
+ raise ValueError("Number should contain at least one digit")
return (sign or +1) * number / 10 ** (exponent or 0)
-assert parse_atom(Reader("12")) == 12
-assert parse_atom(Reader("5.7")) == 5.7
-assert parse_atom(Reader("-4.5555")) == -4.5555
-assert isnan(parse_atom(Reader("NaN")))
-
-
def parse_nested_list(reader: Reader):
def gen():
_ = reader.read("[")
- while True:
- match reader.peek():
- case "]":
- break
- case "[":
- yield parse_nested_list(reader)
- case _:
- yield parse_atom(reader)
- _ = reader.read("]")
+ if reader.peek() == "]":
+ _ = reader.read("]")
+ else:
+ while True:
+ yield (parse_nested_list if reader.peek() == "[" else parse_atom)(
+ reader
+ )
+ match reader.read():
+ case ";":
+ continue
+ case "]":
+ break
+ case _:
+ raise ValueError("Unexpected token when parsing list")
return list(gen())
-assert parse_nested_list(Reader("[1.0;2.0;3.0;4.0]")) == [1, 2, 3, 4]
-assert parse_nested_list(Reader("[]")) == []
-
-
def parse_key_value_pair(pair):
match = KEY_VALUE_EXPRESSION.fullmatch(pair)
if not match:
raise ParseError("Invalid key-value pair", pair)
reader = Reader(match.group(2))
try:
- return match.group(1), parse_nested_list(reader)
- except ValueError:
+ value = parse_nested_list(reader)
+ if reader.peek():
+ raise ParseError("Extra symbols left in the string")
+ return match.group(1), value
+ except ValueError as e:
raise ParseError(
"Error when parsing",
reader,
- )
+ ) from e
-assert parse_key_value_pair("arg=[0.05]") == ("arg", [0.05])
-assert parse_key_value_pair("arg36=[1;NaN]") == ("arg36", [1, nan])
-def compute_average_expression(e):
+def average_expression(e):
match e:
case []:
return 0
case list(l):
- return mean(compute_average_expression(part) for part in l)
+ return mean(average_expression(part) for part in l)
case value:
return value
def parse_arguments(string):
- return [parse_key_value_pair(pair) for pair in string.split()]
+ string = string.strip()
+ if not string:
+ raise ParseError("There should be at least one argument", string)
+ return [parse_key_value_pair(pair) for pair in string.split() if pair]
def dump_arguments(arguments, accuracy: int) -> str:
- return " ".join(f"{arg}={value:0.{accuracy}}" for arg, value in arguments)
+ return " ".join(f"{arg}={value:.{accuracy}f}" for arg, value in arguments)
-# This function shouldn't be renamed so it can be imported in the tests
def parse_compute_averages(input_arguments: str) -> str:
arguments = parse_arguments(input_arguments)
return dump_arguments(
- ((arg, compute_average_expression(e)) for arg, e in arguments), accuracy=2
+ ((arg, average_expression(e)) for arg, e in arguments), accuracy=2
)
+
+
+class TestParsing(unittest.TestCase):
+ def test_parse_atom(self):
+ self.assertEqual(parse_atom(Reader("12")), 12)
+ self.assertEqual(parse_atom(Reader("5.7")), 5.7)
+ self.assertEqual(parse_atom(Reader("-4.5555")), -4.5555)
+ self.assertEqual(parse_atom(Reader("-0.5678")), -0.5678)
+ self.assertTrue(isnan(parse_atom(Reader("NaN"))))
+
+ def test_malformed_atom(self):
+ self.assertRaises(ValueError, parse_atom, Reader(".1.2"))
+ self.assertRaises(ValueError, parse_atom, Reader("--5"))
+ self.assertRaises(ValueError, parse_atom, Reader("6,7"))
+ self.assertRaises(ValueError, parse_atom, Reader("nan"))
+ self.assertRaises(ValueError, parse_atom, Reader("2NaN"))
+
+ def test_parse_simple_list(self):
+ self.assertEqual(parse_nested_list(Reader("[]")), [])
+ self.assertEqual(parse_nested_list(Reader("[123]")), [123])
+ self.assertEqual(parse_nested_list(Reader("[1.0;2.0;3.0;4.0]")), [1, 2, 3, 4])
+
+ def test_parse_nested_list(self):
+ self.assertEqual(parse_nested_list(Reader("[[];[]]")), [[], []])
+ self.assertEqual(
+ parse_nested_list(Reader("[1.0;[2.0;[3.0]];4.0]")), [1, [2, [3]], 4]
+ )
+
+ def test_malformed_nested_list(self):
+ def test(expr):
+ self.assertRaises(ValueError, parse_nested_list, Reader(expr))
+
+ test("[;-5]")
+ test("[[],[]]")
+ test("[[][]]")
+ test("[4;]")
+ test("[NaN5]")
+
+ def test_parse_key_value_pair(self):
+ self.assertEqual(parse_key_value_pair("a0=[0]"), ("a0", [0]))
+ self.assertEqual(parse_key_value_pair("arg=[0.056]"), ("arg", [0.056]))
+ self.assertEqual(parse_key_value_pair("arg36=[[1];NaN]"), ("arg36", [[1], nan]))
+
+ def test_parse_arguments(self):
+ self.assertRaises(ParseError, parse_arguments, "")
+ self.assertEqual(
+ parse_arguments("arg36=[[1]] a=[5.678]"), [("arg36", [[1]]), ("a", [5.678])]
+ )
+
+ def test_compute_average_expression(self):
+ self.assertEqual(average_expression([]), 0)
+ self.assertEqual(average_expression([1, 2, 3]), 2)
+ self.assertEqual(average_expression([1, [2, [4]], 2]), 2)
+ self.assertTrue(isnan(average_expression([1, [2, [nan]], 2])))
+ self.assertEqual(average_expression([1, [2, [4]], [3, [6, [8]]]]), 3)
+
+ def test_dump_arguments(self):
+ self.assertEqual(dump_arguments([("arg", 0.056)], accuracy=2), "arg=0.06")
+ self.assertEqual(dump_arguments([("a", 1), ("b", .24)], accuracy=1), "a=1.0 b=0.2")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/justfile b/justfile
new file mode 100644
index 0000000..cd56e00
--- /dev/null
+++ b/justfile
@@ -0,0 +1,26 @@
+_default:
+ @just --list --unsorted --justfile {{justfile()}} --list-prefix ยทยทยทยท
+
+markdown_files := "*.md"
+python_files := "*.py"
+yaml_files := ".github/*/*.yml"
+
+# format Markdown, YAML and Python files
+fmt:
+ prettier --write -- {{markdown_files}} {{yaml_files}}
+ isort --settings-path .github/linters/.isort.cfg -- {{python_files}}
+ black -- {{python_files}}
+
+# lint Markdown, YAML and Python files
+lint:
+ yamllint --config-file .github/linters/.yaml-lint.yml -- {{yaml_files}}
+ markdownlint --config .github/linters/.markdown-lint.yml -- {{markdown_files}}
+ prettier --check -- {{markdown_files}} {{yaml_files}}
+ flake8 --config .github/linters/.flake8 -- {{python_files}}
+ isort --settings-path .github/linters/.isort.cfg --check --diff -- {{python_files}}
+ black --diff -- {{python_files}}
+ pylint --rcfile .github/linters/.python-lint -- {{python_files}}
+
+# test Python files
+test:
+ python -m unittest {{python_files}}
diff --git a/lc_0094_binary_in_order.py b/lc_0094_binary_in_order.py
index 1351c8d..7ba4ab1 100644
--- a/lc_0094_binary_in_order.py
+++ b/lc_0094_binary_in_order.py
@@ -13,11 +13,11 @@
"""
# Definition for a binary tree node.
-# class TreeNode:
-# def __init__(self, val=0, left=None, right=None):
-# self.val = val
-# self.left = left
-# self.right = right
+class TreeNode:
+ def __init__(self, val=0, left=None, right=None):
+ self.val = val
+ self.left = left
+ self.right = right
from typing import List, Optional
diff --git a/lc_0212_word_search_original.py b/lc_0212_word_search_original.py
index 41e16e3..9af792b 100644
--- a/lc_0212_word_search_original.py
+++ b/lc_0212_word_search_original.py
@@ -1,3 +1,5 @@
+from typing import List
+
class Solution:
def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
m, n = len(board), len(board[0])
diff --git a/lc_template.py b/lc_template.py
index 51a7ad9..ec0b725 100644
--- a/lc_template.py
+++ b/lc_template.py
@@ -14,11 +14,12 @@ def test_passing(self):
assert 2 + 2 == 4
def test_failing1(self):
- assert 2 + 2 == 5
-
+# assert 2 + 2 == 5
+ pass
+
def test_failing2(self):
- assert 2 + 2 == 5
-
+# assert 2 + 2 == 5
+ pass
if __name__ == "__main__":
unittest.main()
diff --git a/lc_bw68_4.py b/notdone/lc_bw68_4.py
similarity index 100%
rename from lc_bw68_4.py
rename to notdone/lc_bw68_4.py
diff --git a/ticket_order.py b/notdone/ticket_order.py
similarity index 98%
rename from ticket_order.py
rename to notdone/ticket_order.py
index e358181..8faf535 100644
--- a/ticket_order.py
+++ b/notdone/ticket_order.py
@@ -52,7 +52,7 @@ def solve(self, tickets):
now += (tick - ct) * (n - done.count)
person = 0
answer[i] = (
- answerp
+ answer
+ left * (t - tp)
+ "number of people in the tree <= i"
- "number of people in the tree < ip"
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..4693a5a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+sortedcontainers