forked from sanger-archive/aker-material-service
-
Notifications
You must be signed in to change notification settings - Fork 0
/
addresser.py
102 lines (89 loc) · 3.79 KB
/
addresser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
def index_to_address_part(index, alpha):
"""Return the row/column part of an address using the given index (from zero),
either 0->"A", 1->"B" or 0->"1", 1->"2", ...
"""
if alpha:
return chr(ord('A')+index)
else:
return str(index+1)
def address_part_to_index(part, alpha):
"""Convert the letter or number from part of an address to a zero-based index.
Return None if the conversion is impossible.
"""
if alpha:
if 'A'<=part<='Z':
return ord(part)-ord('A')
else:
if part.isdigit():
return int(part)-1
class Addresser(object):
"""Class for converting to/from container slot addresses."""
def __init__(self, num_rows, num_cols, row_is_alpha, col_is_alpha, separator=':'):
"""Initialise the addresser with given dimensions and conversion rules."""
self.num_rows = num_rows
self.num_cols = num_cols
self.row_is_alpha = row_is_alpha
self.col_is_alpha = col_is_alpha
self.separator = separator
def __len__(self):
return self.num_rows*self.num_cols
@property
def is_numeric(self):
return not (self.col_is_alpha or self.row_is_alpha)
def index_to_address(self, index):
"""Convert the given index to an address."""
if not 0 <= index < len(self):
raise IndexError("Index out of address range: %s", index)
if self.is_numeric:
return str(index+1)
row = index//self.num_cols
col = index%self.num_cols
return self.separator.join([
index_to_address_part(i,a)
for i,a in ((row, self.row_is_alpha), (col, self.col_is_alpha))
])
__getitem__ = index_to_address
def __contains__(self, address):
"""Is the given address valid for this addresser?"""
if self.is_numeric:
return address.isdigit() and 1<=int(address)<=len(self)
if self.separator not in address:
return False
r,c = address.split(self.separator, 1)
ri, ci = (address_part_to_index(i, a)
for i,a in ((r, self.row_is_alpha), (c, self.col_is_alpha)))
return (ri is not None and ci is not None
and 0 <= ri < self.num_rows and 0 <= ci < self.num_cols)
def index(self, address):
"""Convert the given address (a string) to a 0-based index.
Raises a ValueError if the address cannot be converted."""
if isinstance(address, unicode):
address = str(address)
elif not isinstance(address, str):
raise TypeError("Address must be a string")
if self.is_numeric:
if not address.isdigit():
raise ValueError("Invalid address format: %r"%address)
i = int(address)-1
if not 0 <= i < len(self):
raise ValueError("Address out of range: %r"%address)
return i
if self.separator not in address:
ri = ci = None
else:
r, c = address.split(self.separator, 1)
ri, ci = (address_part_to_index(i, a)
for i,a in ((r, self.row_is_alpha), (c, self.col_is_alpha)))
if ri is None or ci is None:
raise ValueError("Invalid address format: %r"%address)
if not 0 <= ri < self.num_rows:
if not 0 <= ci < self.num_cols:
raise ValueError("Row and column out of range: %r"%address)
raise ValueError("Row out of range: %r"%address)
if not 0 <= ci < self.num_cols:
raise ValueError("Column out of range: %r"%address)
return ri*self.num_cols + ci
def __repr__(self):
return 'Addresser(num_rows=%s, num_cols=%s, row_is_alpha=%s, col_is_alpha=%s)'%(
self.num_rows, self.num_cols, self.row_is_alpha, self.col_is_alpha
)