import sys import re syntax_description = """ A "sequence" is an integer (the "index") followed by an optional description of one or more sequence subsets. A subset is defined by a series of subset descriptions, separated by a comma. A subset description is either an integer, two integers separated by a hypen to indicate a range, or an integer followed only by a hyphen, which will include all the following elements of the sequence to the end. No spaces are allowed in a sequence. All indices are zero-origin. Sequence examples for an index of "3": 3 3(1) 3(0-4) 3(1-4,6-9) 3(5-) Note that some sequence subsets must include two numbers, for example, border lines in a table. """.strip() class Sequence: def __init__(self, count, maxval, spec): def parse_range(match): start, hyphen, end = match.groups() if hyphen is None and end is None: end = start elif end is None: end = maxval return [int(start), int(end)] self.maxval = maxval self.all = True subset_pat = "[-\\d,]+" sequence_rgx = re.compile(fr"(\d+)(\({subset_pat}\))*") match = sequence_rgx.match(spec) self.spec = spec if match and match.group(0) == spec: range_rgx = re.compile(r"(\d+)(-)?(\d+)?") self.index = int(match.group(1)) if (match.group(2)): self.subsets = match.group(2).strip("()").split(",") matches = [range_rgx.match(e) for e in self.subsets] self.ranges = [parse_range(e) if e else None for e in matches] self.all = False else: self.ranges = [[0, self.maxval]] else: print(f'The sequence specification "{spec}" is incorrect.\n\n{syntax_description}\n') sys.exit(1) def __str__(self): #subsets = "all" if self.all else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges]) subsets = "all" if False else ",".join([f"{e[0]}-{e[1]}" for e in self.ranges]) return f"{self.index}[{subsets}]" def __repr__(self): return self.__str__() def has(self, i): for start, end in self.ranges: if i >= start and i <= end: return True return False def items(self, invert=False): result = [] for start,end in self.ranges: for e in range(start, end+1): result.append((e, self.index) if invert else (self.index, e)) return result class Sequences: def __init__(self, count, maxval, sequence_specs): self.count = count self.maxval = maxval self.sequences = {} match_all = re.compile(r"\*(.*)").match(sequence_specs) match_some = re.compile(r"\[(\d+)-(\d+)\](.*)").match(sequence_specs) spec_list = [] if match_all: subseq = match_all.group(1) for i in range(count): spec_list.append(f"{i}{subseq}") elif match_some: start = int(match_some.group(1)) end = int(match_some.group(2)) subseq = match_some.group(3) for i in range(start, end+1): spec_list.append(f"{i}{subseq}") else: spec_list = sequence_specs.split() for specs in spec_list: for spec in self.parse_spec(specs): self.sequences[spec.index] = spec def __getitem__(self, index): return self.sequences.get(index) def has(self, index, subseq_index): return self[index].has(subseq_index) if self[index] else None def parse_spec(self, spec): named_spec = { "last" : [str(self.count-1)], "outer" : ["0", str(self.count-1)], "inner" : [str(e) for e in range(1, self.count - 1)], "all" : [str(e) for e in range(0, self.count)] }.get(spec) if named_spec is None: return [Sequence(self.count, self.maxval, spec)] else: return [Sequence(self.count, self.maxval, e) for e in named_spec] def items(self, invert=False): result = [] for seq in self.sequences: result += self.sequences[seq].items(invert) return set(result) if __name__ == "__main__": import pprint for s in [ Sequence(10, "0"), Sequence(10, "1"), Sequence(10, "2(2)"), Sequence(10, "3(2-)"), Sequence(10, "4(2-4)"), Sequence(10, "5(2-4)"), Sequence(10, "6(2-4,6)"), Sequence(10, "7(2,6-8)"), Sequence(10, "8(2,7-8,9-11,13-14)")]: print(s.spec, "->", s) print("Sequences") S = Sequences(10, "0") for name in "top bottom head outer inner all 1(2-3)".split(): print(name, "->", S.parse_spec(name)) print("Instantiate:") s = Sequences(10, "3(1-2,4-5) 4(8-9)") #print(s.items(True)) print(s) print(s.has(3,1))