Skip to content

Commit ec9c023

Browse files
committed
Move more code to using structural pattern matching
1 parent d62297b commit ec9c023

File tree

3 files changed

+135
-135
lines changed

3 files changed

+135
-135
lines changed

Stoner/core/base.py

-1
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,6 @@ def __mungevalue(self, typ: str, value: Any) -> Any:
426426
are tested in turn and if the type string matches the constructor of
427427
the associated python class is called with value as its argument.
428428
"""
429-
ret = None
430429
if typ == "Invalid Type": # Short circuit here
431430
return repr(value)
432431
for regexp, valuetype in self.__tests:

Stoner/core/setas.py

+134-130
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
__all__ = ["setas"]
55
import re
66
import copy
7-
from collections.abc import MutableMapping, Mapping
7+
from collections.abc import MutableMapping, Mapping, Iterable
88

99
import numpy as np
1010

@@ -137,15 +137,15 @@ def _prepare_call(self, args, kargs):
137137
@property
138138
def _size(self):
139139
"""Calculate a size of the setas attribute."""
140-
if len(self._shape) == 1 and self._row:
141-
c = self._shape[0]
142-
elif len(self._shape) == 1:
143-
c = 1
144-
elif len(self._shape) > 1:
145-
c = self.shape[1]
146-
else:
147-
c = len(self._column_headers)
148-
return c
140+
match len(self.shape):
141+
case 1 if self._row:
142+
return self._shape[0]
143+
case 1:
144+
return 1
145+
case 0:
146+
return len(self._column_headers)
147+
case _:
148+
return self.shape[1]
149149

150150
@property
151151
def _unique_headers(self):
@@ -382,30 +382,32 @@ def __getitem__(self, name):
382382
Either a single letter x,y,z,u,v,w,d,e or f, or a list of letters if used in
383383
list mode, or a single coliumn name or list of names if used in dictionary mode.
384384
"""
385-
if isinstance(name, string_types) and len(name) == 1 and name in "xyzuvwdef.-":
386-
ret = self.to_dict()[name]
387-
if len(ret) == 1:
388-
ret = ret[0]
389-
elif isinstance(name, string_types) and len(name) == 2 and name[0] == "#" and name[1] in "xyzuvwdef.-":
390-
ret = list()
391-
name = name[1]
392-
s = 0
393-
while name in self._setas[s:]:
394-
s = self._setas.index(name) + 1
395-
ret.append(s - 1)
396-
if len(ret) == 1:
397-
ret = ret[0]
398-
elif isinstance(name, index_types):
399-
ret = self.setas[self.find_col(name)]
400-
elif isinstance(name, slice):
401-
indices = name.indices(len(self.setas))
402-
name = range(*indices)
403-
ret = [self[x] for x in name]
404-
elif isiterable(name):
405-
ret = [self[x] for x in name]
406-
else:
407-
raise IndexError(f"{name} was not found in the setas attribute.")
408-
return ret
385+
match name:
386+
case "x" | "y" | "z" | "u" | "v" | "w" | "d" | "e" | "f" | "." | "-":
387+
ret = self.to_dict()[name]
388+
if len(ret) == 1:
389+
ret = ret[0]
390+
return ret
391+
case "#x" | "y#" | "#z" | "#u" | "#v" | "#w" | "#d" | "#e" | "#f":
392+
ret = list()
393+
name = name[1]
394+
s = 0
395+
while name in self._setas[s:]:
396+
s = self._setas.index(name) + 1
397+
ret.append(s - 1)
398+
if len(ret) == 1:
399+
ret = ret[0]
400+
return ret
401+
case int() | str() | _pattern_type():
402+
return self.setas[self.find_col(name)]
403+
case slice():
404+
indices = name.indices(len(self.setas))
405+
name = range(*indices)
406+
return [self[x] for x in name]
407+
case _ if isiterable(name):
408+
return [self[x] for x in name]
409+
case _:
410+
raise IndexError(f"{name} was not found in the setas attribute.")
409411

