53

If I have a list:

to_modify = [5,4,3,2,1,0]

And then declare two other lists:

indexes = [0,1,3,5]
replacements = [0,0,0,0]

How can I take to_modify's elements as index to indexes, then set corresponding elements in to_modify to replacements, i.e. after running, indexes should be [0,0,3,0,1,0].

Apparently, I can do this through a for loop:

for ind in to_modify:
    indexes[to_modify[ind]] = replacements[ind]

But is there other way to do this? Could I use operator.itemgetter somehow?

Benj
  • 736
  • 7
  • 21
Alcott
  • 17,905
  • 32
  • 116
  • 173
  • 3
    What's the problem with the loop solution you mention (only problem I can see is `l` contains indexes outside the bounds of m)? Can you give more context to the problem? – dbr Aug 29 '11 at 14:12
  • @dbr: I think he wants to replace values in s indexed by indexes in l with values from m, so there's no problem with out-of-boundsness here. – machine yearning Aug 29 '11 at 14:14
  • 1
    it contains indices out of bounds of a, since you're accessing a[index], that's no good. – steabert Aug 29 '11 at 14:29
  • @steabert: Good catch, I didn't see that because I couldn't see past all the 1-letter variables. – machine yearning Aug 29 '11 at 14:38
  • 1
    "Python's slice object" has nothing to do with the question asked here. – Karl Knechtel Aug 29 '11 at 15:23

8 Answers8

55

The biggest problem with your code is that it's unreadable. Python code rule number one, if it's not readable, no one's gonna look at it for long enough to get any useful information out of it. Always use descriptive variable names. Almost didn't catch the bug in your code, let's see it again with good names, slow-motion replay style:

to_modify = [5,4,3,2,1,0]
indexes = [0,1,3,5]
replacements = [0,0,0,0]

for index in indexes:
    to_modify[indexes[index]] = replacements[index]
    # to_modify[indexes[index]]
    # indexes[index]
    # Yo dawg, I heard you liked indexes, so I put an index inside your indexes
    # so you can go out of bounds while you go out of bounds.

As is obvious when you use descriptive variable names, you're indexing the list of indexes with values from itself, which doesn't make sense in this case.

Also when iterating through 2 lists in parallel I like to use the zip function (or izip if you're worried about memory consumption, but I'm not one of those iteration purists). So try this instead.

for (index, replacement) in zip(indexes, replacements):
    to_modify[index] = replacement

If your problem is only working with lists of numbers then I'd say that @steabert has the answer you were looking for with that numpy stuff. However you can't use sequences or other variable-sized data types as elements of numpy arrays, so if your variable to_modify has anything like that in it, you're probably best off doing it with a for loop.

