Udostępnij za pośrednictwem


Найди ошибку: плохие сравнения. Часть 2

Предположим, я хочу отсортировать набор строк вначале по длине, а потом каждую группу с одинаковой длиной отсортировать еще по какому-нибудь критерию. Мы легко можем сделать это на языке высокого уровня.

 static Comparison<string> FirstByLength(Comparison<string> thenBy)
{
return (string x, string y) =>
{
// Строки равные null, располагаются перед пустыми строками; и помните, что нам нужно обеспечить полную упорядоченность.
if (x == null && y == null)
      return 0;
    if (x == null)
      return -1;
    if (y == null)
      return 1;
    if (x.Length > y.Length)
      return 1;
    if (x.Length < y.Length)
      return -1;
    // Строки равны; сортируем их по другому критерию.
    return thenBy(x, y);
};
}

Отлично. Идея создания новых функций сравнения по уже существующим, довольно интересна. Мы даже можем создать функцию, сортирующую в обратном порядке.

 static Comparison<string> Reverse(Comparison<string> comparison)
{
  return (string x, string y) => -comparison(x, y);
}

Однако, по крайней мере одна из этих функций сравнения содержит трудноуловимую ошибку. Сможете ли вы ее найти?

.

.

.

.

.

.

.

.

Давайте повторим еще раз наш контракт. Функция сравнения возвращает отрицательное целое, если первый аргумент меньше второго, положительное целое, если первый аргумент больше второго и 0, если они равны. Подойдет любое целочисленное значение, и, в частности, в качестве отрицательного целого подойдет Int32.MinValue. Предположим, у нас есть странная функция сравнения, которая вместо -1 возвращает Int32.MinValue.

 Comparison<string> bizarre = whatever;

И мы используем ее так:

 Comparison<string> reverseFirstByLength = Reverse(FirstByLength(bizarre));

Предположим, что строки имеют одинаковую длину, а странная функция для них возвращает Int32.MinValue. Функция Reverse должна вернуть положительное число, однако -Int32.MinValue либо сгенерирует исключение (в проверяемом (checked) контексте), либо назад вернет Int32.MinValue (в непроверяемом (unchecked) контексте). Помните, что количество отрицательных значений на одно больше, чем положительных.

Правильная реализация функции Reverse следующая:

 static Comparison<string> Reverse(Comparison<string> comparison)
{
  return (string x, string y) =>
  {
    int result = comparison(x, y);
    if (result > 0) return -1;
    if (result < 0) return 1;
    return 0;
  };
}

Или, как предложили в комментариях, поменять местами левый и правый аргументы:

 static Comparison<string> Reverse(Comparison<string> comparison)
{
  return (string x, string y) => comparison(y, x);
}

В следующий раз: попробуем отыскать еще одну ошибку.

Оригинал статьи