code: def _match_by_norm_func(l1, l2, norm_fn, dist_fn, thresh):
      common = []
      l1_only_idx = set(range(len(l1)))
      l2_only_idx = set(range(len(l2)))
      buckets_l1 = _group_by_fn(enumerate(l1), lambda x: norm_fn(x[1]))
      buckets_l2 = _group_by_fn(enumerate(l2), lambda x: norm_fn(x[1]))
      for normed, l1_elements in buckets_l1.items():
          l2_elements = buckets_l2.get(normed, [])
          if not l1_elements or not l2_elements:
              continue
          _, (_, e1_first) = l1_elements[0]
          _, (_, e2_first) = l2_elements[0]
          match_is_ambiguous = not (
              len(l1_elements) == len(l2_elements) and (
                  all(e2 == e2_first for (_, (_, e2)) in l2_elements) or
                  all(e1 == e1_first for (_, (_, e1)) in l1_elements)
              )
          )
          if match_is_ambiguous:
              continue
          for (e1_idx, e1), (e2_idx, e2) in zip(l1_elements, l2_elements):
              if dist_fn(e1, e2) > thresh:
                  continue
              l1_only_idx.remove(e1_idx)
              l2_only_idx.remove(e2_idx)
              common.append((e1, e2))
      l1_only = [l1[i] for i in l1_only_idx]
      l2_only = [l2[i] for i in l2_only_idx]
      return common, l1_only, l2_only