class IterableDiff<T> {

  private final ComparisonStrategy comparisonStrategy;

  List<T> unexpected;
  List<T> missing;

  IterableDiff(Iterable<T> actual, Iterable<T> expected, ComparisonStrategy comparisonStrategy) {
    this.comparisonStrategy = comparisonStrategy;
    // return the elements in actual that are not in expected: actual - expected
    this.unexpected = unexpectedActualElements(actual, expected);
    // return the elements in expected that are not in actual: expected - actual
    this.missing = missingActualElements(actual, expected);
  }

  static <T> IterableDiff<T> diff(Iterable<T> actual, Iterable<T> expected, ComparisonStrategy comparisonStrategy) {
    return new IterableDiff<>(actual, expected, comparisonStrategy);
  }

  static <T> IterableDiff<T> diff(Iterable<T> actual, Iterable<T> expected) {
    return diff(actual, expected, StandardComparisonStrategy.instance());
  }

  boolean differencesFound() {
    return !unexpected.isEmpty() || !missing.isEmpty();
  }

  /**
   * Returns the list of elements in the first iterable that are not in the second, i.e. first - second
   *
   * @param actual the list we want to subtract from
   * @param expected the list to subtract
   * @return the list of elements in the first iterable that are not in the second, i.e. first - second
   */
  private List<T> unexpectedActualElements(Iterable<T> actual, Iterable<T> expected) {
    List<T> missingInFirst = new ArrayList<>();
    // use a copy to deal correctly with potential duplicates
    List<T> copyOfExpected = newArrayList(expected);
    for (T elementInActual : actual) {
      if (isActualElementInExpected(elementInActual, copyOfExpected)) {
        // remove the element otherwise a duplicate would be found in the case if there is one in actual
        iterablesRemoveFirst(copyOfExpected, elementInActual);
      } else {
        missingInFirst.add(elementInActual);
      }
    }
    return unmodifiableList(missingInFirst);
  }

  private boolean isActualElementInExpected(T elementInActual, List<T> copyOfExpected) {<FILL_FUNCTION_BODY>}

  private List<T> missingActualElements(Iterable<T> actual, Iterable<T> expected) {
    List<T> missingInExpected = new ArrayList<>();
    // use a copy to deal correctly with potential duplicates
    List<T> copyOfActual = newArrayList(actual);
    for (T expectedElement : expected) {
      if (iterableContains(copyOfActual, expectedElement)) {
        // remove the element otherwise a duplicate would be found in the case if there is one in actual
        iterablesRemoveFirst(copyOfActual, expectedElement);
      } else {
        missingInExpected.add(expectedElement);
      }
    }
    return unmodifiableList(missingInExpected);
  }

  private boolean iterableContains(Iterable<?> actual, T expectedElement) {
    return comparisonStrategy.iterableContains(actual, expectedElement);
  }

  private void iterablesRemoveFirst(Iterable<?> actual, T value) {
    comparisonStrategy.iterablesRemoveFirst(actual, value);
  }
}

    // the order of comparisonStrategy.areEqual is important if element comparison is not symmetrical, we must compare actual to
    // expected but not expected to actual, for ex recursive comparison where:
    // - actual element is PersonDto, expected a list of Person
    // - Person has more fields than PersonDto => comparing PersonDto to Person is ok as it looks at PersonDto fields only,
    // --- the opposite always fails as the reference fields are Person fields and PersonDto does not have all of them.
    return copyOfExpected.stream().anyMatch(expectedElement -> comparisonStrategy.areEqual(elementInActual, expectedElement));
  
<no_super_class>