docs: protected double[][] refineResult(Relation<V> relation, double[][] means, List<ModifiableDBIDs> clusters, final WritableDataStore<Meta> metas, ArrayModifiableDBIDs tids) {
      NumberVectorDistanceFunction<? super V> df = getDistanceFunction();
      // Our desired cluster size:
      final int minsize = tids.size() / k; // rounded down
      final int maxsize = (tids.size() + k - 1) / k; // rounded up
  
      // Comparator: sort by largest gain by transfer
      final Comparator<DBIDRef> comp = new Comparator<DBIDRef>() {
        @Override
        public int compare(DBIDRef o1, DBIDRef o2) {
          Meta c1 = metas.get(o1), c2 = metas.get(o2);
          return Double.compare(c1.priority(), c2.priority());
        }
      };
      // List for sorting cluster preferences
      final int[] preferences = MathUtil.sequence(0, k);
      // Comparator for this list.
      final PreferenceComparator pcomp = new PreferenceComparator();
  
      // Initialize transfer lists:
      ArrayModifiableDBIDs[] transfers = new ArrayModifiableDBIDs[k];
      for(int i = 0; i < k; i++) {
        transfers[i] = DBIDUtil.newArray();
      }
  
      DBIDArrayIter id = tids.iter();
      for(int iter = 0; maxiter <= 0 || iter < maxiter; iter++) {
        updateDistances(relation, means, metas, df);
        tids.sort(comp);
        int active = 0; // Track if anything has changed
        for(id.seek(0); id.valid(); id.advance()) {
          Meta c = metas.get(id);
          IntegerArrayQuickSort.sort(preferences, pcomp.select(c));
          ModifiableDBIDs source = clusters.get(c.primary);
          assert (source.contains(id));
          tloop: for(int i : preferences) {
            if(i == c.primary) {
              continue; // Already assigned here
            }
            ModifiableDBIDs dest = clusters.get(i);
            // Can we pair this transfer?
            final double gain = c.gain(i);
            for(DBIDMIter other = transfers[i].iter(); other.valid(); other.advance()) {
              Meta c2 = metas.get(other);
              if(gain + c2.gain(c.primary) > 0) {
                transfer(metas, c2, dest, source, other, c.primary);
                transfer(metas, c, source, dest, id, i);
                active += 2;
                other.remove(); // last, as this invalidates the reference!
                source = dest; // We are assigned here now.
                continue tloop; // Can try another transfer, with next cluster.
              }
            }
            // If cluster sizes allow, move a single object.
            if(gain > 0 && (dest.size() < maxsize && source.size() > minsize)) {
              transfer(metas, c, source, dest, id, i);
              active += 1;
              source = dest; // We are assigned here now.
              continue tloop;
            }
          }
          // If the object would prefer a different cluster, put in outgoing
          // transfer list.
          if (c.primary != preferences[0] && c.dists[c.primary] > c.dists[preferences[0]]) {
            transfers[c.primary].add(id);
          }
        }
        // TODO: try to get more transfers out of the transfer lists done by
        // considering more than one object?
        int pending = 0;
        // Clear transfer lists for next iteration.
        for(int i = 0; i < k; i++) {
          pending += transfers[i].size();
          transfers[i].clear();
        }
        if(LOG.isDebuggingFine()) {
          LOG.debugFine("Iteration #" + iter + ": performed " + active + " transfers skipped " + pending);
        }
        if(active <= 0) {
          break;
        }
        // Recompute means after reassignment
        means = means(clusters, means, relation);
      }
      return means;
    }