views:

113

answers:

5

Hello, I'm trying to represent an array of evenly spaced floats, an arithmetic progression, starting at a0 and with elements a0, a0 + a1, a0 + 2a1, a0 + 3a1, ... This is what numpy's arange() method does, but it seems to allocate memory for the whole array object and I'd like to do it using an iterator class which just stores a0, a1 and n (the total number of elements, which might be large). Does anything that does this already exist in the standard Python packages? I couldn't find it so, ploughed ahead with:

class mylist():
    def __init__(self, n, a0, a1):
        self._n = n
        self._a0 = a0
        self._a1 = a1

    def __getitem__(self, i):
        if i < 0 or i >= self._n:
            raise IndexError
        return self._a0 + i * self._a1

    def __iter__(self):
        self._i = 0
        return self

    def next(self):
        if self._i >= self._n:
            raise StopIteration
        value = self.__getitem__(self._i)
        self._i += 1
        return value

Is this a sensible approach or am I revinventing the wheel?

+1  A: 

Only for reasons that are probably obvious to someone out there, iterating over mylist: for i,x in enumerate(a): print i,a doesn't return the values I expect but just a whole lot of references to the mylist instance. What am I doing wrong?

The culprit is print i,a. You are printing a, which is an array. You ought to print x instead. Chane this line to:

print i,x

Also a couple of things:

  1. Change you class name to TitleCase. For e.g. class MyList or class Mylist.
  2. It is a good idea to inherit from object if you are using Python 2.x. So class MyList(object): ...
Manoj Govindan
Good point! Post edited!
A: 

You're enumerating over a, so printing it will print a lot of references to it. Print x instead.

Ignacio Vazquez-Abrams
+3  A: 

Well, one thing that you are doing wrong is that it should be for i, x in enumerate(a): print i, x.

Also, I'd probably use a generator method instead of the hassle with the __iter__ and next() methods, especially because your solution wouldn't allow you to iterate over the same mylist twice at the same time with two different iterators (as self._i is local to the class).

This is probably a better solution which gives you random access as well as an efficient iterator. The support for the in and len operators are thrown in as a bonus :)

class mylist(object):
    def __init__(self, n, a0, a1, eps=1e-8):
        self._n = n
        self._a0 = a0
        self._a1 = a1
        self._eps = eps

    def __contains__(self, x):
        y = float(x - self._a0) / self._a1
        return 0 <= int(y) < self._n and abs(y - int(y)) < self._eps

    def __getitem__(self, i):
        if 0 <= i < self._n:
            return self._a0 + i * self._a1
        raise IndexError

    def __iter__(self):
        current = self._a0
        for i in xrange(self._n):
             yield current
             current += self._a1

    def __len__(self):
        return self._n
Tamás
your membership test is broken due to floating point arithmetic. Try something like: `return abs(y - int(y)) < .0001 and 0 <= int(y) < self._n` the magic number `.0001` should probably be smaller and configurable.
aaronasterling
Yes, you're right, fixed.
Tamás
there's still a possibility that `y` will compare as greater than `self._n` due to floating point issues when it really shouldn't. Similarly for less than zero. You can dodge that by converting it to an `int`
aaronasterling
Thanks for your help. I can see a potential problem with __iter__ though if a0 is large and a1 very small... if I repeatedly add a very small quantity to a large one won't I get bitten by round-off sooner or later?
@AaronMcSmooth: thanks, I've added that as well.
Tamás
@user293594: probably yes. If you expect such use-cases, it's better to use multiplication in `__iter__` as you did in your original `__getitem__` method.
Tamás
+3  A: 

Other answers answer the immediate problem. Note that if all you want is an iterator and you don't need random access, there's no need to write a whole iterator class.

def mylist(n, a0, a1):
    for i in xrange(n):
        yield a0 + i*a1
Glenn Maynard
There's no need to write anything...`itertools.count()` already exists.
Tim Pietzcker
@Tim: If you happen to be in 2.7, though the real point was showing the use of `yield` which the OP may not be aware of--it's really not too often that you actually need to write an iterator class manually.
Glenn Maynard
You're right, although `itertools.count()` has been there since 2.3; before 2.7 you do need to implement the `step` parameter manually.
Tim Pietzcker
I think we can infer from the presence of `__getitem__` that OP _does_ want random access.
aaronasterling
@AaronMcSmooth: Not at all. Extranneous methods are often added because they seem like a good idea for "completeness", not because they're actually needed.
Glenn Maynard
A: 

Yes, there's a built-in generator for this (Python 2.7 and up):

import itertools
mygen = itertools.count(a0,a1)

If you don't have Python 2.7 yet (Python 2.4 and up):

import itertools
mygen = (a0 + a1*i for i in itertools.count())
Tim Pietzcker
if you look at the class though, OP does seem to want random access
aaronasterling
@AaronMcSmooth: You're right. I was thrown off by "I'd like to do it using an iterator class"...
Tim Pietzcker