CCR/.venv/lib/python3.12/site-packages/portion/io.py

222 lines
6.9 KiB
Python

import re
from .const import Bound, inf
from .interval import Interval
def from_string(
string,
conv,
*,
bound=r".+?",
disj=r" ?\| ?",
sep=r", ?",
left_open=r"\(",
left_closed=r"\[",
right_open=r"\)",
right_closed=r"\]",
pinf=r"\+inf",
ninf=r"-inf",
klass=Interval,
):
"""
Parse given string and create an Interval instance.
A converter function has to be provided to convert a bound (as string) to a value.
This function raises a ValueError if given string cannot be parsed to an interval.
:param string: string to parse.
:param conv: function to convert a bound (as string) to an object.
:param bound: regex pattern for a value.
:param disj: regex pattern for disjunctive operator (default matches '|' and ' | ').
:param sep: regex pattern for bound separator (default matches ',').
:param left_open: regex pattern for left open boundary (default matches '(').
:param left_closed: regex pattern for left closed boundary (default
matches '[').
:param right_open: regex pattern for right open boundary (default matches ')').
:param right_closed: regex pattern for right closed boundary (default
matches ']').
:param pinf: regex pattern for positive infinity (default matches '+inf').
:param ninf: regex pattern for negative infinity (default matches '-inf').
:param klass: class to use for creating intervals (default to Interval).
:return: an interval.
"""
re_left_boundary = r"(?P<left>{}|{})".format(left_open, left_closed)
re_right_boundary = r"(?P<right>{}|{})".format(right_open, right_closed)
re_bounds = r"(?P<lower>{bound})({sep}(?P<upper>{bound}))?".format(
bound=bound, sep=sep
)
re_interval = r"{}(|{}){}".format(re_left_boundary, re_bounds, re_right_boundary)
intervals = []
has_more = True
source = string
def _convert(bound):
if re.match(pinf, bound):
return inf
elif re.match(ninf, bound):
return -inf
else:
return conv(bound)
while has_more:
match = re.match(re_interval, string)
if match is None:
raise ValueError('"{}" cannot be parsed to an interval.'.format(source))
# Parse atomic interval
group = match.groupdict()
left = (
Bound.CLOSED if re.match(left_closed + "$", group["left"]) else Bound.OPEN
)
right = (
Bound.CLOSED if re.match(right_closed + "$", group["right"]) else Bound.OPEN
)
lower = group.get("lower", None)
upper = group.get("upper", None)
lower = _convert(lower) if lower is not None else inf
upper = _convert(upper) if upper is not None else lower
intervals.append(klass.from_atomic(left, lower, upper, right))
string = string[match.end() :]
# Are there more atomic intervals?
if len(string) > 0:
match = re.match(disj, string)
if match is None:
raise ValueError('"{}" cannot be parsed to an interval.'.format(source))
else:
string = string[match.end() :]
else:
has_more = False
return klass(*intervals)
def to_string(
interval,
conv=repr,
*,
disj=" | ",
sep=",",
left_open="(",
left_closed="[",
right_open=")",
right_closed="]",
pinf="+inf",
ninf="-inf",
):
"""
Export given interval to string.
:param interval: an interval.
:param conv: function that is used to represent a bound (default is `repr`).
:param disj: string representing disjunctive operator (default is ' | ').
:param sep: string representing bound separator (default is ',').
:param left_open: string representing left open boundary (default is '(').
:param left_closed: string representing left closed boundary (default is '[').
:param right_open: string representing right open boundary (default is ')').
:param right_closed: string representing right closed boundary (default is ']').
:param pinf: string representing a positive infinity (default is '+inf').
:param ninf: string representing a negative infinity (default is '-inf').
:return: a string representation for given interval.
"""
if interval.empty:
return left_open + right_open
def _convert(bound):
if bound == inf:
return pinf
elif bound == -inf:
return ninf
else:
return conv(bound)
exported_intervals = []
for item in interval:
left = left_open if item.left == Bound.OPEN else left_closed
right = right_open if item.right == Bound.OPEN else right_closed
lower = _convert(item.lower)
upper = _convert(item.upper)
if item.lower == item.upper:
exported_intervals.append(left + lower + right)
else:
exported_intervals.append(left + lower + sep + upper + right)
return disj.join(exported_intervals)
def from_data(
data, conv=None, *, pinf=float("inf"), ninf=float("-inf"), klass=Interval
):
"""
Import an interval from a list of 4-uples (left, lower, upper, right).
:param data: a list of 4-uples (left, lower, upper, right).
:param conv: function to convert bound values, default to identity.
:param pinf: value used to represent positive infinity.
:param ninf: value used to represent negative infinity.
:param klass: class to use for creating intervals (default to Interval).
:return: an interval.
"""
intervals = []
conv = (lambda v: v) if conv is None else conv
def _convert(bound):
if bound == pinf:
return inf
elif bound == ninf:
return -inf
else:
return conv(bound)
for item in data:
left, lower, upper, right = item
intervals.append(
klass.from_atomic(
Bound(left),
_convert(lower),
_convert(upper),
Bound(right),
)
)
return klass(*intervals)
def to_data(interval, conv=None, *, pinf=float("inf"), ninf=float("-inf")):
"""
Export given interval to a list of 4-uples (left, lower, upper, right).
:param interval: an interval.
:param conv: function to convert bound values, default to identity.
:param pinf: value used to encode positive infinity.
:param ninf: value used to encode negative infinity.
:return: a list of 4-uples (left, lower, upper, right)
"""
conv = (lambda v: v) if conv is None else conv
data = []
def _convert(bound):
if bound == inf:
return pinf
elif bound == -inf:
return ninf
else:
return conv(bound)
for item in interval:
data.append(
(
item.left.value,
_convert(item.lower),
_convert(item.upper),
item.right.value,
)
)
return data