Операция ToLookup

33

Операция ToLookup создает объект Lookup типа <К, Т> или, возможно, <К, Е> из входной последовательности типа T, где К — тип ключа, a T — тип хранимых значений. Либо же, если Lookup имеет тип <К, E>, то типом хранимых значений может быть Е, который отличается от типа элементов входной последовательности Т.

Хотя все прототипы операции ToLookup создают Lookup, возвращают они объект, реализующий интерфейс ILookup. В этой статье объект, реализующий интерфейс ILookup, обычно будет называться просто Lookup.

Если вы незнакомы с классом Lookup коллекций C#, то знайте, что он позволяет хранить элементы, которые могут быть извлечены по ключу. Каждый ключ должен быть уникальным, и под одним ключом может быть сохранено множество элементов. Обращение по индексу к Lookup с применением ключа извлекает последовательность сохраненных с этим ключом элементов.

Операция ToLookup имеет четыре прототипа, описанные ниже:

Первый прототип операции ToLookup
public static ILookup<K, T> ToLookup<T, K>( 
       this IEnumerable<T> source, 
       Func<T, K> keySelector);

В этом прототипе создается Lookup типа <К, Т> и возвращается за счет перечисления входной последовательности source. Делегат метода keySelector вызывается для извлечений ключевого значения из каждого входного элемента, и этот ключ является ключом элемента в Lookup. Эта версия операции сохраняет в Lookup значения того же типа, что и элементы входной последовательности.

Поскольку прототип предотвращает указание объекта проверки эквивалентности IEqualityComparer<K>, данная версия Lookup по умолчанию использует в качестве такого объекта экземпляр класса EqualityComparer<K>.Default.

Второй прототип операции ToLookup

Второй прототип ToLookup подобен первому, за исключением того, что предоставляет возможность указывать объект проверки эквивалентности IEqualityComparer<K>. Он рассматривается ниже:

public static ILookup<K, T> ToLookup<T, K>( 
     this IEnumerable<T> source, 
     Func<T, K> keySelector, 
     IEqualityComparer<K> comparer);

Этот прототип дает возможность указывать объект comparer типа IEquality Comparer<К> для проверки эквивалентности ключей. Поэтому при добавлении или обращении к элементу в Lookup он использует этот объект comparer для сравнения указанного ключа с ключами, уже находящимися в Lookup, определяя их соответствие.

Реализация по умолчанию интерфейса IEqualityComparer<K> предоставляется EqualityComparer.Default. Однако если вы собираетесь использовать класс проверки эквивалентности по умолчанию, то указывать объект, проверяющий эквивалентность, незачем, потому что предыдущий прототип применяет его в любом случае. Класс StringComparer реализует некоторые классы проверки эквивалентности, такие как, например, игнорирующий различия в регистре. Таким образом, ключи "Joe" и "joe" трактуются как один и тот же ключ.

Третий прототип операции ToLookup

Третий прототип ToLookup подобен первому, за исключением того, что позволяет указывать селектор элемента, чтобы тип данных значения, сохраненного в Lookup, мог отличаться от типа элементов входной последовательности. Третий прототип представлен ниже:

public static ILookup<K, E> ToLookup<T, K, E> ( 
        this IEnumerable<T> source, 
        Func<T, K> keySelector, 
        Func<T, E> elementSelector);

В аргументе elementSelector можно указывать делегат метода, возвращающий часть входного элемента, либо вновь созданный объект совершенно другого типа, который должен быть сохранен в Lookup.

Четвертый прототип операции ToLookup

Четвертый прототип операции ToLookup концентрирует в себе лучшее из предыдущих. Он представляет собой комбинацию второго и третьего прототипов, а это означает, что можно указывать elementSelector и объект проверки эквивалентности comparer. Четвертый прототип описан ниже:

public static ILookup<K, E> ToLookup<T, K, E>( 
    this IEnumerable<T> source, 
    Func<T, K> keySelector, 
    Func<T, E> elementSelector, 
    IEqualityComparer<K> comparer);

Этот прототип позволяет указывать и elementSelector, и comparer.

Если аргумент source, keySelector или elementSelector равен null, либо ключ, возвращенный keySelector, равен null, генерируется исключение ArgumentNullException.

В следующем примере прототипа ToLookup вместо типичного массива cars, который постоянно применялся, необходим класс с элементами, содержащими члены, которые могут быть использованы в качестве ключей, но не являются уникальными. Для этой цели задействован общий класс Actor.

Ниже содержится пример вызова операции ToLookup с использованием класса Actor:

