input: Write a python function to calculate page rank of webpages.
  assert calculate_page_rank([{"url": "a", "nodes": ["b", "c"]}, {"url": "b", "nodes": ["c"]}, {"url": "c", "nodes": ["a"]}]) == {'a': 0.3333333333333333, 'b': 0.3333333333333333, 'c': 0.3333333333333333}
  assert calculate_page_rank([{"url": "a", "nodes": ["b"]}, {"url": "b", "nodes": ["a"]}]) == {'a': 0.5, 'b': 0.5}
  assert calculate_page_rank([{"url": "a", "nodes": ["b", "c"]}, {"url": "b", "nodes": ["c"]}, {"url": "c", "nodes": []}]) == {'a': 0.3888888888888889, 'b': 0.3194444444444444, 'c': 0.2916666666666667}
output: def calculate_page_rank(webpages, damping_factor=0.85, max_iterations=100, tol=1.0e-6):
      from collections import defaultdict
      
      N = len(webpages)
      ranks = {page['url']: 1 / N for page in webpages}
      outgoing_links = {page['url']: len(page['nodes']) for page in webpages}
      incoming_links = defaultdict(list)
      
      for page in webpages:
          for node in page['nodes']:
              incoming_links[node].append(page['url'])
      
      for _ in range(max_iterations):
          new_ranks = {}
          for page in ranks:
              rank_sum = sum(ranks[in_link] / outgoing_links[in_link] for in_link in incoming_links[page])
              new_ranks[page] = (1 - damping_factor) / N + damping_factor * rank_sum
          
          if all(abs(new_ranks[page] - ranks[page]) < tol for page in ranks):
              break
          
          ranks = new_ranks
      
      return ranks