code: def greedy_set_cover(universe, subsets, costs):
      
      elements = set(e for s in subsets.keys() for e in subsets[s])
      # elements don't cover universe -> invalid input for set cover
      if elements != universe:
          return None
  
      # track elements of universe covered
      covered = set()
      cover_sets = []
  
      while covered != universe:
          min_cost_elem_ratio = float("inf")
          min_set = None
          # find set with minimum cost:elements_added ratio
          for s, elements in subsets.items():
              new_elements = len(elements - covered)
              # set may have same elements as already covered -> new_elements = 0
              # check to avoid division by 0 error
              if new_elements != 0:
                  cost_elem_ratio = costs[s] / new_elements
                  if cost_elem_ratio < min_cost_elem_ratio:
                      min_cost_elem_ratio = cost_elem_ratio
                      min_set = s
          cover_sets.append(min_set)
          # union
          covered |= subsets[min_set]
      return cover_sets
docs: Approximate greedy algorithm for set-covering. Can be used on large
      inputs - though not an optimal solution.
  
      Args:
          universe (list): Universe of elements
          subsets (dict): Subsets of U {S1:elements,S2:elements}
          costs (dict): Costs of each subset in S - {S1:cost, S2:cost...}