ILookup<int, Actor> lookup = Actor.GetActors().ToLookup(k => k.birthYear);

      // Посмотрим, можно ли найти кого-то, рожденного в 1964 г.
      IEnumerable<Actor> actors = lookup[1964];
      foreach (var actor in actors)
        Console.WriteLine("{0} {1}", actor.firstName, actor.lastName);

Сначала создается Lookup с указанием члена Actor.birthYear в качестве ключа к Lookup. Затем производится доступ к нему по индексу с ключом 1964. Затем возвращенные значения перечисляются. Ниже показан результат:

Вызов первого прототипа ToLookup

Неожиданно был получен множественный результат. Хорошо, что входная последовательность была преобразована в Lookup вместо Dictionary, т.к. имеется несколько элементов с одинаковым ключом.

Для построения примера, демонстрирующего работу второго прототипа ToLookup, в общий класс Actor вносится небольшая модификация. Будет создан класс Actor2, во всем идентичный Actor, за исключением того, что член birthYear теперь имеет тип string вместо int:

public class Actor2
  {
    public string birthYear;
    public string firstName;
    public string lastName;

    public static Actor2[] GetActors()
    {
      Actor2[] actors = new Actor2[] {
        new Actor2 { birthYear = "1964", firstName = "Keanu", lastName = "Reeves" },
        new Actor2 { birthYear = "1968", firstName = "Owen", lastName = "Wilson" },
        new Actor2 { birthYear = "1960", firstName = "James", lastName = "Spader" },
        // Пример даты с ведущим нулем
        new Actor2 { birthYear = "01964", firstName = "Sandra", 
          lastName = "Bullock" },  
      };

      return (actors);
    }
  }

Обратите внимание, что в этом классе тип члена birthYear изменен на string. Теперь операция ToLookup будет вызываться, как показано ниже:

ILookup<string, Actor2> lookup = Actor2.GetActors()
        .ToLookup(k => k.birthYear, new MyStringifiedNumberComparer());

IEnumerable<Actor2> actors = lookup["0001964"];
foreach (var actor in actors)
      Console.WriteLine("{0} {1}", actor.firstName, actor.lastName);

...

public class MyStringifiedNumberComparer : IEqualityComparer<string>
  {
    public bool Equals(string x, string y)
    {
      return (Int32.Parse(x) == Int32.Parse(y));
    }

    public int GetHashCode(string obj)
    {
      return Int32.Parse(obj).ToString().GetHashCode();
    }
  }

Здесь используется тот же самый объект проверки эквивалентности, что и в примере Dictionary. В этом случае входная последовательность преобразуется в Lookup, с предоставлением объекта проверки эквивалентности, поскольку известно, что ключ, хранимый в виде string, может иногда содержать ведущие нули. Этот объект знает, как с этим справиться. Результат получается таким же, как и в первом примере:

Обратите внимание на попытку извлечь все элементы со значением ключа "0001964". В результате получаются элементы с ключами "1964" и "01964". Значит, объект проверки эквивалентности работает.

В примере с третьим прототипом операции ToLookup применяется тот же самый класс Actor, что и в примере кода первого прототипа для ToLookup. Ниже показан необходимый код:

ILookup<int, string> lookup = Actor.GetActors()
        .ToLookup(k => k.birthYear,
                  a => string.Format("{0} {1}", a.firstName, a.lastName));

IEnumerable<string> actors = lookup[1964];
foreach (var actor in actors)
        Console.WriteLine("{0}", actor);

Использование варианта операции ToLookup с elementSelector позволяет сохранять в Lookup данные другого типа, отличного от типа данных элемента входной последовательности.

При написании примера для четвертого прототипа ToLookup используется класс Actor2 и общий класс MyStringifiedNumberComparer. Код примера показан ниже:

ILookup<string, string> lookup = Actor2.GetActors()
        .ToLookup(k => k.birthYear,
                  a => string.Format("{0} {1}", a.firstName, a.lastName),
                  new MyStringifiedNumberComparer());

IEnumerable<string> actors = lookup["0001964"];
foreach (var actor in actors)
        Console.WriteLine("{0}", actor);

Как видите, здесь производится обращение к Lookup по индексу с указанием значения ключа, отличного от любого из значений ключей извлеченных значений, поэтому можно сделать вывод, что объект проверки эквивалентности работает. Вместо сохранения всего объекта Actor2 сохраняется просто интересующая строка.

CodeChick.io - Задачи по программированию с автоматической проверкой
Пройди тесты
Лучший чат для C# программистов