code: def separate_groups(groups, key, total):
      
      optimum, extra = compute_optimum(len(groups), total)
      over_loaded, under_loaded, optimal = _smart_separate_groups(groups, key, total)
      # If every group is optimal return
      if not extra:
          return over_loaded, under_loaded
      # Some groups in optimal may have a number of elements that is optimum + 1.
      # In this case they should be considered over_loaded.
      potential_under_loaded = [
          group for group in optimal
          if key(group) == optimum
      ]
      potential_over_loaded = [
          group for group in optimal
          if key(group) > optimum
      ]
      revised_under_loaded = under_loaded + potential_under_loaded
      revised_over_loaded = over_loaded + potential_over_loaded
      return (
          sorted(revised_over_loaded, key=key, reverse=True),
          sorted(revised_under_loaded, key=key),
      )
docs: Separate the group into overloaded and under-loaded groups.
  
      The revised over-loaded groups increases the choice space for future
      selection of most suitable group based on search criteria.
  
      For example:
      Given the groups (a:4, b:4, c:3, d:2) where the number represents the number
      of elements for each group.
      smart_separate_groups sets 'a' and 'c' as optimal, 'b' as over-loaded
      and 'd' as under-loaded.
  
      separate-groups combines 'a' with 'b' as over-loaded, allowing to select
      between these two groups to transfer the element to 'd'.
  
      :param groups: list of groups
      :param key: function to retrieve element count from group
      :param total: total number of elements to distribute
      :returns: sorted lists of over loaded (descending) and under
          loaded (ascending) group