machine yearning
  • 9,889
  • 5
  • 38
  • 51
  • yes, I like the zip-way better, but I would use i,rep for the running values (I don't like the one-letter difference, but use it too sometimes). So, I guess array or zip depending on situation. – steabert Aug 29 '11 at 15:38
  • 1
    Yeah I use shortened variable names like that too, especially as values in my iterations; I just wanted to emphasize that there's no shame in using a whole word if clarity's important. Zen of python says, "readability counts". – machine yearning Aug 29 '11 at 15:45
  • Adding to machine yearning's comment... [Zen of python - see pep 20.](https://www.python.org/dev/peps/pep-0020/) – theQuestionMan Aug 01 '17 at 06:33
35

numpy has arrays that allow you to use other lists/arrays as indices:

import numpy
S=numpy.array(s)
S[a]=m
steabert
  • 6,540
  • 2
  • 26
  • 32
  • 1
    +1 Haha excellent I was just adding this to my post, GJ. But I would debate whether or not this is necessary. – machine yearning Aug 29 '11 at 14:32
  • well, everything is debatable, even short vs long variable names, as sometimes short variable names are quite ok (if meaningfull in small functions). As for the numpy solution: for me it's more readable and for large arrays you're using C's power :) but yes, only really useful if you're working with arrays from the start, as converting a list to an array takes time. – steabert Aug 29 '11 at 15:15
  • I totally agree, it would depend on why he was doing this in the first place... But what about if he's not even really working with arrays of numbers, and he's got a list of dicts or something funky like that? No way, Jose. His problem is clearly underspecified. – machine yearning Aug 29 '11 at 15:30
  • Lol I think we got pwned... see @eph solution. – machine yearning Aug 29 '11 at 18:28
17

Why not just:

map(s.__setitem__, a, m)
eph
  • 1,988
  • 12
  • 25
  • 2
    +1 Pretty elegant but it's not pretty and it's not mainstream. Nice to see this come in so late though. – machine yearning Aug 29 '11 at 18:26
  • nice, but then you still just have an iterator ;) – steabert Aug 29 '11 at 18:44
  • 1
    @steabert: yeah but it's clever that you don't use the result of this function; it kinda violates the functional programming principle of no side-effects, which is ironic, because `map` itself is part of python's functional programming paradigm. – machine yearning Aug 30 '11 at 01:14
  • @machine yearning: Thanks for the advise. I never considered side-effects in `map`. Sometimes I just take it as the equivalent of `Array.forEach`, which has no Python implementation. – eph Aug 30 '11 at 10:33
  • 1
    @eph: yeah in case you didn't know, a side-effect is just a word used for something a function does that doesn't involve a return value... so even things like input/output are always based on side effects. not necessarily bad, it's just part of the functional programming http://en.wikipedia.org/wiki/Functional_programming paradigm to try to avoid them as much as possible; anything your function does should be captured in its return value. :) – machine yearning Aug 30 '11 at 12:50
  • You still need to consume the iterator. `*map(s.__setitem__, a, m),` or more efficiently `collections.deque(map(s.__setitem__, a, m), maxlen=0)` https://stackoverflow.com/questions/50937966/fastest-most-pythonic-way-to-consume-an-iterator – Tom Huntington Jan 27 '23 at 01:45
  • @machineyearning functional programming with side effects is a pretty elegant way to program. You don't need to eliminate mutating state entirely, there's a readability benefit to eliminating some but not all. Checkout my [fork](https://github.com/tom-huntington/aoc2022) of some guys aoc solutions – Tom Huntington Jan 27 '23 at 01:55
3

You can use operator.setitem.

from operator import setitem
a = [5, 4, 3, 2, 1, 0]
ell = [0, 1, 3, 5]
m = [0, 0, 0, 0]
for b, c in zip(ell, m):
    setitem(a, b, c)
>>> a
[0, 0, 3, 0, 1, 0]

Is it any more readable or efficient than your solution? I am not sure!

Praveen Gollakota
  • 37,112
  • 11
  • 62
  • 61
3

A little slower, but readable I think:

>>> s, l, m
([5, 4, 3, 2, 1, 0], [0, 1, 3, 5], [0, 0, 0, 0])
>>> d = dict(zip(l, m))
>>> d  #dict is better then using two list i think
{0: 0, 1: 0, 3: 0, 5: 0}
>>> [d.get(i, j) for i, j in enumerate(s)]
[0, 0, 3, 0, 1, 0]
utdemir
  • 26,532
  • 10
  • 62
  • 81
2

for index in a:

This will cause index to take on the values of the elements of a, so using them as indices is not what you want. In Python, we iterate over a container by actually iterating over it.

"But wait", you say, "For each of those elements of a, I need to work with the corresponding element of m. How am I supposed to do that without indices?"

Simple. We transform a and m into a list of pairs (element from a, element from m), and iterate over the pairs. Which is easy to do - just use the built-in library function zip, as follows:

for a_element, m_element in zip(a, m):
  s[a_element] = m_element

To make it work the way you were trying to do it, you would have to get a list of indices to iterate over. This is doable: we can use range(len(a)) for example. But don't do that! That's not how we do things in Python. Actually directly iterating over what you want to iterate over is a beautiful, mind-liberating idea.

what about operator.itemgetter

Not really relevant here. The purpose of operator.itemgetter is to turn the act of indexing into something, into a function-like thing (what we call "a callable"), so that it can be used as a callback (for example, a 'key' for sorting or min/max operations). If we used it here, we'd have to re-call it every time through the loop to create a new itemgetter, just so that we could immediately use it once and throw it away. In context, that's just busy-work.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
0

You can solve it using dictionary

to_modify = [5,4,3,2,1,0]
indexes = [0,1,3,5]
replacements = [0,0,0,0]

dic = {}
for i in range(len(indexes)):
    dic[indexes[i]]=replacements[i]
print(dic)

for index, item in enumerate(to_modify):
    for i in indexes:
        to_modify[i]=dic[i]
print(to_modify)

The output will be

{0: 0, 1: 0, 3: 0, 5: 0}
[0, 0, 3, 0, 1, 0]
0
elif menu.lower() == "edit":
    print ("Your games are: "+str (games))
    remove = input("Which one do you want to edit: ")
    add = input("What do you want to change it to: ")
    for i in range(len(games)) :
        if str(games[i]) == str(remove) :
            games[i] = str(add)
            break
        else :
            pass
    pass

why not use it like this? replace directly from where it was removed and anyway you can add arrays and the do .sort the .reverse if needed

Muaz
  • 1