How to use heapq module - python-3.x

I don't understand how I can properly use heapq module.
I realized that without transforming my list to a heap (without using heapify) I can still use other functions which require a heap as input (heappop, heappush..). So when do I need to use heapify?
Should I create an empty list, transform it to a heap using heapify, and then use it? I tried this. I got TypeError.
my_list = heapq.heapify([0])
heapq.heappush(my_list, -8)
TypeError: heap argument must be a list
heapq.heappush(my_list, -8)
In the example below I can push -8 to my list with heappush if I don't transform my list to a heap. However when I want to see the min element of the heap, it gives me 0. In the heapq documentation it says that I can reach the min element using the index 0.
my_list = [0]
heapq.heappush(my_list, -8)
print(my_list, my_list[0])
output: [0, -8] 0
I'm trying to use it in a loop, so I want to be able to perform quick push and pop operations without transforming a list to a heap in each iteration, it would take O(N)

You should use heapify when you don't start from an empty list and you want to transform the list to a heap inplace.
>>> queue = [1,9,2,4]
>>> heapify(queue)
>>> queue
[1, 4, 2, 9]
When you start from an empty list, then you can perform the operations such as heappush, heappop, and heappushpop directly on the list.
Example:
>>> queue = []
>>> heappush(queue, 3)
>>> heappush(queue, 1)
>>> heappush(queue, 4)
>>> heappop(queue)
1
>>> heappop(queue)
3
>>> heappop(queue)
4
You get an error, because you are using the return value of heapify, which is None since it's inplace.
>>> res = heapify([1,9,2,4])
>>> res is None
True

Related

List of dicts - Partial shuffle

Let's suppose I have this:
my_list = [{'id':'2','value':'4'},
{'id':'6','value':'3'},
{'id':'4','value':'5'},
{'id':'9','value':'10'},
{'id':'0','value':'9'}]
and I want to shuffle the list but I want to do it partly - by this I mean that I do not want to shuffle all the elements but only a percentage of them (eg 40%).
For example like this:
my_list = [{'id':'4','value':'5'},
{'id':'6','value':'3'},
{'id':'2','value':'4'},
{'id':'9','value':'10'},
{'id':'0','value':'9'}]
How can this be efficiently done?
random.shuffle does not allow you to specify only part of a list, it will always shuffle an entire list.
A trade-off between effort, speed, and memory footprint would be to slice out the part of the list you want to shuffle, do it, and then assign it back to that slice:
>>> from random import shuffle
>>> x = list(range(10))
>>> y = x[:5]
>>> shuffle(y)
>>> x[:5] = y
>>> x
[2, 1, 4, 3, 0, 5, 6, 7, 8, 9]
My solution is the following:
from random import sample
shuffle_percentage = 0.4
x = sample(range(len(my_list)), int(len(my_list) * shuffle_percentage))
for index in range(0, len(x)-1, 2):
my_list[x[index]], my_list[x[index+1]] = my_list[x[index+1]], my_list[x[index]]

Why does shallow copy behaves as deep copy for a simple list

I was going understanding shallow copy and deep copy concepts in python. I observe most of the posts/blogs/SO answer explain these concepts are using a nested lists.
import copy
lst = [[1,2,3],[4,5,6]]
b = copy.copy(lst)
c = copy.deepcopy(lst)
# Shallow copy demo
b[0][0] = 9
print(b)
# >>> [[9, 2, 3], [4, 5, 6]]
print(lst)
# >>> [[9, 2, 3], [4, 5, 6]]
# Deepcopy demo
c[0][0] = 10
print(c)
# >>> [[10, 2, 3], [4, 5, 6]]
print(lst)
# >>> [[9, 2, 3], [4, 5, 6]]
I understood the shallow and deep copy concept with the above simple example. But when I implement the concept, on a simple list (one-dimensional list), the observation is shallow copy behaves as deep copy.
import copy
lst = [1,2,3]
b = copy.copy(lst)
c = copy.deepcopy(lst)
# Shallow copy demo
b[0] = 0
print(b)
# >>> [0, 2, 3]
print(lst)
# >>> [1,2,3]
# Deepcopy demo
c[0] = 9
print(c)
# >>> [9,2,3]
print(lst)
# >>> [1,2,3]
This shows that copy.copy(lst) behaves different and does deep copy instead of shallow copy.
I would like to understand, why the behavior of copy.copy() is different for nested list and simple list. Also if i have to get shallow copy working for simple list, how can i achieve it?.
The results that you're getting are not directly related with the "level of depth", the
most important thing to keep in mind here is the concept of mutabiliy.
List are mutable, meanwhile numeric values are not. That means that you can add or modify items on a list, but those operations doesn't create or destroy the list, they only change it. You can verify that using the built-in function id(), which gives you the memory address of a variable:
lst = [1, 2, 3]
print(id(lst)) # the number printed by this...
lst.append(4)
lst[1] = 0
print(id(lst)) # should be the same printed by this one. That tells us that
# the variable 'lst' keeps referecing the same object, although
# the object have changed in form (mutated)
Numbers are totally different, and it makes sense, since a numeric type variable can only
store a single numeric value:
a = 5
print(id(a)) # the number printed by this...
a = 6
print(id(a)) # should be different than this one, meaning that a new numeric
# value were created and stored in a different memory address
On the line
b[0][0] = 9
of your first example, the list at b[0] is being manipulated, but it remains being the same object, and since b[0] is nothing more than a reference to the same list at lst[0] (because b is a shallow copy), when we print lst we will see that it changed too.
On your implementation, when you assign b[0] = 0, python is creating the value 0, storing it on a new memory location, and overriding the reference that b[0] had to the same value as lst[0] (cause thats the natural behavior of numeric types).
As is said, this doesn't have to be with the level of nesting of compound data structures,
since some of the are inmutable (as for example the tuple) and the same that happened on your implementation would happen with this inmutable data structures.
You can read some more about the id() built-in function here, and more about
mutable and inmutable types here
Hope this answer helps you!

