3

I have a list a for which I want to change the elements a[i: j] according to a function f. Can I do better than the naive way?

for index in range(i, j): a[index] = f(a) 

[By better I mean something closer to map(f, a), or something faster.]

2
  • You might prefer xrange over range (see stackoverflow.com/a/135114/589206). Commented Feb 16, 2012 at 3:21
  • I think this is closer to your intent: for index, ai in enumerate(a[i:j], start=i): a[index] = f(ai) Commented Feb 16, 2012 at 3:49

3 Answers 3

3

You can assign to the slice:

a[i:j] = map(f, a[i:j]) 
Sign up to request clarification or add additional context in comments.

1 Comment

@Randomblue read up on slices. docs.python.org/library/stdtypes.html#mutable-sequence-types "s[i:j] = t slice of s from i to j is replaced by the contents of the iterable t"
2

I am not going to do timing exercises but I'll show you what internal code the various options turn into. Your code is naive. The map-based solution with the slice as l-value is map_lvalue_slice. The list comprehension with the slice as l-value is list_comp_lvalue_slice. A solution related to the list comprehension uses a tuple and is called tuple_lvalue_slice:

>>> from dis import dis >>> >>> def naive(a, f, i, j): ... for index, ai in enumerate(a[i:j], start=i): ... a[index] = f(ai) ... >>> def map_lvalue_slice(a, f, i, j): ... a[i:j] = map(f, a[i:j]) ... >>> def list_comp_lvalue_slice(a, f, i, j): ... a[i:j] = [f(ai) for ai in a[i:j]] ... >>> def tuple_lvalue_slice(a, f, i, j): ... a[i:j] = tuple(f(ai) for ai in a[i:j]) ... >>> dis(naive) 2 0 SETUP_LOOP 55 (to 58) 3 LOAD_GLOBAL 0 (enumerate) 6 LOAD_FAST 0 (a) 9 LOAD_FAST 2 (i) 12 LOAD_FAST 3 (j) 15 SLICE+3 16 LOAD_CONST 1 ('start') 19 LOAD_FAST 2 (i) 22 CALL_FUNCTION 257 25 GET_ITER >> 26 FOR_ITER 28 (to 57) 29 UNPACK_SEQUENCE 2 32 STORE_FAST 4 (index) 35 STORE_FAST 5 (ai) 3 38 LOAD_FAST 1 (f) 41 LOAD_FAST 5 (ai) 44 CALL_FUNCTION 1 47 LOAD_FAST 0 (a) 50 LOAD_FAST 4 (index) 53 STORE_SUBSCR 54 JUMP_ABSOLUTE 26 >> 57 POP_BLOCK >> 58 LOAD_CONST 0 (None) 61 RETURN_VALUE >>> >>> dis(map_lvalue_slice) 2 0 LOAD_GLOBAL 0 (map) 3 LOAD_FAST 1 (f) 6 LOAD_FAST 0 (a) 9 LOAD_FAST 2 (i) 12 LOAD_FAST 3 (j) 15 SLICE+3 16 CALL_FUNCTION 2 19 LOAD_FAST 0 (a) 22 LOAD_FAST 2 (i) 25 LOAD_FAST 3 (j) 28 STORE_SLICE+3 29 LOAD_CONST 0 (None) 32 RETURN_VALUE >>> >>> dis(list_comp_lvalue_slice) 2 0 BUILD_LIST 0 3 LOAD_FAST 0 (a) 6 LOAD_FAST 2 (i) 9 LOAD_FAST 3 (j) 12 SLICE+3 13 GET_ITER >> 14 FOR_ITER 18 (to 35) 17 STORE_FAST 4 (ai) 20 LOAD_FAST 1 (f) 23 LOAD_FAST 4 (ai) 26 CALL_FUNCTION 1 29 LIST_APPEND 2 32 JUMP_ABSOLUTE 14 >> 35 LOAD_FAST 0 (a) 38 LOAD_FAST 2 (i) 41 LOAD_FAST 3 (j) 44 STORE_SLICE+3 45 LOAD_CONST 0 (None) 48 RETURN_VALUE >>> >>> dis(tuple_lvalue_slice) 2 0 LOAD_GLOBAL 0 (tuple) 3 LOAD_CLOSURE 0 (f) 6 BUILD_TUPLE 1 9 LOAD_CONST 1 (<code object <genexpr> at 0xb748dc38, file "<stdin>", line 2>) 12 MAKE_CLOSURE 0 15 LOAD_FAST 0 (a) 18 LOAD_FAST 2 (i) 21 LOAD_FAST 3 (j) 24 SLICE+3 25 GET_ITER 26 CALL_FUNCTION 1 29 CALL_FUNCTION 1 32 LOAD_FAST 0 (a) 35 LOAD_FAST 2 (i) 38 LOAD_FAST 3 (j) 41 STORE_SLICE+3 42 LOAD_CONST 0 (None) 45 RETURN_VALUE 

The solutions that resolve fastest to C code, preferably in a tight loop, are most desirable in my opinion because they are likely using mostly optimized C code and not predominantly interpreted instructions. I'd prefer the slice as l-value solutions over your code and I might lean towards to the map solution even though I am mostly a list comprehension guy.

Also, here is the proof that they are equivalent code:

>>> i, j = 4, 8 >>> def f(ai): ... return -ai ... >>> for fn in (naive, map_lvalue_slice, list_comp_lvalue_slice, tuple_lvalue_slice): ... a = range(10) ... fn(a, f, i, j) ... print "%-40s: %r" % (fn.__name__, a) ... naive : [0, 1, 2, 3, -4, -5, -6, -7, 8, 9] map_lvalue_slice : [0, 1, 2, 3, -4, -5, -6, -7, 8, 9] list_comp_lvalue_slice : [0, 1, 2, 3, -4, -5, -6, -7, 8, 9] tuple_lvalue_slice : [0, 1, 2, 3, -4, -5, -6, -7, 8, 9] 

Comments

1

Use a list comprehension...

a[i:j] = [f(ai) for ai in a[i:j]] 

or the map equivalent...

a[i:j] = map(f, a[i:j]) 

1 Comment

You should test it on your use case. It will depend, probably, on the values of i and j and the types of the elements.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.