tags:

views:

616

answers:

9

Let's say I have a list like:

my_list = [[1,2,3],[4,5,6],[7,8,9]]

How do I alter every value in the list without doing?:

for x in range(0, 3):
    for y in range(0, 3):
        my_list[x][y] = -my_list[x][y]

I have tried to simplify this by doing

my_list = [[[-a, -b, -c] for [a, b, c] in d] for d in my_list]

but the values remain the same.

+4  A: 

By "alter" I assume you mean "negate" (but you should have said that).

I notice that you're iterating over each element of a two-dimensional array (a list of lists) and treating each element as a list of three elements... but in fact each element is just a number, in your question as stated. So I would do something like this:

my_list = [[-n for n in l] for l in my_list]
David Zaslavsky
+3  A: 

Try this:

my_list = [[-a, -b, -c] for [a, b, c] in my_list]

Note that this creates a new list, rather than altering the list.

+1  A: 

As others have noted, there are two "levels" of lists and one "level" of ints, so two loops gives you an int, not another list.

For comparison, the loop version should be:

for x in range(0, 3):
    for y in range(0, 3):
        my_list[x][y] = -my_list[x][y]

That also fixes the bounds for your range. The upper bound is exclusive, not inclusive.

Nikhil Chelliah
Thank you for clarifying the range bounds.
jsumners
+1  A: 

Although Constantin's answer is correct, I would make two improvements:

  1. Generality is not always the best way. What if the altering function should work on lists?
  2. Using enumerate and indexing is not as fast as creating a list copy and assigning it in-place using [:] slicing.

So here's my alternative, which also modifies the list and all inner lists in-place:

def lst_apply(lst, func, lvl):
    if lvl:
        for x in lst:
            lst_apply(x, func, lvl - 1)
    else:
        lst[:] = [func(x) for x in lst]

>>> lst_apply(my_list, lambda x: -x, 1)
>>> my_list
[[-1, -2, -3], [-4, -5, -6], [-7, -8, -9]]

I still believe, though, that the best approach here is:

def simple_apply(lst, func):
    lst[:] = [[func(x) for x in y] for y in lst]

timeit results:

  • simple_apply: 4.0s
  • lst_apply: 5.4s
  • alter_elements: 11.5s
DzinX
+5  A: 

It's possible to code a more general solution to this problem. The following works in Python 3.0, regardless of the level of nesting.

Let's define recursive_map:

import collections

def recursive_map(f, iterable):
    for e in iterable:
        if isinstance(e, collections.Iterable):
            yield recursive_map(f, e)
        else:
            yield f(e)

Now, the requested negation function can be coded as follows:

import functools
import operator

negate = functools.partial(recursive_map, operator.neg)

Thus, for some collection of arbitrarily nested iterables x, we calculate its negation y like this:

y = negate(x)


Addendum:

As noted by user chradcliffe, the above negate function yields a generator which may contain other generators which ..., etc. To expand/evaluate all these generators, we need to apply list() to all of them. So we define another general mapping function, this time one that works on the iterables themselves.

def recursive_iter_map(f, iterable):
    def rec(e):
        if isinstance(e, collections.Iterable):
            return recursive_iter_map(f, e)
        else:
            return e

    return f(map(rec, iterable))

Now,

all_lists = functools.partial(recursive_iter_map, list)
y = all_lists(negate(x))

will actually negate every element right away and return the complete list.

Note that we can regard a nested collection of iterables as a tree. Each iterable is a subtree, while non-iterables are leaves. Hence the first function I defined works on leaves, and the second function works on non-leaves.

Stephan202
Great solution, but `negate` returns a generator and the depth of the list is an issue once again. Is there an easy way to expand generators recursively that I don't know about?
chradcliffe
Well, easy... not really. I did extend the answer. Hope there is an easier solution.
Stephan202
+1  A: 

It is ugly but you can modify the list in-place with a list comprehension if you really want to.

>>> my_list = [[1,2,3],[4,5,6],[7,8,9]]
>>> [[slist.__setitem__(i, -n) for i, n in enumerate(slist)] for slist in my_list]
[[None, None, None], [None, None, None], [None, None, None]]
>>> my_list
[[-1, -2, -3], [-4, -5, -6], [-7, -8, -9]]

Explicit for loops

Using explicit for loops, as shown in other answers, is considered better style when side-effects are involved, however. I think a nice form for the for loop style is:

for nested_list in my_list:
    for i, x in enumerate(nested_list):
        nested_list[i] = -x
Jacob Gabrielson
+1: explicit for-loops
J.F. Sebastian
+8  A: 

Another option is to use the built-in map function:

>>> my_list = [[1,2,3],[4,5,6],[7,8,9]]
>>> neg = lambda x: -x
>>> f = lambda x: map(neg, x)
>>> map(f, my_list)
[[-1, -2, -3], [-4, -5, -6], [-7, -8, -9]]
chradcliffe
it was made for this exact task
jle
+3  A: 

Many answers are about creating altered copy of list, but literal meaning of question is about in-place modification of list.

Here is my version of best-of-breed in-place list altering solution:

def alter_elements(lst, func):
  for i, item in enumerate(lst):
    if isinstance(item, list):
      alter_elements(item, func)
    else:
      lst[i] = func(item)

Test run:

>>> sample = [[1,2,3],[4,5,6],[7,8,9]]
>>> alter_elements(sample, lambda x: -x)
>>> print sample
>>> [[-1, -2, -3], [-4, -5, -6], [-7, -8, -9]]

No list copies. No hardcoded bounds. No list comprehensions with side-effects.

Constantin
Correct. I want to modify the actual list, not a copy. I merely used negation as an easy example. Thank you for the the solution. This does exactly what I want very quickly. Looping through each element was taking 3+ seconds. I knew there had to be a more efficient method.
jsumners
Constantin, could you reformat your code block with spaces instead of pre tags so that we get code highlighting?
Phil H
and what are the side-effects of comprehensions?
SilentGhost
The point is that list comprehensions shouldn't have side-effects. The point of a l-c is to create a new list. You can use the built-in looping to do other things (such as solve this problem, perhaps) but you shouldn't. See http://en.wikipedia.org/wiki/Side_effect_(computer_science) .
John Fouhy
SilentGhost, i was referring to Jacob's answer http://stackoverflow.com/questions/693630/alter-all-values-in-a-python-list-of-lists/693811#693811
Constantin
A: 

If you have a 3x3 array of numbers, and you want to perform a transformation on every element of it, I suspect you may be better served in the long run by using a numerical library like NumPy/SciPy, and using the matrix routines it provides. If you are in any way interested in high performance, then this would be a necessity. Much of python's flexibility can be foregone at the array element level in exchange for optimised numerical algorithm speed.

Phil H