410412
def __iter__(self):
411413
"""Iterate over thew column assignments.
@@ -432,26 +434,22 @@ def __setitem__(self, name, value):
432434
a single letter string in the set above.
433435
value (integer or column index): See above.
434436
"""
435-
if isLikeList(name): # Sipport indexing with a list like object
436-
if isLikeList(value) and len(value) == len(name):
437-
for n, v in zip(name, value):
438-
self._setas[n] = v
439-
else:
440-
for n in name:
441-
self[n] = value
442-
elif isinstance(name, string_types) and len(name) == 1 and name in "xyzuvwdef.-": # indexing by single letter
443-
for c in self.find_col(value, force_list=True):
444-
self._setas[c] = name
445-
elif (
446-
isinstance(name, index_types)
447-
and isinstance(value, string_types)
448-
and len(value) == 1
449-
and value in "xyzuvwdef.-"
450-
):
451-
for c in self.find_col(name, force_list=True):
452-
self.setas[c] = value
453-
else:
454-
raise IndexError(f"Failed to set setas as couldn't workout what todo with setas[{name}] = {value}")
437+
match name:
438+
case Iterable() if not isinstance(name, str):
439+
if isLikeList(value) and len(value) == len(name):
440+
for n, v in zip(name, value):
441+
self._setas[n] = v
442+
else:
443+
for n in name:
444+
self[n] = value
445+
case "x" | "y" | "z" | "u" | "v" | "w" | "d" | "e" | "f" | "." | "-":
446+
for c in self.find_col(value, force_list=True):
447+
self._setas[c] = name
448+
case int() | str() | _pattern_type() if value in [letter for letter in "xyzuvwdef.-"]:
449+
for c in self.find_col(name, force_list=True):
450+
self.setas[c] = value
451+
case _:
452+
raise IndexError(f"Failed to set setas as couldn't workout what todo with setas[{name}] = {value}")
455453

456454
def __len__(self):
457455
"""Return our own length."""
@@ -503,48 +501,54 @@ def __iadd__(self, other):
503501

504502
def _sub_core_(self, new, other):
505503
"""Implement subtracting either column indices or x,y,z,d,e,f,u,v,w for the current setas."""
506-
if isinstance(other, string_types) and len(other) == 1 and other in "xyzuvwdef":
507-
while True:
508-
try:
509-
new._setas[new._setas.index(other)] = "."
510-
except ValueError:
511-
break
512-
return new
513-
if isinstance(other, index_types):
504+
if isinstance(other, str):
514505
try:
515-
new._setas[new.find_col(other)] = "."
516-
return new
506+
other = self.find_col(other)
517507
except KeyError:
518-
other = new.clone(other, _self=True)
519-
520-
if isinstance(other, Mapping):
521-
me = new.to_dict()
522-
other = new.clone(other, _self=True).to_dict()
523-
for k, v in other.items():
524-
v = [v] if not isinstance(v, list) else v
525-
if k in me:
526-
for header in v:
527-
if header in me[k]:
528-
if isinstance(me[k], list):
529-
me[k].remove(header)
508+
other = decode_string(other)
509+
match other:
510+
case "x" | "y" | "z" | "u" | "v" | "w" | "d" | "e" | "f":
511+
while other in new._setas:
512+
new._setas[new._setas.index(other)] = "."
513+
return new
514+
case dict():
515+
me = new.to_dict()
516+
other = new.clone(other, _self=True).to_dict()
517+
for k, v in other.items():
518+
v = [v] if not isinstance(v, list) else v
519+
if k in me:
520+
for header in v:
521+
if header in me[k]:
522+
if isinstance(me[k], list):
523+
me[k].remove(header)
524+
else:
525+
me[k] = ""
526+
if len(me[k]) == 0:
527+
del me[k]
530528
else:
531-
me[k] = ""
532-
else:
533-
raise ValueError(f"{header} is not set as {k}")
534-
if len(me[k]) == 0:
535-
del me[k]
536-
else:
537-
raise ValueError(f"No column is set as {k}")
538-
new.clear()
539-
new(me)
540-
return new
541-
if isiterable(other):
542-
for o in other:
543-
new = self._sub_core_(new, o)
544-
if new is NotImplemented:
545-
return NotImplemented
546-
return new
547-
return NotImplemented
529+
raise ValueError(f"{header} is not set as {k}")
530+
else:
531+
raise ValueError(f"No column is set as {k}")
532+
new.clear()
533+
new(me)
534+
return new
535+
case Iterable() if all(isinstance(x, (str, int, slice, list, re.Pattern)) for x in other):
536+
for o in other:
537+
if o == other:
538+
continue
539+
new = self._sub_core_(new, o)
540+
if new is NotImplemented:
541+
return NotImplemented
542+
return new
543+
case int() | slice() | list() | re.Pattern():
544+
try:
545+
new._setas[new.find_col(other)] = "."
546+
return new
547+
except KeyError:
548+
other = new.clone(other, _self=True)
549+
550+
case _:
551+
return NotImplemented
548552

