12

I've downloaded from a supposedly serious source a sage script. It doesn't work on my computer, and a quick debugging showed that a problem came from the fact that at some point, the authors were doing as if a n-element list was numbered from 1 to n (whereas the “normal” numbering in Python and (thus) sage is 0..n-1).

What am I missing? Is there a global variable hidden somewhere that changes this convention, like in APL?

Thanks for your help (I hope my question is clear despite my feeble grasp of both English and CSish...)

  • 2
    could you at least post the relevant part of the code? – steabert Aug 30 '11 at 07:07
  • 3
    the (1..n) notation seems to be a Sage specific writing (http://stackoverflow.com/questions/3511699/python-1-n-syntax) – Cédric Julien Aug 30 '11 at 07:49
  • 1
    @CédricJulien Lists are indexed starting at 0 in Sage as well because it is based on Python. The link you give is a way of making a list... such as [6..12] is the list [6, 7, 8, 9, 10, 11, 12]. But, the indices of the items in this list would be 0, 1, 2, 3, 4, 5, 6. Again, it's a way of making a list, not a way of reindexing a list. – GeoffDS Dec 22 '11 at 03:23

5 Answers5

8

Python (and therefore sage) lists are always numbered from 0, and there isn't a way to change that.

Looking at CPython's source, in http://hg.python.org/cpython/file/70274d53c1dd/Objects/listobject.c on line 449:

static PyObject *
list_item(PyListObject *a, Py_ssize_t i)
{
    if (i < 0 || i >= Py_SIZE(a)) {
        if (indexerr == NULL) {
            indexerr = PyString_FromString(
                "list index out of range");
            if (indexerr == NULL)
                return NULL;
        }
        PyErr_SetObject(PyExc_IndexError, indexerr);
        return NULL;
    }
    Py_INCREF(a->ob_item[i]);
    return a->ob_item[i];
}

The item lookup delegates straight to the underlying C array, and C arrays are always zero-based. So Python lists are always zero-based as well.

javawizard
  • 1,277
  • 1
  • 12
  • 17
5

A simple class that shifts the index for you provides a clean interface to something reusable.

class Array(object):

    def __init__(self, items: list) -> None:
        self.items = items

    def __repr__(self) -> str:
        return '{}({})'.format(self.__class__.__name__, self.items)

    def __len__(self) -> int:
        return len(self.items)

    def __contains__(self, item: any) -> bool:
        return item in self.items

    def __getitem__(self, key: int) -> any:
        return self.items[key - 1]

    def __setitem__(self, key: int, value: any) -> None:
        self.items[key - 1] = value

    def __delitem__(self, key: int) -> None:
        del self.items[key - 1]
kautenja
  • 139
  • 2
  • 6
  • This is merely a very weak list wrapper though that isn't able to run any actual list functionality. Subclassing collections.abc.MutableSequence would be a more apt way of doing this imo. – upgrd Nov 20 '20 at 16:08
  • 1
    @upgrd feel free to upload a better implementation... – kautenja Nov 20 '20 at 16:33
  • This is my solution: https://stackoverflow.com/a/64935398/6455731 ; not really happy with it though.. :( – upgrd Nov 20 '20 at 19:03
3

Well I too was facing the same idea on how to implement the method of indexing to be start from 1. I wanted to implement the Insertion Sort Algorithm which is as follows: Insertion Sort Algorithm

As we already know python list start from 0, what I did was following:

A = ['dummy',5,2,6,4,1,3]
for j in range(2,len(A)):
    key = A[j]
    i=j-1
    while i>0 and A[i]>key:
        A[i+1] = A[i]
        i = i-1
    A[i+1] = key
A.pop(0)
print A

I Just added a 'Dummy' in index 0, did all the work like in Algorithm and removed the 'dummy' again. This was just a cheating method.

Mahadeva
  • 1,584
  • 4
  • 23
  • 56
0
In [1]: index_0 = ['foo', 'bar', 'quux']

In [2]: index_1 = [None] + index_0

In [3]: index_1[1]
Out[3]: 'foo'

In [4]: index_1[1:]
Out[4]: ['foo', 'bar', 'quux']
Brian Edwards
  • 311
  • 3
  • 5
0

I would suggest subclassing e.g. collections.abc.MutableSequence for something like this, because once the protocol (in this case: __getitem__, __setitem__, __delitem__, __len__, insert) is implemented all list methods should work on the custom sequence type.

The solution I came up with uses collections.abc.MutableSequence with a list wrapper (_lst) and a helper class component that doesn't know much about anything except that it is subscriptable, i.e. it implements __getitem__ which handles the index modification.

import collections.abc

class _IndexComponent:
    def __getitem__(self, index):
        if index == 0: raise IndexError("Index 0 is a lie.")
        if index > 0: return index -1
        else: return index
        
class OneList(collections.abc.MutableSequence):

    def __init__(self, init: list = None) -> None:
        self._index_comp = _IndexComponent()
        self._lst = []
        if not init is None: # and isinstance(init, list)?
            self._lst.extend(init)
    
    def __getitem__(self, index: int) -> any:
        return self._lst[self._index_comp[index]]

    def __setitem__(self, index: int, val: any) -> None:
        self._lst[self._index_comp] = val

    def __delitem__(self, index: int) -> None:
        del self._lst[self._index_comp[index]]

    def __len__(self) -> int:
        return len(self._lst)

    def insert(self, index: int, val: any) -> None:
        self._lst.insert(self._index_comp[index], val)

    def __repr__(self) -> str:
        return f"{self._lst}"

Now for example pop works although it isn't explicitly implemented:

ol = OneList([1,2,3,4])
print(ol.pop(1))
ol.pop(0) # IndexError

Somehow this feels kind of messy though, I would appriciate if someone shared a better solution.

l = [] l.extend([]) print(l)

upgrd
  • 720
  • 7
  • 16