docs: def hash_parameters(keys, minimize=True, to_int=None):
      """
      Calculates the parameters for a perfect hash. The result is returned
      as a HashInfo tuple which has the following fields:
  
      t
         The "table parameter". This is the minimum side length of the
         table used to create the hash. In practice, t**2 is the maximum
         size of the output hash.
      slots
         The original inputs mapped to a vector. This is the hash
         function.
      r
         The displacement vector. This is the displacement of the given
         row in the result vector. To find a given value, use
         ``x + r[y]``.
      offset
         The amount by which to offset all values (once converted to ints)
      to_int
         A function that converts the input to an int (if given).
  
      Keyword parameters:
  
      ``minimize``
          Whether or not offset all integer keys internally by the minimum
          value. This typically results in smaller output.
      ``to_int``
          A callable that converts the input keys to ints. If not
          specified, all keys should be given as ints.
  
  
      >>> hash_parameters([1, 5, 7], minimize=False)
      HashInfo(t=3, slots=(1, 5, 7), r=(-1, -1, 1), offset=0, to_int=None)
  
      >>> hash_parameters([1, 5, 7])
      HashInfo(t=3, slots=(1, 5, 7), r=(0, 0, 2), offset=-1, to_int=None)
  
      >>> l = (0, 3, 4, 7 ,10, 13, 15, 18, 19, 21, 22, 24, 26, 29, 30, 34)
      >>> phash = hash_parameters(l)
      >>> phash.slots
      (18, 19, 0, 21, 22, 3, 4, 24, 7, 26, 30, 10, 29, 13, 34, 15)
  
      For some values, the displacement vector will be rather empty:
  
      >>> hash_parameters('Andrea', to_int=ord).r
      (1, None, None, None, 0, -3, 4, None)
      """
  
      # If to_int is not assigned, simply use the identity function.
      if to_int is None:
          to_int = __identity
  
      key_to_original = {to_int(original): original for original in keys}
  
      # Create a set of all items to be hashed.
      items = list(key_to_original.keys())
  
      if minimize:
          offset = 0 - min(items)
          items = frozenset(x + offset for x in items)
      else:
          offset = 0
  
      # 1. Start with a square array (not stored) that is t units on each side.
      # Choose a t such that t * t >= max(S)
      t = choose_best_t(items)
      assert t * t > max(items) and t * t >= len(items)
  
      # 2. Place each key K in the square at location (x,y), where
      # x = K mod t, y = K / t.
      row_queue = place_items_in_square(items, t)
  
      # 3. Arrange rows so that they'll fit into one row and generate a
      # displacement vector.
      final_row, displacement_vector = arrange_rows(row_queue, t)
  
      # Translate the internal keys to their original items.
      slots = tuple(key_to_original[item - offset] if item is not None else None
                    for item in final_row)
  
      # Return the parameters
      return HashInfo(
          t=t,
          slots=slots,
          r=displacement_vector,
          offset=offset,
          to_int=to_int if to_int is not __identity else None
      )