add proxy for the first part and try to develop the second part
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from collections.abc import Iterable
|
||||
from functools import cache
|
||||
from typing import TypeVar
|
||||
|
||||
_T = TypeVar('_T')
|
||||
|
||||
_undefined = object() # unique object for when None is not a good default
|
||||
|
||||
|
||||
def first(iterable, default=_undefined):
|
||||
"""Return the first item of *iterable*, or *default* if *iterable* is
|
||||
empty.
|
||||
|
||||
>>> first([0, 1, 2, 3])
|
||||
0
|
||||
>>> first([], 'some default')
|
||||
'some default'
|
||||
|
||||
If *default* is not provided and there are no items in the iterable,
|
||||
raise ``ValueError``.
|
||||
|
||||
:func:`first` is useful when you have a generator of expensive-to-retrieve
|
||||
values and want any arbitrary one. It is marginally shorter than
|
||||
``next(iter(iterable), default)``.
|
||||
|
||||
"""
|
||||
# NOTE: https://more-itertools.readthedocs.io/en/stable/_modules/more_itertools/more.html#first
|
||||
try:
|
||||
return next(iter(iterable))
|
||||
except StopIteration as e:
|
||||
# I'm on the edge about raising ValueError instead of StopIteration. At
|
||||
# the moment, ValueError wins, because the caller could conceivably
|
||||
# want to do something different with flow control when I raise the
|
||||
# exception, and it's weird to explicitly catch StopIteration.
|
||||
if default is _undefined:
|
||||
raise ValueError(
|
||||
'first() was called on an empty iterable, and no '
|
||||
'default value was provided.',
|
||||
) from e
|
||||
return default
|
||||
|
||||
|
||||
def match_to_find(m: re.Match):
|
||||
if m is None:
|
||||
return None
|
||||
g = m.groups(default=m.string[0:0])
|
||||
if len(g) == 1:
|
||||
return g[0]
|
||||
else:
|
||||
return g or m.group()
|
||||
|
||||
|
||||
def findalliter(pattern, string, pos=None, endpos=None, flags=0):
|
||||
"""
|
||||
like finditer(), but with return values like findall()
|
||||
|
||||
implementation taken from cpython/Modules/_sre.c/findall()
|
||||
"""
|
||||
r = (
|
||||
pattern
|
||||
if isinstance(pattern, re.Pattern)
|
||||
else re.compile(pattern, flags=flags)
|
||||
)
|
||||
if endpos is not None:
|
||||
iterator = r.finditer(string, pos=pos, endpos=endpos)
|
||||
elif pos is not None:
|
||||
iterator = r.finditer(string, pos=pos)
|
||||
else:
|
||||
iterator = r.finditer(string)
|
||||
for m in iterator:
|
||||
yield match_to_find(m)
|
||||
|
||||
|
||||
def findfirst(pattern, string, pos=None, endpos=None, flags=0, default=_undefined):
|
||||
"""
|
||||
Avoids using the inefficient findall(...)[0], or first(findall(...))
|
||||
"""
|
||||
return first(
|
||||
findalliter(pattern, string, pos=pos, endpos=endpos, flags=flags),
|
||||
default=default,
|
||||
)
|
||||
|
||||
|
||||
def topsort(nodes: Iterable[_T], order: Iterable[tuple[_T, _T]]) -> list[_T]:
|
||||
# https://en.wikipedia.org/wiki/Topological_sorting
|
||||
|
||||
order = set(order)
|
||||
result: list[_T] = [] # Empty list that will contain the sorted elements
|
||||
|
||||
def with_incoming() -> set[_T]:
|
||||
return {m for (_, m) in order}
|
||||
|
||||
# Set of all nodes with no incoming edges
|
||||
pending = list(set(nodes) - with_incoming())
|
||||
while pending:
|
||||
n = pending.pop()
|
||||
result.append(n)
|
||||
|
||||
# nodes m with an edge from n to m
|
||||
outgoing = {m for (x, m) in order if x == n}
|
||||
order -= {(n, m) for m in outgoing}
|
||||
pending.extend(outgoing - with_incoming())
|
||||
|
||||
if order:
|
||||
raise ValueError('There are cycles in the topological order')
|
||||
|
||||
return list(reversed(result)) # a topologically sorted list
|
||||
|
||||
|
||||
@cache
|
||||
def cached_re_compile(regex: re.Pattern | str | bytes) -> re.Pattern | None:
|
||||
if isinstance(regex, re.Pattern):
|
||||
return regex
|
||||
return re.compile(regex) if isinstance(regex, (str | bytes)) else None
|
||||
Reference in New Issue
Block a user