549553
def __sub__(self, other):
550554
"""Jump to the core."""
@@ -553,8 +557,7 @@ def __sub__(self, other):
553557

554558
def __isub__(self, other):
555559
"""Jump to the core."""
556-
new = self
557-
return self._sub_core_(new, other)
560+
return self._sub_core_(self, other)
558561

559562
def find_col(self, col, force_list=False):
560563
"""Indexes the column headers in order to locate a column of data.shape.
@@ -581,43 +584,44 @@ def find_col(self, col, force_list=False):
581584
Returns:
582585
The matching column index as an integer or a KeyError
583586
"""
584-
if isinstance(col, int_types): # col is an int so pass on
585-
if col >= len(self.column_headers):
586-
raise IndexError(f"Attempting to index a non - existent column {col}")
587-
if col < 0:
588-
col = col % len(self.column_headers)
589-
elif isinstance(col, string_types): # Ok we have a string
590-
col = str(col)
591-
if col in self.column_headers: # and it is an exact string match
592-
col = self.column_headers.index(col)
593-
else: # ok we'll try for a regular expression
594-
test = re.compile(col)
587+
match col:
588+
case int():
589+
if col >= len(self.column_headers):
590+
raise IndexError(f"Attempting to index a non - existent column {col}")
591+
if col < 0:
592+
col = col % len(self.column_headers)
593+
case str():
594+
col = str(col)
595+
if col in self.column_headers: # and it is an exact string match
596+
col = self.column_headers.index(col)
597+
else: # ok we'll try for a regular expression
598+
test = re.compile(col)
599+
possible = [x for x in self.column_headers if test.search(x)]
600+
if not possible:
601+
try:
602+
col = int(col)
603+
except ValueError as err:
604+
raise KeyError(
605+
f'Unable to find any possible column matches for "{col} in {self.column_headers}"'
606+
) from err
607+
if col < 0 or col >= self.data.shape[1]:
608+
raise KeyError("Column index out of range")
609+
else:
610+
col = self.column_headers.index(possible[0])
611+
case _pattern_type():
612+
test = col
595613
possible = [x for x in self.column_headers if test.search(x)]
596614
if not possible:
597-
try:
598-
col = int(col)
599-
except ValueError as err:
600-
raise KeyError(
601-
f'Unable to find any possible column matches for "{col} in {self.column_headers}"'
602-
) from err
603-
if col < 0 or col >= self.data.shape[1]:
604-
raise KeyError("Column index out of range")
605-
else:
606-
col = self.column_headers.index(possible[0])
607-
elif isinstance(col, _pattern_type):
608-
test = col
609-
possible = [x for x in self.column_headers if test.search(x)]
610-
if not possible:
611-
raise KeyError(f"Unable to find any possible column matches for {col.pattern}")
612-
col = self.find_col(possible)
613-
elif isinstance(col, slice):
614-
indices = col.indices(self.shape[1])
615-
col = range(*indices)
616-
col = self.find_col(col)
617-
elif isiterable(col):
618-
col = [self.find_col(x) for x in col]
619-
else:
620-
raise TypeError(f"Column index must be an integer, string, list or slice, not a {type(col)}")
615+
raise KeyError(f"Unable to find any possible column matches for {col.pattern}")
616+
col = self.find_col(possible)
617+
case slice():
618+
indices = col.indices(self.shape[1])
619+
col = range(*indices)
620+
col = self.find_col(col)
621+
case _ if isiterable(col):
622+
col = [self.find_col(x) for x in col]
623+
case _:
624+
raise TypeError(f"Column index must be an integer, string, list or slice, not a {type(col)}")
621625
if force_list and not isinstance(col, list):
622626
col = [col]
623627
return col

Stoner/core/utils.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,7 @@ class tab_delimited(csv.Dialect):
211211
def decode_string(value: str) -> str:
212212
"""Expand a string of column assignments, replacing numbers with repeated characters."""
213213
pattern = re.compile(r"(([0-9]+)(x|y|z|d|e|f|u|v|w|\.|\-))")
214-
while True:
215-
res = pattern.search(value)
216-
if res is None:
217-
break
214+
while res := pattern.search(value):
218215
(total, count, code) = res.groups()
219216
count = int(count)
220217
value = value.replace(total, code * count, 1)

0 commit comments

Comments
 (0)