set item at multiple indexes in a list - python

I am trying to find a way to use a list of indexes to set values at multiple places in a list (as is possible with numpy arrays).
I found that I can map __getitem__ and a list of indexes to return the values at those indexes:
# something like
a_list = ['a', 'b', 'c']
idxs = [0, 1]
get_map = map(a_list.__getitem__, idxs)
print(list(get_map)) # yields ['a', 'b']
However, when applying this same line of thought to __setitem__, the setting fails. This probably has something to do with pass-by-reference vs pass-by-value, which I have never fully understood no matter how many times I've read about it.
Is there a way to do this?
b_list = ['a', 'b', 'c']
idxs = [0, 1]
put_map = map(b_list.__setitem__, idx, ['YAY', 'YAY'])
print(b_list) # yields ['YAY', 'YAY', 'c']
For my use case, I only want to set one value at multiple locations. Not multiple values at multiple locations.
EDIT: I know how to use list comprehension. I am trying to mimic numpy's capability to accept a list of indexes for both getting and setting items in an array, except for lists.

The difference between the get and set case is that in the get case you are interested in the result of map itself, but in the set case you want a side effect. Thus, you never consume the map generator and the instructions are never actually executed. Once you do, b_list gets changed as expected.
>>> put_map = map(b_list.__setitem__, idxs, ['YAY', 'YAY'])
>>> b_list
['a', 'b', 'c']
>>> list(put_map)
[None, None]
>>> b_list
['YAY', 'YAY', 'c']
Having said that, the proper way for get would be a list comprehension and for set a simple for loop. That also has the advantage that you do not have to repeat the value to put in place n times.
>>> for i in idxs: b_list[i] = "YAY"
>>> [b_list[i] for i in idxs]
['YAY', 'YAY']

Related

How to arrange the output of set based on predefined list

list1=['f','l','a','m','e','s'] #This is the predefined list
list2=['e','e','f','a','s','a'] #This is the list with repitition
x=list(set(list2)) # I want to remove duplicates
print(x)
Here I want the variable x to retain the order which list1 has. For example, if at one instance set(list2) produces the output as ['e','f','a','s'], I want it to produce ['f','a','e','s'] (Just by following the order of list1).
Can anyone help me with this?
Construct a dictionary that maps characters to their position in list1. Use its get method as the sort-key.
>>> dict1 = dict(zip(list1, range(len(list1))))
>>> sorted(set(list2), key=dict1.get)
['f', 'a', 'e', 's']
This is one way using dictionary:
list1=['f','l','a','m','e','s'] #This is the predefined list
list2=['e','e','f','a','s','a'] #This is the list with repitition
x=list(set(list2)) # I want to remove duplicates
d = {key:value for value, key in enumerate(list1)}
x.sort(key=d.get)
print(x)
# ['f', 'a', 'e', 's']
Method index from the list class can do the job:
sorted(set(list2), key=list1.index)
What is best usually depends on actual use. With this problem it is important to know the expected sizes of the lists to choose the most efficient approach. If we are keeping much of the dictionary the following query works well and has the additional benefit that it is easy to read.
set2 = set(list2)
x = [i for i in list1 if i in set2]
It would also work without turning list2 into a set first. However, this would run much slower with a large list2.

Sum of first elements in nested lists

I am trying to get the first element in a nested list and sum up the values.
eg.
nested_list = [[1, 'a'], [2, 'b'], [3, 'c'], [4, 'd']]
print sum(i[0] for i in nested_list)
However, there are times in which the first element in the lists None instead
nested_list = [[1, 'a'], [None, 'b'], [3, 'c'], [4, 'd']]
new = []
for nest in nested_list:
if not nest[0]:
pass
else:
new.append(nest[0])
print sum(nest)
Wondering what is the better way that I can code this?
Just filter then, in this case testing for values that are not None:
sum(i[0] for i in nested_list if i[0] is not None)
A generator expression (and list, dict and set comprehensions) takes any number of nested for loops and if statements. The above is the equivalent of:
for i in nested_list:
if i[0] is not None:
i[0] # used to sum()
Note how this mirrors your own code; rather than use if not ...: pass and else, I inverted the test to only allow for values you actually can sum. Just add more for loops or if statements in the same left-to-right to nest order if you need more loops with filters, or use and or or to string together multiple tests in a single if filter.
In your specific case, just testing for if i[0] would also suffice; this would filter out None and 0, but the latter value would not make a difference to the sum anyway:
sum(i[0] for i in nested_list if i[0])
You already approached this in your own if test in the loop code.
First of all, Python has no null, the equivalent of null in languages like Java, C#, JavaScript, etc. is None.
Secondly, we can use a filter in the generator expression. The most generic is probably to check with numbers:
from numbers import Number
print sum(i[0] for i in nested_list if isinstance(i[0], Number))
Number will usually make sure that we accept ints, longs, floats, complexes, etc. So we do not have to keep track of all objects in the Python world that are numerical ourselves.
Since it is also possible that the list contains empty sublists, we can also check for that:
from numbers import Number
print sum(i[0] for i in nested_list if i and isinstance(i[0], Number))

How to force Python to correctly identify list element index in FOR loop

