Here is one approach. The following code defines the class, OneBasedList
implementing one-based indexing on a regular, mutable Python list
. The script after it consists of unit tests (see Chapter 10) to check it works.
class OneBasedList(list):
"""A class implementing a list with one-based indexing."""
def _resolve_index(self, i):
"""A helper function to resolve one-based list indexes to native ones."""
if i is None:
return None
if i == 0:
raise IndexError('No zero index for OneBasedList')
if i < 0:
return i
return i - 1
def _resolve_slice(self, sl):
"""A helper function to resolve one-based list slices to native ones."""
istart = self._resolve_index(sl.start)
istop = sl.stop
if sl.step and sl.step < 0:
istop = self._resolve_index(sl.stop)
return slice(istart, istop, sl.step)
def _resolve_slice_or_index(self, x):
"""
A helper function to resolve one-based list indexes or slices into
native, zero-based ones.
"""
if type(x) is slice:
return self._resolve_slice(x)
return self._resolve_index(x)
def __setitem__(self, i, e):
"""Item setting, either with either a one-based index or slice."""
return super().__setitem__(self._resolve_slice_or_index(i), e)
def __getitem__(self, i):
"""Item getting, either with either a one-based index or slice."""
if type(i) == slice:
# Take care to return another OneBasedList object.
return type(self)(super().__getitem__(self._resolve_slice(i)))
return super().__getitem__(self._resolve_index(i))
def __delitem__(self, i):
"""Delete item at (one-based) index or slice i."""
return super().__delitem__(self._resolve_slice_or_index(i))
def pop(self, i=None):
"""Pop an item from the end of the OneBasedList or at index i."""
if i is None:
return super().pop()
return super().pop(i-1)
def index(self, e, istart=None, istop=None):
"""
Obtain the index of element e, possibly constrained to within the
(one-based) index range istart to istop.
"""
sl = self._resolve_slice(slice(istart, istop, 1))
_istart = sl.start
_istop = sl.stop
if _istop is None:
if _istart is None:
return super().index(e) + 1
return super().index(e, _istart) + 1
return super().index(e, _istart, _istop) + 1
def copy(self):
"""Copy the OneBasedList, returning a new instance of this class."""
return type(self)(self)
def insert(self, i, e):
"""Insert e at (one-based) index i."""
_i = self._resolve_index(i)
super().insert(_i, e)
def __add__(self, other):
"""OneBasedList concatenation, returning another OneBasedList.
Note that adding a list to a OneBasedList returns a OneBasedList;
adding a OneBasedList to a list returns a list.
"""
return type(self)(super().__add__(other))
def __mul__(self, n):
"""OneBasedList concatenation by repetition n times."""
return type(self)(super().__mul__(n))
def __rmul__(self, n):
"""OneBasedList concatenation by repetition n times."""
return type(self)(super().__rmul__(n))
For example,
In [x]: lst1 = OneBasedList('ABC')
In [x]: lst1[1], lst1[2], lst1[3]
Out[x]: ('A', 'B', 'C')
Slicing, insertion, deletion, concatenation, pop
, etc. are all supported.
The test script: (save as one_based_list.py
).
from one_based_list import OneBasedList
import unittest
class TestOneBasedList(unittest.TestCase):
def test_assignment(self):
lst0 = ['a', 'b', 'c']
lst1 = OneBasedList(lst0)
self.assertEqual(str(lst1), str(lst0))
self.assertEqual(lst1[1], 'a')
self.assertEqual(lst1[3], 'c')
self.assertRaises(IndexError, lst1.__getitem__, 0)
self.assertEqual(lst1[-1], lst0[-1])
self.assertEqual(lst1[-3], lst0[-3])
self.assertRaises(IndexError, lst1.__getitem__, -4)
def test_slices(self):
lst0 = list('abcdef')
lst1 = OneBasedList('abcdef')
self.assertEqual(lst0[1:3], lst1[2:3])
self.assertEqual(lst0[1:], lst1[2:])
self.assertEqual(lst0[1:6:2], lst1[2:6:2])
self.assertEqual(lst0[:6], lst1[1:6])
self.assertEqual(lst0[:], lst1[:])
self.assertRaises(IndexError, lst1.__getitem__, slice(0,1))
self.assertEqual(lst0[-1:1:-1], lst1[-1:2:-1])
self.assertEqual(lst0[5:1:-2], lst1[6:2:-2])
self.assertRaises(IndexError, lst1.__setitem__, 0, 'x')
lst0[0] = 'z'
lst1[1] = 'z'
self.assertEqual(lst0, lst1)
lst0[-1] = 'q'
lst1[-1] = 'q'
self.assertEqual(lst0, lst1)
lst0[1:4] = 'STU'
lst1[2:4] = 'STU'
self.assertEqual(lst0, lst1)
lst0[-2::-1] = [1,2,3,4,5]
lst1[-2::-1] = [1,2,3,4,5]
self.assertEqual(lst0, lst1)
lst2 = lst1[:]
self.assertEqual(type(lst2), OneBasedList)
lst2 = lst1[1:3]
self.assertEqual(type(lst2), OneBasedList)
lst3 = OneBasedList('ABCDEFGHIJKL')
self.assertEqual(lst3[3:11:4], ['C','G','K'])
self.assertEqual(lst3[::3], ['A', 'D','G','J'])
self.assertEqual(lst3[::-3], ['L', 'I','F','C'])
def test_delete_items(self):
lst0 = list('abcdef')
lst1 = OneBasedList('abcdef')
del lst0[3]
del lst1[4]
self.assertEqual(lst0, lst1)
del lst0[2:]
del lst1[3:]
self.assertEqual(lst0, lst1)
def test_equality(self):
lst0 = list('ABCD')
lst1 = OneBasedList('ABCD')
self.assertEqual(lst1, lst1)
self.assertEqual(lst0, lst1)
lst2 = OneBasedList('ABCDE')
lst2.pop()
self.assertEqual(lst1, lst2)
def test_index(self):
lst1 = OneBasedList('abcd')
self.assertEqual(lst1.index('a'), 1)
self.assertRaises(ValueError, lst1.index, 'z')
lst2 = OneBasedList('abcdefabcdef')
self.assertEqual(lst2.index('a', 6), 7)
self.assertEqual(lst2.index('a', 6, 7), 7)
def test_pop(self):
lst1 = OneBasedList('abcdefgh')
lst1.pop()
self.assertEqual(lst1, list('abcdefg'))
lst1.pop(3)
self.assertEqual(lst1, list('abdefg'))
def test_copy(self):
lst1 = OneBasedList('abcdefgh')
lst2 = lst1.copy()#[:]
self.assertEqual(type(lst2), OneBasedList)
def test_clear(self):
lst = OneBasedList('ABCD')
lst.clear()
self.assertEqual(type(lst), OneBasedList)
def test_insert(self):
lst0 = list('ABCD')
lst1 = OneBasedList('ABCD')
lst0.insert(1, 'z')
lst1.insert(2, 'z')
self.assertEqual(lst0, lst1)
lst0.insert(-1, 'q')
lst1.insert(-1, 'q')
self.assertEqual(lst0, lst1)
def test_concatenate(self):
lst1 = OneBasedList('abcd')
lst2 = OneBasedList('efgh')
self.assertEqual(lst1 + lst2, list('abcdefgh'))
self.assertEqual(type(lst1 + lst2), OneBasedList)
self.assertEqual(type(lst1 + [1, 2, 3]), OneBasedList)
self.assertEqual(type([1, 2, 3] + lst1), list)
self.assertEqual(lst1*3, list('abcdabcdabcd'))
self.assertEqual(type(lst1*3), OneBasedList)
self.assertEqual(2*lst1, list('abcdabcd'))
self.assertEqual(type(2*lst1), OneBasedList)
def test_extend(self):
lst1 = OneBasedList('abcd')
lst2 = OneBasedList('efgh')
lst1.extend(lst2)
self.assertEqual(lst1, list('abcdefgh'))
self.assertEqual(type(lst1), OneBasedList)
unittest.main()