Skip to main content
Previously wrong; didn't yield one of every option in a group
Source Link
Reinderien
  • 71.2k
  • 5
  • 76
  • 257
import re
from numbers import Real
from random import randint, choice
from typing import Union, Callable


class Pattern:
    chunk_pat = re.compile(
        r'([^|]+)'   # group: within a chunk, at least one non-pipe character
        r'(?:'       # non-capturing group for termination character
            r'\||$'  # pipe, or end of string
        r')'         # end of termination group
    )

    option_pat = re.compile(
        r'([^,]+)'  # at least one non-comma character in an option
        r'(?:'      # non-capturing group for termination character
            r',|$'  # comma, or end of string
        r')'        # end of termination group
    )

    range_pat = re.compile(
        r'^'            # start
        r'('
            r'[0-9.]+'  # first number group
        r')-('
            r'[0-9.]+'  # second number group
        r')'
        r'$'            # end
    )

    def __init__(self, pattern: str):
        chunk_strs = Pattern.chunk_pat.finditer(pattern)

        self.tree = tuple(
            self.parse_chunk(chunk[1])
            for chunk in chunk_strs
        )

    @staticmethod
    def choosechoose_in_group(selfgroup: tuple) -> Union[str, Real]tuple:
        node = self.tree

      for option whilein Truegroup:
            if isinstance(nodeoption, Callable):
                returnyield nodeoption()
            ifelse:
 isinstance(node, tuple):              yield option

    def choose(self) -> Union[str, tuple]:
        nodegroup = choice(nodeself.tree)
   
        if isinstance(group, elsetuple):
            return tuple(self.choose_in_group(group))
        return nodegroup

    @staticmethod
    def precis_parse(as_str: str) -> (Real, int):
        if '.' in as_str:
            return float(as_str), len(as_str.rsplit('.', 1)[-1])
        return int(as_str), 0

    @classmethod
    def make_choose(cls, start: Real, end: Real, precis: int):
        if precis:
            factor = 10**precis
            start = int(start * factor)
            end = int(end * factor)
            def choose():
                return randint(start, end) / factor

        else:
            def choose():
                return randint(start, end)

        return choose

    @classmethod
    def parse_options(cls, options: str):
        for option in cls.option_pat.finditer(options):
            range_match = cls.range_pat.match(option[1])
            if range_match:
                start_str, end_str = range_match.groups()
                start, start_n = cls.precis_parse(start_str)
                end, end_n = cls.precis_parse(end_str)
                yield cls.make_choose(start, end, max(start_n, end_n))
            else:
                # Fall back to one raw string
                yield option[1]

    @classmethod
    def parse_chunk(cls, chunk: str):
        if (
            chunk[0] == '(' and chunk[-1] == ')' or
            chunk[0] == '[' and chunk[-1] == ']'
        ):
            return tuple(cls.parse_options(chunk[1:-1]))

        # Fall back to returning the raw string
        return chunk


def test():
    p = Pattern('foo|(bar,3-4,50,6.3-7,92-99)')

    for _ in range(20):
        print(p.choose())

if __name__ == '__main__':
    test()
import re
from numbers import Real
from random import randint, choice
from typing import Union, Callable


class Pattern:
    chunk_pat = re.compile(
        r'([^|]+)'   # group: within a chunk, at least one non-pipe character
        r'(?:'       # non-capturing group for termination character
            r'\||$'  # pipe, or end of string
        r')'         # end of termination group
    )

    option_pat = re.compile(
        r'([^,]+)'  # at least one non-comma character in an option
        r'(?:'      # non-capturing group for termination character
            r',|$'  # comma, or end of string
        r')'        # end of termination group
    )

    range_pat = re.compile(
        r'^'            # start
        r'('
            r'[0-9.]+'  # first number group
        r')-('
            r'[0-9.]+'  # second number group
        r')'
        r'$'            # end
    )

    def __init__(self, pattern: str):
        chunk_strs = Pattern.chunk_pat.finditer(pattern)

        self.tree = tuple(
            self.parse_chunk(chunk[1])
            for chunk in chunk_strs
        )

    def choose(self) -> Union[str, Real]:
        node = self.tree

        while True:
            if isinstance(node, Callable):
                return node()
            if isinstance(node, tuple):
                node = choice(node)
            else:
                return node

    @staticmethod
    def precis_parse(as_str: str) -> (Real, int):
        if '.' in as_str:
            return float(as_str), len(as_str.rsplit('.', 1)[-1])
        return int(as_str), 0

    @classmethod
    def make_choose(cls, start: Real, end: Real, precis: int):
        if precis:
            factor = 10**precis
            start = int(start * factor)
            end = int(end * factor)
            def choose():
                return randint(start, end) / factor

        else:
            def choose():
                return randint(start, end)

        return choose

    @classmethod
    def parse_options(cls, options: str):
        for option in cls.option_pat.finditer(options):
            range_match = cls.range_pat.match(option[1])
            if range_match:
                start_str, end_str = range_match.groups()
                start, start_n = cls.precis_parse(start_str)
                end, end_n = cls.precis_parse(end_str)
                yield cls.make_choose(start, end, max(start_n, end_n))
            else:
                # Fall back to one raw string
                yield option[1]

    @classmethod
    def parse_chunk(cls, chunk: str):
        if (
            chunk[0] == '(' and chunk[-1] == ')' or
            chunk[0] == '[' and chunk[-1] == ']'
        ):
            return tuple(cls.parse_options(chunk[1:-1]))

        # Fall back to returning the raw string
        return chunk


def test():
    p = Pattern('foo|(bar,3-4,50,6.3-7,92-99)')

    for _ in range(20):
        print(p.choose())

if __name__ == '__main__':
    test()
