Naming
Explicit names can go a long way in conveying what a piece of code does. Combine that with unpacking and explicit assignments to name parts of sequences, you can get:
from collections import defaultdict
n, m = map(int, input().split())
edges = [tuple(map(int, input().split())) for _ in range(m)]
adjacents = defaultdict(set)
for node1, node2 in edges:
adjacent[node1].add(node2)
adjacent[node2].add(node1)
degrees = defaultdict(set)
for node, neighbours in adjacents.items():
degrees[len(neighbours].add(node)
if len(degrees[1]) == 0:
print('0')
else:
count = 0
while len(degrees[1]) > 0:
dec = defaultdict(lambda: [list(), 0]) # Can't figure out a good name for that
singles = degrees[1]
while len(singles) > 0:
single = singles.pop()
for node in adjacent[single]:
dec[node][0].append(single)
dec[node][1] += 1
del adjacent[single]
for node, (neighbours, count_neighbours) in dec.items():
node_degree = len(adjacent[node])
if node in degrees[node_degree]:
degrees[node_degree].remove(node)
degrees[node_degree - count_neighbours].add(node)
for neighbour in neighbours:
adjacent[node].remove(neighbour)
count += 1
print(count)
Quick simplifications
We can see from there that some parts are unnecessary complicated: for node in adjacent(single)
does not need to be a loop as there must be only one node adjacent to a node with degree 1. You can use unpacking to exctract this value; it will also let you ensure that there was actually only one value there:
while len(singles) > 0:
single = singles.pop()
neighbour, = adjacent[single] # Do not forget the comma here
del adjacent[single]
dec[neighbour][0].append(single)
dec[neighbour][1] += 1
You also have if len(degrees[1]) == 0: ... else: while len(degrees[1]) > 0
which are pretty much the same thing. You can remove the if
, if there is no node of degree 1, then the while won't execute and count
will still be 0.
Pythonic constructs
Every containers (lists, dicts, strings, sets, …) are considered False
in a boolean context if they are empty and True
otherwise. Which mean you should write while degrees[1]:
or if not degrees[1]
.
You may also have noticed the use of dict.items()
to iterate over both the keys and the values of a dictionnary at the same time. It is more efficient than using the key to retrieve the values latter.
You also don't really need to build a list of edges before building the dictionnary of adjacent nodes:
from collections import defaultdict
_, m = map(int, input().split())
adjacents = defaultdict(set)
for _ in range(m):
node1, node2 = map(int, input().split())
adjacent[node1].add(node2)
adjacent[node2].add(node1)
degrees = defaultdict(set)
for node, neighbours in adjacents.items():
degrees[len(neighbours].add(node)
count = 0
while degrees[1]:
dec = defaultdict(lambda: [list(), 0]) # Can't figure out a good name for that
singles = degrees[1]
while singles:
single = singles.pop()
for node in adjacent[single]:
dec[node][0].append(single)
dec[node][1] += 1
del adjacent[single]
for node, (neighbours, count_neighbours) in dec.items():
node_degree = len(adjacent[node])
if node in degrees[node_degree]:
degrees[node_degree].remove(node)
degrees[node_degree - count_neighbours].add(node)
for neighbour in neighbours:
adjacent[node].remove(neighbour)
count += 1
print(count)
Adding and removing nodes
You spend quite a lot of time moving nodes around in various lists and dictionaries. You could simplify the whole thing by using a more straightforward approach:
- you only need to keep track of the nodes with degree 1
- for each of these nodes:
- remove it from its neighbour list of neighbours
- if this neighbour now has degree 1, keep track of if in a temporary
set
- when done, iterate with the new set of nodes with degree 1.
from collections import defaultdict
_, m = map(int, input().split())
adjacents = defaultdict(set)
for _ in range(m):
node1, node2 = map(int, input().split())
adjacent[node1].add(node2)
adjacent[node2].add(node1)
singles = {node for node, neighbours in adjacents.items() if len(neighbours) == 1}
count = 0
while singles:
new_singles = set()
for single in singles:
neighbour, = adjacents[single]
adjacents[neighbour].remove(single)
if len(adjacents[neighbour]) == 1:
new_singles.add(neighbour)
# del adjacents[single] if you whish but it has no added value here
singles = new_singles
count += 1
print(count)
Functions
Using can help make things faster as Python resolves local symbols faster than global ones. But mainly recommended to organise the flow of things better and reuse parts of code.
You should also wrap the top-level code that you don't want to put in a function under an if __name__ == '__main__'
clause:
from collections import defaultdict
def remove_singles(adjacents):
singles = {node for node, neighbours in adjacents.items()
if len(neighbours) == 1}
passes = 0
while singles:
new_singles = set()
for single in singles:
neighbour, = adjacents[single]
adjacents[neighbour].remove(single)
if len(adjacents[neighbour]) == 1:
new_singles.add(neighbour)
singles = new_singles
passes += 1
return passes
if __name__ == '__main__':
_, m = map(int, input().split())
graph = defaultdict(set)
for _ in range(m):
node1, node2 = map(int, input().split())
graph[node1].add(node2)
graph[node2].add(node1)
print(remove_singles(graph))