Why does Python list comprehension print a list of "None"s in the end?

From Python 3.6 prompt:
>>> [print(i) for i in range(3)]
0
1
2
[None, None, None]
A list comprehension creates a list containing the results of evaluating the expression for each iteration. As well as the "side-effect" of printing out whatever you give it, the print function returns None, so that is what gets stored in the list.
Since you're in an interactive console, the return value is printed out at the end. If you ran this as a script, the output wouldn't include [None, None, None].
If you're wanting to loop over 0, 1, 2 and print them out, I'm afraid you have to do it as a proper for-loop:
for i in range(3)
print(i)
Though I fully empathise with the desire to use the neat one-line syntax of a list comprehension! 😞
As other people have commented, you can achieve the same effect by constructing the list (using a list comprehension) and then printing that out:
print(*[i for i in range(3)], sep="\n")
You need the sep argument if you want them on a new line each; without it they'll be on one line, each separated by a space.
When you put the print statement there, it prints each of the value, then prints the list which has no values in it.
>>> this = print([(i) for i in range(3)])
[0, 1, 2]
>>> print(this)
None
>>>
This should show you the problem. What you want to use is
>>> [(i) for i in range(3)]
[0, 1, 2]

If I have duplicates in a list with brackets, what should I do

Suppose I have the following list:
m=[1,2,[1],1,2,[1]]
I wish to take away all duplicates. If it were not for the brackets inside the the list, then I could use:
m=list(set(m))
but when I do this, I get the error:
unhashable type 'set'.
What command will help me remove duplicates so that I could only be left with the list
m=[1,2,[1]]
Thank you
You can do something along these lines:
m=[1,2,[1],1,2,[1]]
seen=set()
nm=[]
for e in m:
try:
x={e}
x=e
except TypeError:
x=frozenset(e)
if x not in seen:
seen.add(x)
nm.append(e)
>>> nm
[1, 2, [1]]
From comments: This method preserves the order of the original list. If you want the numeric types in order first and the other types second, you can do:
sorted(nm, key=lambda e: 0 if isinstance(e, (int,float)) else 1)
The first step will be to convert the inner lists to tuples:
>> new_list = [tuple(i) if type(i) == list else i for i in m]
Then create a set to remove duplicates:
>> no_duplicates = set(new_list)
>> no_duplicates
{1, 2, (1,)}
and you can convert that into list if you wish.
For a more generic solution you can serialize each list item with pickle.dumps before passing them to set(), and then de-serialize the items with pickle.loads:
import pickle
m = list(map(pickle.loads, set(map(pickle.dumps, m))))
If you want the original order to be maintained, you can use a dict (which has become ordered since Python 3.6+) instead of a set:
import pickle
m = list(map(pickle.loads, {k: 1 for k in map(pickle.dumps, m)}))
Or if you need to be compatible with Python 3.5 or earlier versions, you can use collections.OrderedDict instead:
import pickle
from collections import OrderedDict
m = list(map(pickle.loads, OrderedDict((k, 1) for k in map(pickle.dumps, m))))
result = []
for i in m:
flag = True
for j in m:
if i == j:
flag = False
if flag:
result.append(i)
Result will be: [1,2,[1]]
There are ways to make this code shorter, but I'm writing it more verbosely for readability. Also, note that this method is O(n^2), so I wouldn't recommend for long lists. But benefits is the simplicity.
Simple Solution,
m=[1,2,[1],1,2,[1]]
l= []
for i in m:
if i not in l:
l.append(i)
print(l)
[1, 2, [1]]
[Program finished]

How to pass a map to a function in Python 3?

I get a map of numbers from user and I want to pass the map to a function. I am able to display the map, but I can't find its length. I understand that in Python 3, maps have no length and they have to be converted in lists, but I had no success with that. I also noticed that if I attempt to display the length of the map before calling function info, then the info() will print an empty map.
def info(phys):
print("phys =",list(phys)) # it displays correctly only if I comment line 10
print("len =",len(list(phys))) # it always displays 0 and I expect 3
for i in phys:
print(str(i))
return
phys = map(int, input().strip().split()) # I pass "1 2 3"
print("len(phys) =",len(list(phys))) # if this command executes before next, line 2 will print "phys = []"
info(phys)
The result of a map() call is a generator which will yield resulting values only once. See relevant documentation about map.
>>> phys = map(int, input().strip().split())
1 2 3 4
>>> list(phys)
[1, 2, 3, 4]
>>> list(phys)
[] # Second attempt to iterate through "phys" does not return anything anymore
Apparently you want to materialize the values and work with them later. Then store them:
>>> phys = map(int, input().strip().split())
1 2 3 4
>>> result = list(phys)
>>> len(result)
4
>>> result[1]
2
>>> result[-2:]
[3, 4]

Resources