import re
from numbers import Real
from random import randint, choice
from typing import Union, Callable


class Pattern:
    chunk_pat = re.compile(
        r'([^|]+)'   # group: within a chunk, at least one non-pipe character
        r'(?:'       # non-capturing group for termination character
            r'\||$'  # pipe, or end of string
        r')'         # end of termination group
    )

    option_pat = re.compile(
        r'([^,]+)'  # at least one non-comma character in an option
        r'(?:'      # non-capturing group for termination character
            r',|$'  # comma, or end of string
        r')'        # end of termination group
    )

    range_pat = re.compile(
        r'^'            # start
        r'('
            r'[0-9.]+'  # first number group
        r')-('
            r'[0-9.]+'  # second number group
        r')'
        r'$'            # end
    )

    def __init__(self, pattern: str):
        chunk_strs = Pattern.chunk_pat.finditer(pattern)

        self.tree = tuple(
            self.parse_chunk(chunk[1])
            for chunk in chunk_strs
        )

    @staticmethod
    def choose_in_group(group: tuple) -> tuple:
        for option in group:
            if isinstance(option, Callable):
                yield option()
            else:
                yield option

    def choose(self) -> Union[str, tuple]:
        group = choice(self.tree)
 
        if isinstance(group, tuple):
            return tuple(self.choose_in_group(group))
        return group

    @staticmethod
    def precis_parse(as_str: str) -> (Real, int):
        if '.' in as_str:
            return float(as_str), len(as_str.rsplit('.', 1)[-1])
        return int(as_str), 0

    @classmethod
    def make_choose(cls, start: Real, end: Real, precis: int):
        if precis:
            factor = 10**precis
            start = int(start * factor)
            end = int(end * factor)
            def choose():
                return randint(start, end) / factor

        else:
            def choose():
                return randint(start, end)

        return choose

    @classmethod
    def parse_options(cls, options: str):
        for option in cls.option_pat.finditer(options):
            range_match = cls.range_pat.match(option[1])
            if range_match:
                start_str, end_str = range_match.groups()
                start, start_n = cls.precis_parse(start_str)
                end, end_n = cls.precis_parse(end_str)
                yield cls.make_choose(start, end, max(start_n, end_n))
            else:
                # Fall back to one raw string
                yield option[1]

    @classmethod
    def parse_chunk(cls, chunk: str):
        if (
            chunk[0] == '(' and chunk[-1] == ')' or
            chunk[0] == '[' and chunk[-1] == ']'
        ):
            return tuple(cls.parse_options(chunk[1:-1]))

        # Fall back to returning the raw string
        return chunk


def test():
    p = Pattern('foo|(bar,3-4,50,6.3-7,92-99)')

    for _ in range(20):
        print(p.choose())

if __name__ == '__main__':
    test()
Source Link
Reinderien
  • 71.2k
  • 5
  • 76
  • 257

Here's an implementation whose major endeavour, when compared with your implementation as well as that of the accepted answer, is separation of parsing and execution. It's unclear whether this is important for you, but it's generally good design, and is likely faster to re-execute once parsed:

import re
from numbers import Real
from random import randint, choice
from typing import Union, Callable


class Pattern:
    chunk_pat = re.compile(
        r'([^|]+)'   # group: within a chunk, at least one non-pipe character
        r'(?:'       # non-capturing group for termination character
            r'\||$'  # pipe, or end of string
        r')'         # end of termination group
    )

    option_pat = re.compile(
        r'([^,]+)'  # at least one non-comma character in an option
        r'(?:'      # non-capturing group for termination character
            r',|$'  # comma, or end of string
        r')'        # end of termination group
    )

    range_pat = re.compile(
        r'^'            # start
        r'('
            r'[0-9.]+'  # first number group
        r')-('
            r'[0-9.]+'  # second number group
        r')'
        r'$'            # end
    )

    def __init__(self, pattern: str):
        chunk_strs = Pattern.chunk_pat.finditer(pattern)

        self.tree = tuple(
            self.parse_chunk(chunk[1])
            for chunk in chunk_strs
        )

    def choose(self) -> Union[str, Real]:
        node = self.tree

        while True:
            if isinstance(node, Callable):
                return node()
            if isinstance(node, tuple):
                node = choice(node)
            else:
                return node

    @staticmethod
    def precis_parse(as_str: str) -> (Real, int):
        if '.' in as_str:
            return float(as_str), len(as_str.rsplit('.', 1)[-1])
        return int(as_str), 0

    @classmethod
    def make_choose(cls, start: Real, end: Real, precis: int):
        if precis:
            factor = 10**precis
            start = int(start * factor)
            end = int(end * factor)
            def choose():
                return randint(start, end) / factor

        else:
            def choose():
                return randint(start, end)

        return choose

    @classmethod
    def parse_options(cls, options: str):
        for option in cls.option_pat.finditer(options):
            range_match = cls.range_pat.match(option[1])
            if range_match:
                start_str, end_str = range_match.groups()
                start, start_n = cls.precis_parse(start_str)
                end, end_n = cls.precis_parse(end_str)
                yield cls.make_choose(start, end, max(start_n, end_n))
            else:
                # Fall back to one raw string
                yield option[1]

    @classmethod
    def parse_chunk(cls, chunk: str):
        if (
            chunk[0] == '(' and chunk[-1] == ')' or
            chunk[0] == '[' and chunk[-1] == ']'
        ):
            return tuple(cls.parse_options(chunk[1:-1]))

        # Fall back to returning the raw string
        return chunk


def test():
    p = Pattern('foo|(bar,3-4,50,6.3-7,92-99)')

    for _ in range(20):
        print(p.choose())

if __name__ == '__main__':
    test()