Questions about UserList __init__, ( [:], isinstance ) - python

I wanted to extend the class list in python37 with some custom methods.
and ended up reading the UserList cpython code. After reading it new questions arose with regards of [:] usage.
If I understand correctly the `[:]` makes a slice copy of the whole
`self.data`. But I am trying to see what is the point of using `[:]`
at the left side of the `=` operator.
Is there any difference between option one and two? Tried in the python
interpreter, and both seem to have the same effect, am I missing
something?
letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
# option (1)
letters[:] = []
# option (2)
letters = []
Now it comes my questions with regards to UserList code. I added comments with questions I have.
class UserList(_collections_abc.MutableSequence):
def __init__(self, initlist=None):
self.data = []
if initlist is not None:
if type(initlist) == type(self.data):
# NOTE: Is this if statement doing the same?
# if isinstance(initlist, list):
self.data[:] = initlist
# NOTE: wouldn't in this case self.data keep a reference to initlist
# instead of a copy?
# self.data[:] = initlist[:] # could one replace that line with this one?
elif isinstance(initlist, UserList):
self.data[:] = initlist.data[:]
# NOTE: would this line accomplish the same?
# self.data = initlist.data[:]
else:
self.data = list(initlist)
...

They don't behave the same if you have another reference to letters.
Scenario 1: modifying letters in place.
>>> letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> lst = letters
>>> letters[:] = []
>>> letters
>>> []
>>> lst
>>> []
Scenario 2, reassigning the name letters to an empty list.
>>> letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
>>> lst = letters
>>> letters = []
>>> letters
>>> []
>>> lst
>>> ['a', 'b', 'c', 'd', 'e', 'f', 'g']
Since names are reassigned independently, lst does not see any change.
If you had
self.data = initlist
mutations to initlist would affect self.data (since they are the same object in memory).

When you specify a on the left side of the = operator, you are using Python's normal assignment, which changes the name a in the current context to point to the new value. This does not change the previous value to which a was pointing.
By specifying a[0:2] on the left side of the = operator, you are telling Python you want to use Slice Assignment. Slice Assignment is a special syntax for lists, where you can insert, delete, or replace contents from a list.
See: How assignment works with python list slice
Maybe this helps you.

Related

Rearrange a list of strings

I want to rearrange or modify he sequence of elements (strings) in a list. This is the original list
['A', 'B', 'C', 'D', 'E', 'F', 'G']
I want to move E and F behind (or after?) B.
['A', 'B', 'E', 'F', 'C', 'D', 'G']
^^^ ^^^
The decision what to move comes from the user. There is no rule behind and no way to formulate that in an algorithm. In other words the action move something behind something other is input from the user; e.g. the user mark two elements with her/his mouse and drag an drop it behind another element.
My code works and is able to do this. But I wonder if there is a more efficient and pythonic way to do this. Maybe I missed some of Python's nice in-build features.
#!/usr/bin/env python3
# input data
original = list('ABCDEFG')
# move "EF" behind "B" (this is user input)
to_move = 'EF'
behind = 'B'
# expected result
rearanged = list('ABEFCDG')
# index for insertion
idx_behind = original.index(behind)
# each element to move
for c in reversed(to_move): # "reverse!"
# remove from original position
original.remove(c)
# add to new position
original.insert(idx_behind + 1, c)
# True
print(original == rearanged)
You can assume
Elements in original are unique.
to_move always exist in original.
behind always exist in original.
The elements in to_move are always adjacent.
Other example of possible input:
Move ['B'] behind F
Move ['A', 'B'] behind C
This is not possible:
Move ['A', 'F'] behind D
Don't use .remove when the goal is to erase from a specific position; though you may know what is at that position, .remove a) will search for it again, and b) remove the first occurrence, which is not necessarily the one you had in mind.
Don't remove elements one at a time if you want to remove several consecutive elements; that's why slices exist, and why the del operator works the way that it does. Not only is it already harder to iterate when you can say what you want directly, but you have to watch out for the usual problems with modifying a list while iterating over it.
Don't add elements one at a time if you want to add several elements that will be consecutive; instead, insert them all at once by slice assignment. Same reasons apply here.
Especially don't try to interleave insertion and removal operations. That's far more complex than necessary, and could cause problems if the insertion location overlaps the source location.
Thus:
original = list('ABCDEFG')
start = original.index('E')
# grabbing two consecutive elements:
to_move = original[start:start+2]
# removing them:
del original[start:start+2]
# now figure out where to insert in that result:
insertion_point = original.index('B') + 1
# and insert:
original[insertion_point:insertion_point] = to_move
If it is just a small number of items you want to rearrange, just swap the relevant elements:
lst = ['A', 'B', 'C', 'D', 'E', 'F', 'G']
lst[2], lst[4] = lst[4], lst[2] # switch 'C' and 'E'
lst[3], lst[5] = lst[5], lst[3] # switch 'D' and 'F'
lst
['A', 'B', 'E', 'F', 'C', 'D', 'G']

python for everyone L8 exercise

