2

I would like to create a list which contains an initial point x = [x0 x1] and perturbed points y1 = [x0+h x1], y2 = [x0-h x1], y3 = [x0 x1+h], etc. I have tried this:

xs = [x]*5;
for i in range(len(x)):
    if isinstance(h, float):
        xs[2*i+1][i] -= h;
        xs[2*i+2][i] += h;

But this appears to change the elements back and forth as it runs through the loop.

Remi Guan
  • 21,506
  • 17
  • 64
  • 87
Gregory
  • 341
  • 1
  • 2
  • 10
  • 3
    `[x]*5` makes a list where all items are a reference to the same list. change that to `[x[:] for i in range(5)]` to create a copy of each list – R Nar Nov 20 '15 at 21:35
  • Your question does not make sense to me. You start by saying you havea list with a single element, I guess (does `[x0 x1]` mean `[(x0, x1)]`?), then your proposed code tries to reference a nested list? – Two-Bit Alchemist Nov 20 '15 at 21:36
  • Do you need a function for points in a plane (i.e. with 2 coordinates), as your explanation suggests; or points with an arbitrary number of coordinates, as your "example" code (which has a lot of errors) seems to suggest? – LeartS Nov 20 '15 at 21:40
  • @RNar, thanks for the explanation. It can be arbitrary. The answer by LeartS addresses my question though I understand that it was filled with errors (I'm new to python so I didn't realize that making those copies just referenced the same list). – Gregory Nov 20 '15 at 22:20
  • It's a common gotcha for python beginners (and not-so-beginners). – LeartS Nov 20 '15 at 22:22

2 Answers2

2

If I interpreted correctly what you wanted to do from your explanation and code:

def perturbate(x, h):
    """
    Given an initial point x = [x0, x1, ..., xn]
    Returns a list of points containing the original point and,
    for every coordinate, "perturbations" of that point created
    by adding and subtracting 'h' to the coordinate.
    """
    # start with the original point plus
    # 2 copies of it for every coordinate
    points = [x[:] for i in range(len(x)*2 + 1)]

    for coordinate_index in range(len(x)):
        points[coordinate_index*2 + 1][coordinate_index] -= h
        points[coordinate_index*2 + 2][coordinate_index] += h
    return points

Which gives:

>>> perturbate([1.0,2.0], 1.0)
[[1.0, 2.0], [0.0, 2.0], [2.0, 2.0], [1.0, 1.0], [1.0, 3.0]]

So basically your code was almost correct (but not really clear, the generic-named variables don't help), except for the "results" inizialization: when you do [x] * 5, where x is a mutable object, you're creating a list of 5 times the same object. If you change a property of that object, it will change in all the indices, because they reference the same object!

>>> a = [[0,0]] * 5
>>> a
[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
>>> a[0][0] = 1
>>> a
[[1, 0], [1, 0], [1, 0], [1, 0], [1, 0]]
>>>

Also the hardcoded 5 assumes 2-dimensional points. But if we assume 2 dimensional points, than why using the len of the point instead of just using a surely simpler and clearer "hardcoded" implementation?

LeartS
  • 2,866
  • 2
  • 25
  • 45
  • I gave a simple example of a more general code, thats why I had len(x), but it could have just been 2, you're correct. And thanks for the quick response this was very helpful! – Gregory Nov 20 '15 at 22:17
2

I submit that the accepted answer to this is needlessly complicated which makes it difficult to reason about and therefore more difficult to debug and maintain. It is also more complex since it needlessly prefills a list which it then has to iterate over.

An somewhat less complicated general solution like the above would be as follows:

def perturbate(x, h):
    perturbations = [x]  # initialize with original point first
    for i, _ in enumerate(x):   # no range(len())
        shifted_down = x[:]
        shifted_down[i] -= h
        perturbations.append(shifted_down)
        shifted_up = x[:]
        shifted_up[i] += h
        perturbations.append(shifted_up)
    return perturbations

Aside from that, this general solution supporting up to n-dimensional points is far less readable than just spelling it out if you're working in 2D:

def perturbate(point, h):
    x, y = point
    return [[x, y], [x-h, y], [x+h, y], [x, y-h], [x, y+h]]

If this is all you need, this two-line function is infinitely more readable and understandable than either of the monstrosities above.

Remember:

Two-Bit Alchemist
  • 17,966
  • 6
  • 47
  • 82
  • First, my code was based on his code, as I wanted to show where the error was in his code. And also because the question was not that clear before the edits and the additional comments, so one had to guess what he wanted to do from his code. Second, "my" (actually his) solution is not O(N^2), it's O(n). Third, we can all agree that for 2D points an hardcoded solution is a lot more readable and simpler than a general solution, as I said myself in my answer. – LeartS Nov 24 '15 at 11:14
  • You say OP's code is O(N^2), which is wrong. You say to not use `xrange(len)` but then you use an `enumerate` that considers only the index (https://stackoverflow.com/questions/11901081/only-index-needed-enumerate-or-xrange). And, most importantly, **you do not address OP's problem, i.e. the only thing objectively wrong in his code**. Instead you answered with basically a code review. My answer has been downvoted too, and yet it explained (with examples) the error in OP's code, provided a working code (based on OP's code) and it provided your same useful pointers, 1 day before. Go figure. – LeartS Nov 24 '15 at 15:18
  • The accepted answers prefers enumerate for general reasons that don't apply here, the top answer by number of upvotes prefers xrange because more explicit. Regarding complexity, if you consider copying a point O(N), then both algorithms are O(N^2). Your algorithm also does `x[:]` inside the loop! In fact, I tested both with 10,100,1000,10000 coordinates and both perform the same (very poorly!) Anyway, you are right, your answer, while I still feel it doesn't really "implicitly show the error", does not deserve downvote. If you edit it I can change the vote. – LeartS Nov 24 '15 at 18:57
  • @LeartS You're right about the complexity. I've changed my answer to remove the bits about time complexity accordingly. I should have revisited the code before continuing to assert I was right the first time. Other than that, I see no point to continuing this argument, other than creating a very long comment chain. I'm not deleting my answer, and I'm not that concerned over its score. I also can't change my vote on yours whether I want or not, so let's leave it at that. – Two-Bit Alchemist Nov 24 '15 at 20:02
  • Oh, one more thing: in defense of my use of enumerate, it was not an arbitrary choice here. I chose it specifically to avoid having to do things like `points[coordinate_index*2 + 1][coordinate_index] -= h` after already having `[x[:] for i in range(len(x)*2 + 1)]`. Personally I think it's much easier to tell what's going on when you don't have these complicated mathematical formulae for indexing that changes every iteration. – Two-Bit Alchemist Nov 24 '15 at 20:05