class RSpec::Matchers::BuiltIn::ContainExactly::PairingsMaximizer

@private
that produces the fewest unmatched items.
looking for a solution which pairs all elements in both lists, or, barring that,
(or vice versa). From here, it performs a brute-force depth-first search,
What’s left is only the items which match multiple items from the other list
considered further.
* Any items which reciprocally match only each other are paired up and not
from these arrays.
The extra items and missing items in the matcher failure message are derived
placed into the ‘unmatched_expected_indexes` or `unmatched_actual_indexes` array.
* Any items which match none of the items in the other list are immediately
problem space:
force solution, but with a few heuristics applied to reduce the size of the
conversely, to minimize the number of unpaired items. It’s essentially a brute
This class is designed to maximize the number of actual/expected pairings – or,
compared to another expected element matcher, we need to consider every possible pairing.
an expected element which is a matcher that matches a superset of actual items
(/foo/ with “fool”), which would leave /fool/ and “food” unmatched. When we have
the original algorithm used by this matcher would pair the first elements it could
This should pass (because we can pair /fool/ with “fool” and /foo/ with “food”), but
expect([“fool”, “food”]).to contain_exactly(/foo/, /fool/)
much more complicated. Consider this expression:
Once we started supporting composing matchers, the algorithm for this matcher got

def apply_pairing_to(indeterminates, original_matches, other_list_index)

def apply_pairing_to(indeterminates, original_matches, other_list_index)
  indeterminates.inject({}) do |accum, index|
    accum[index] = original_matches[index] - [other_list_index]
    accum
  end
end

def best_solution_for_pairing(expected_index, actual_index)

def best_solution_for_pairing(expected_index, actual_index)
  modified_expecteds = apply_pairing_to(
    solution.indeterminate_expected_indexes,
    expected_to_actual_matched_indexes, actual_index)
  modified_expecteds.delete(expected_index)
  modified_actuals = apply_pairing_to(
    solution.indeterminate_actual_indexes,
    actual_to_expected_matched_indexes, expected_index)
  modified_actuals.delete(actual_index)
  solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution
end

def categorize_indexes(indexes_to_categorize, other_indexes)

def categorize_indexes(indexes_to_categorize, other_indexes)
  unmatched     = []
  indeterminate = []
  indexes_to_categorize.each_pair do |index, matches|
    if matches.empty?
      unmatched << index
    elsif !reciprocal_single_match?(matches, index, other_indexes)
      indeterminate << index
    end
  end
  return unmatched, indeterminate
end

def find_best_solution

def find_best_solution
  return solution if solution.candidate?
  best_solution_so_far = NullSolution
  expected_index = solution.indeterminate_expected_indexes.first
  actuals = expected_to_actual_matched_indexes[expected_index]
  actuals.each do |actual_index|
    solution = best_solution_for_pairing(expected_index, actual_index)
    return solution if solution.ideal?
    best_solution_so_far = solution if best_solution_so_far.worse_than?(solution)
  end
  best_solution_so_far
end

def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)

def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
  @expected_to_actual_matched_indexes = expected_to_actual_matched_indexes
  @actual_to_expected_matched_indexes = actual_to_expected_matched_indexes
  unmatched_expected_indexes, indeterminate_expected_indexes =
    categorize_indexes(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
  unmatched_actual_indexes, indeterminate_actual_indexes =
    categorize_indexes(actual_to_expected_matched_indexes, expected_to_actual_matched_indexes)
  @solution = Solution.new(unmatched_expected_indexes,     unmatched_actual_indexes,
                           indeterminate_expected_indexes, indeterminate_actual_indexes)
end

def reciprocal_single_match?(matches, index, other_list)

def reciprocal_single_match?(matches, index, other_list)
  return false unless matches.one?
  other_list[matches.first] == [index]
end