could you help me to figure out smth in one exercise that relates to Lists topic (Ch 8 - https://www.py4e.com/)?
here is my code:
def delete_head(t):
del t[0]
letters = ['a', 'b', 'c', 'd', 'f', 'g']
q = delete_head(letters)
print(q)
delete_head(letters)
print(letters)
I got this output:
output
I cannot understand why output is not like ['b', 'c', 'd', 'f', 'g']
q = delete_head(letters)
This deletes 'a'
delete_head(letters)
and this deletes 'b' (since 'a' is already gone)
You are calling delete_head() twice, first in line 4 where you assign the return value of the function to q, and a second time in line 6.
Notice how q is None. This is because your function does not return the list. It get's a reference to the original list and deletes the first item. It does not return the list, but the changes aren't lost because you are using a reference to the original list. Therefore the return value of delete_head() is None

Alternative to using the sort function when adding to a list?

I want to insert a word alphabetically into a list. Originally I would append the word I'm adding to the end of the list and then sort the list, but I am not allowed to use the sort() function.
Is there a way to do this through a function?
Based of of #SheshankS.'s answer. A function to do this for you:
def insert(item, _list):
for index, element in enumerate(_list):
if item < element: # in python, this automatically compares alphabetical precedence.
_list.insert(index, item)
return # exit out of the function since we already inserted
# if the item was not inserted, it must have the lowest precedence, so just append it
_list.append(item)
Note that since lists are mutable, this will actually mutate the given instance.
So, this:
someList = ["a", "b", "d"]
insert("c", someList)
Will actually change someList instead of just returning the new value.
Try doing this:
array = ["asdf", "bsdf", "kkkk", "zssdd"]
insertion_string = "zzat"
i = 0
for element in array:
if insertion_string < element:
array.insert(i, insertion_string)
break
i += 1
# if it is last one
if not insertion_string in array:
array.append(insertion_string)
print (array )
Repl.it = https://repl.it/repls/VitalAvariciousCodec
You did not say if you are allowed to use third-party modules, and you did not say if speed is a factor. If you want to add a new item to your sorted list quickly and you are allowed to use a module, use the SortedList class from sortedcontainers. This is a module included in many distributions of Python, such as Anaconda.
This will be simple and fast, even for large lists.
someList = SortedList(["a", "b", "d"])
someList.add("c")
print(someList)
The printout from that is
SortedList(['a', 'b', 'c', 'd'])
>>> import bisect
>>> someList = ["a", "b", "d"]
>>> bisect.insort(someList,'c')
>>> someList
['a', 'b', 'c', 'd']
>>>
If standard lib is allowed you can use bisect:
>>> import bisect
>>> lst = list('abcefg')
>>> for x in 'Adh':
... lst.insert(bisect.bisect(lst, x), x)
... print(lst)
...
['A', 'a', 'b', 'c', 'e', 'f', 'g']
['A', 'a', 'b', 'c', 'd', 'e', 'f', 'g']
['A', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

Python insert operation on list

I am newbie to Python and I have a doubt regarding insert operation on the list.
Example 1:
mylist = ['a','b','c','d','e']
mylist.insert(len(mylist),'f')
print(mylist)
Output:
['a', 'b', 'c', 'd', 'e', 'f']
Example 2:
mylist = ['a','b','c','d','e']
mylist.insert(10,'f')
print(mylist)
Output:
['a', 'b', 'c', 'd', 'e', 'f']
In second example why it still inserts 'f' element in the list even if I am giving index 10 to insert method?
The list.insert function will insert before the specified index. Since the list is not that long anyways in your example, it goes on the end. Why not just use list.append if you want to put stuff on the end anyways.
x = ['a']
x.append('b')
print x
Output is
['a', 'b']
The concept here is "insert before the element with this index". To be able to insert at the end, you have to allow the invalid off-the-end index. Since there are no well-defined "off-the-end iterators" or anything in Python, it makes more sense to just allow all indices than to allow one invalid one, but no others.

Combining elements in list using python

Given input:
list = [['a']['a', 'c']['d']]
Expected Ouput:
mylist = a,c,d
Tried various possible ways, but the error recieved is TypeError: list indices must be integers not tuple.
Tried:
1.
k= []
list = [['a']['a', 'c']['d']]
#k=str(list)
for item in list:
k+=item
print k
2.
print zip(*list)
etc.
Also to strip the opening and closing parenthesis.
What you want is flattening a list.
>>> import itertools
>>> l
[['a'], ['a', 'c'], ['d']]
>>> res = list(itertools.chain.from_iterable(l))
>>> res
['a', 'a', 'c', 'd']
>>> set(res) #for uniqify, but doesn't preserve order
{'a', 'c', 'd'}
Edit: And your problem is, when defining a list, you should seperate values with a comma. So, not:
list = [['a']['a', 'c']['d']]
Use commas:
list = [['a'], ['a', 'c'], ['d']]
And also, using list as a variable is a bad idea, it conflicts with builtin list type.
And, if you want to use a for loop:
l = [['a'], ['a', 'c'], ['d']]
k = []
for sublist in l:
for item in sublist:
if item not in k: #if you want list to be unique.
k.append(item)
But using itertools.chain is better idea and more pythonic I think.
While utdemir's answer does the job efficiently, I think you should read this - start from "11.6. Recursion".
The first examples deals with a similar problem, so you'll see how to deal with these kinds of problems using the basic tools.

Categories

Resources