There is a simple list, for example,
my_list = ['a', 'b', 'b', 'c', 'c']
I want to run through my_list[1:] with for loop to get previous element value for each iteration:
for i in my_list[1:]:
print(my_list[my_list.index(i)-1])
I expected to see a b b c on output, but get a a b b instead.
I guess this is because index() method search only for first i value, ignoring the fact that there are two elements "b" as well as two "c"...
How to fix this code to get expected output?
The list.index() method will return the index of first occurrence of its argument. And since you have multiple duplicate items in your list it doesn't give you the expected result. You can use a simple slicing to get your expected output:
>>> my_list = ['a', 'b', 'b', 'c', 'c']
>>>
>>> my_list[:-1]
['a', 'b', 'b', 'c']
Or if you want to access these items through a loop you can use zip() function like following:
>>> for i, j in zip(my_list,my_list[1:]):
... print(i, j)
...
a b
b b
b c
c c
Matching elements with their predecessors or sucessors is a common use case for zip:
In [13]: for i,prior in zip(my_list[1:], my_list[0:]):
print (prior)
....:
a
b
b
c
You can always emulate the behaviour of C/Pascal/Perl/whatever 'for' instead of Python 'for' (which is actually more like foreach). Note that the range starts with 1 to avoid returning the last element on the first iteration.
for i in range(1, len(my_list)):
print(my_list[i], my_list[i-1])
Not very Pythonic, but this approach is sometimes more intuitive for people with background in other languages.
As you noticed, using index does not work here, as it always finds the first position of the given element. Also, it is pretty wasteful, as in the worst case you have to search the entire list each time.
You could use enumerate with start parameter to get the element along with its index:
start = 1
for i, x in enumerate(my_list[start:], start):
print(i, x, my_list[i-1]) # index, current, last
This will do the trick:
for i in range(len(my_list)+1):
try: print(my_list[i-1])
except: print 'it is 1st iteration'

Reserving space for list

I have to make list2 which use name from list1 but number of name in list1 can vary.
foo = ['spam', 'eggs']
bar = [['spam', ['a', 'b']], ['eggs', ['c', 'd']]]
So, I reserve bar by
bar = [[None] * 2] * len(foo)
and copy names from list1 by looping
bar[i][0] = foo[i]
but the result is the name in every sublist are the same like this
bar = [['eggs', ['a', 'b']], ['eggs', ['c', 'd']]]
I try to reserve by
bar = [[None, None], [None, None]]
and no issue at all. So I think the problem come from how I reserve the list.
How can I fix this. If you don't understand my English, please ask. Thanks.
Create your second list like this:
list2 = [[None]*2 for x in range(len(list1))]
or alternativly, if for some reason you don't like to use comprehensions, you could do
list2 = []
for x in range(len(list1)):
list2.append([None,None])
The problem is that when you do something like
[listItem] * numCopies
you get a list that contains numCopies copies of the same listItem, so your sublists are all the same - when you change one you change them all.
The way that I have suggested will create unique lists that contain the same content (two copies of None). Thus changing one of these sublists will not change any others.
Python list type is for contiguous sequences, not arrays. (Python does have an array type, but list is not that.)
The example data structure you show (a sequence of pairs, with a single name and a collection of values) would be IMO better served by a mapping. Python's built-in mapping type is dict.
foo = ['spam', 'eggs']
bar = {
'spam': ['a', 'b'],
'eggs': ['c', 'd'],
}
So there is no need to reserve space in a Python list nor a Python dict. Just assign and delete items as you need, and the collection will manage its own space.
When you want to set items in the mapping bar keyed by the values you already have in the list foo, you just use a value from the list as a key in the dict:
for name in foo:
value = determine_what_is_the_value_for(name)
bar[name] = value

Python - List of Lists Slicing Behavior

When I define a list and try to change a single item like this:
list_of_lists = [['a', 'a', 'a'], ['a', 'a', 'a'], ['a', 'a', 'a']]
list_of_lists[1][1] = 'b'
for row in list_of_lists:
print row
It works as intended. But when I try to use list comprehension to create the list:
row = ['a' for range in xrange(3)]
list_of_lists = [row for range in xrange(3)]
list_of_lists[1][1] = 'b'
for row in list_of_lists:
print row
It results in an entire column of items in the list being changed. Why is this? How can I achieve the desired effect with list comprehension?
Think about if you do this:
>>> row = ['a' for range in xrange(3)]
>>> row2 = row
>>> row2[0] = 'b'
>>> row
['b', 'a', 'a']
This happens because row and row2 are two different names for the same list (you have row is row2) - your example with nested lists only obscures this a little.
To make them different lists, you can cause it to re-run the list-creation code each time instead of doing a variable assignment:
list_of_lists = [['a' for range in xrange(3)] for _ in xrange(3)]
or, create a new list each time by using a slice of the full old list:
list_of_lists = [row[:] for range in xrange(3)]
Although this isn't guaranteed to work in general for all sequences - it just happens that list slicing makes a new list for the slice. This doesn't happen for, eg, numpy arrays - a slice in those is a view of part of the array rather than a copy. If you need to work more generally than just lists, use the copy module:
from copy import copy
list_of_lists = [copy(row) for range in xrange(3)]
Also, note that range isn't the best name for a variable, since it shadows the builtin - for a throwaway like this, _ is reasonably common.
This happens because most objects in python (exept for strings and numbers) get passed reference (not exactly by reference, but here you have a better explanation) so when you try to do it in the "list comprehensive" way, yo get a list of 3 references to the same list (the one you called "row"). So when you change the value of one row you see that change in all of them)
So what you have to do is to change your "matrix" creation like this:
list_of_lists = [list(row) for range in xrange(3)]
Here you have some ideas on how to correctly get a copy of a list. Depending on what you are trying to do, you may use one or another...
Hope it helps!
Copy the list instead of just the reference.
list_of_lists = [row[:] for range in xrange(3)]

Categories

Resources