6199

Коллекции данных и их обработка на языке С# и в среде Net

Реферат

Информатика, кибернетика и программирование

Коллекции В некоторых ситуациях возникает потребность хранения более одного элемента в коллекциях данных. Может понадобиться хранить группу или коллекцию, некоторым образом включенную в более крупную конструкцию. Язык С# и среда Net представляют мно...

Русский

2012-12-30

219 KB

12 чел.

Коллекции

В некоторых ситуациях возникает потребность хранения более одного элемента в коллекциях данных. Может понадобиться хранить группу или коллекцию, некоторым образом включенную в более крупную конструкцию. Язык С# и среда Net представляют множество возможностей подобного рода для хранения и сортировки данных в программах. К ним относятся:

  •  коллекции;
  •  массивы-списки;
  •  стеки;
  •  очереди;
  •  сортированные списки;
  •  словари (иногда называемые картами).

За исключением базового System.Array, все эти классы структур данных находятся в пространстве имен System.Collections.

Наименование System.Collections отражает одну из двусмысленностей, которой страдает компьютерная терминология. Часто термин «коллекция» применяется для обозначения любой структуры данных. Однако он также имеет более специфическое значение, а именно - класс, реализующий IEnumerable или ICollection - определенные типы структур данных. Здесь мы будем использовать термин «коллекция» в более специфическом смысле, за исключением тех случаев, когда имена базовых классов .NET вынудят нас применять его в более общем контексте.

Коллекции

Идея коллекции заключается в том, что она представляет набор объектов, доступ к которым можно получить, перебирая все ее элементы по очереди. В частности, можно получить доступ к набору объектов, используя цикл foreach. Например, когда вы пишете код, подобный показанному ниже, то предполагаете, что messageSet является коллекцией:

foreach (string nextMessage in messageSet)

{DoSomething(nextMessage);}

Возможность применения цикла foreach — главное свойство коллекций. Помимо того, коллекции предлагают много дополнительных средств.

Что такое коллекция?

Внутренне объект является коллекцией, если способен предоставить ссылку на вязанный с ним объект, называемый перечислителем (enumerator), который позволяет проходить по элементам коллекции. Точнее говоря, коллекция должна реализовывать интерфейс System.Collections.IEnumerable. В интерфейсе IEnumerable-определен только один метод, как показано ниже:

interface IEnumerable 

{

 IEnumerator GetEnumerator();

}

Назначение GetEnumerator () — возвращать объект-перечислитель. Как можно понять из предыдущего кода, объект-перечислитель должен реализовывать интерфейс System.Collections.IEnumerator.

Дополнительный интерфейс коллекций — ICollection — наследуется от IEnumerable. Этот интерфейс реализует более сложные разновидности коллекций. Помимо GetEnumerator(), он предусматривает свойство, возвращающее количество элементов в коллекции. Кроме того, он поддерживает копирование коллекции в массив и может предоставлять информацию, указывающую на его безопасность к потокам. Однако в следующем примере рассматривается только простейший интерфейс коллекций — IEnumerable.

IEnumerator выглядит так, как показано ниже:

interface IEnumerator {

object Current { get;}

bool MoveNext();

void Reset ();}

IEnumerator должен работать следующим образом: объект, который реализует его, должен быть ассоциирован с одной из конкретных коллекций. Когда этот объект впервые инициализируется, он не ссылается ни на какой из элементов коллекции, и для того, чтобы он ссылался на первый ее элемент, необходимо вызвать метол MoveNext( ). После этого текущий элемент можно получить через свойство Current. Current возвращает объектную ссылку, поэтому ее необходимо привести к типу объекта, который, как вы ожидаете, хранится в коллекции. С этим объектом вы вольны делать что угодно, прежде чем переходить к следующему элементу коллекции, снова вызвав метод MoveNext( ). Этот процесс повторяется до тех пор, пока не закончатся элементы коллекции — вы узнаете об этом, когда свойство Current вернет null.

В любой момент можно вернуться к началу коллекции вызовом метода Reset( ). Отметим, что Reset( ) возвращает указатель в точку, предшествующую первому элементу коллекции, поэтому чтобы получить первый элемент, нужно снова вызвать MoveNext( ).

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

В некотором смысле коллекция — это базовый тип групп объектов, поскольку он не позволяет добавлять или удалять элементы группы. Все, что можно с ней делать — извлекать элементы в порядке, определяемом самой коллекцией и исследовать их. Невозможно даже заменять или модифицировать позиции в коллекции, поскольку свойство Current доступно только для чтения. Чаще всего коллекции нужны для того, чтобы синтаксически согласовать группы данных с циклом foreach. Очевидно, что массивы также представляют собой коллекции, поскольку к ним с успехом можно применять циклы foreach. Для конкретного случая массивов, перечислитель, применяемый к классу System.Array, проходит по элементам в порядке возрастания индекса, начиная с нуля.

Одним важным аспектом коллекций является то, что перечислитель возвращается как отдельный объект. Это не тот же объект, что сама коллекция. Причина в том, что таким образом можно одновременно с одной и той же коллекцией использовать несколько перечислителей.

Массивы-списки

Массив-список очень похож на массив, за исключением того, что он имеет возможность расширения. Эту структуру данных представляет класс System.Collections.ArrayList.

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

Простейшая форма создания экземпляра ArrayList выглядит следующим образом:

ArrayList baseballTeams = new ArrayList();

При этом создается объект ArrayList емкостью 16 — это значит, что в массив можно поместить 16 ссылок. Чтобы получить доступ к классу АгrayList, необходимо в проекте обратиться к пространству имен System.Collections. Можно также создать экземпляр ArrayList, указав начальную емкость, например:

ArrayList baseballTeams = new ArrayList(20);

Можно также установить емкость объекта ArrayList непосредственно после обычного создания экземпляра, используя свойство Capacity, как показано в следующем примере:

ArrayList baseballTeams  = new ArrayList();

baseballTeams.Capacity = 20;

Отметим, однако, что изменение емкости перемещает весь ArrayList в новый блок памяти указанного размера. Некоторые другие замечания, важные для понимания емкости ArrayList, связаны с ее ростом. Изначально, как уже говорилось, емкость ArrayList устанавливается равной 16 элементам. Идея ArrayList состоит том, что он может расти при необходимости. Однако когда вы добавляете 17-й элемент к ArrayList по умолчанию, то при этом создается новый объект ArrayList который имеет вдвое большую емкость — то есть 32 элемента. Как только новый объект ArrayList создан, содержимое исходного объекта копируется в этот новый экземпляр. После этого исходный объект ArrayList помечается для удаления сборщиком мусора. Точно так же он ведет себя и тогда, когда вы определяете емкость ArrayList самостоятельно.

Например, если вы используете следующую конструкцию:

ArrayList baseballTeams  = new ArrayList(20);

то создается экземпляр ArrayList емкостью 20 элементов. При добавлении к нему 21-го элемента создается новый ArrayList — на этот раз размером в 40 элементов Просто имейте в виду, что когда ArrayList вынужден динамически, расширяться, его емкость удваивается.

Количество элементов в ArrayList можно получить через свойство Count:

int nBaseballTeams = baseballTeams.Count;

Создав экземпляр ArrayList, к нему сразу можно добавлять элементы с помощью метода Add( ). Следующий пример консольного приложения использует метод Add( ), а затем отображает все добавленные элементы:

ArrayList baseballTeams = new ArrayList();

baseballTeams.Add("St.Louis Cardinals");

baseballTeams.Add("Seattle Mariners");

baseballTeams.Add("Florida Marlins");

foreach(string item in baseballTeams)

{Console.Write (item + "\n") ;}

Console.ReadLine();

Один момент, который нужно отметить, говоря о добавлении элементов в ArrayList — это то, что ArrayList трактует свои элементы как ссылки на object. Это значит, что в ArrayList можно сохранять объекты любого рода, но при обращении к ним придется приводить их обратно к соответствующему типу данных, как показано ниже:

string elementl = (string)baseballTeams[l];

В дополнение к приведению этого элемента ArrayList к string, предыдущий пример обращается к нему в коллекции по индексатору (indexer). В данном примере elementl присваивается значение Seattle Mariners. При добавлении элементов к ArrayList можно  специфицировать  местоположение в  коллекции, куда следует вставить элемент, используя метод Insert( ). Это иллюстрирует следующий пример:

baseballTeams.Insert(1, "San Francisco Giants");

Существует также удобная перегрузка Insert( ), которая позволяет вставить сразу все элементы коллекции в ArrayList, передав ссылку на интерфейс ICollection.

Можно удалять элементы из определенной точки коллекции, используя метод RemoveAt( ). Это делается следующим образом:

baseballTeams.RemoveAt(1);

Удаление объекта можно выполнить методом — Remove( ). Однако важно отметить, что эта операция выполняется дольше, потому что ей приходится выполнять линейный поиск в массиве, чтобы добраться до указанного объекта. При вставке и удалении элемента все последующие элементы коллекции соответующим образом сдвигаются в памяти, даже если никакого повторного размещения  всего ArrayList не требуется.

До сих пор мы рассматривали добавление и удаление отдельных элементов в коллекции ArrayList. Другой способ добавления позиций в ArrayList предусматривает применение метода AddRange( ). Этот метод позволяет добавить целую коллекцию элементов за один раз. Вернемся к предыдущему примеру с бейсбольными командами и добавим несколько дополнительных элементов. На этот раз добавим коллекцию позиций, представленную в массиве строк. Вот код примера:

ArrayList baseballTeams = new ArrayList(); baseballTeams.Add("St.Louis Cardinals"); baseballTeams.Add("Seattle Mariners");

baseballTeams.Add("Florida Marlins");

string[] myStringgArray = new string[2]; myStringArray[0] = "San Francisco Giants"; myStringArray[l] = "Los Angeles Dodgers"; baseballTeams.AddRange(myStringArray);

foreach(string item in baseballTeams)

Console.Write(item + "\n");

Console.Readline();

Здесь создается строковый массив, к нему добавляется два элемента, затем вызыватся метод AddRange( ) объекта ArrayList для добавления всего этого строкового в коллекцию за один раз. После этого в коллекции оказывается пять элемен которые затем отображаются на консоли.

Метод RemoveRange( ) позволяет специфицировать диапазон элементов, которые нужно удалить из коллекции ArrayList. Метод RemoveRange( ) принимает два параметра — первый представляет позицию начального удаляемого элемента, а второй — количество удаляемых элементов, начиная с заданной позиции. Например, добавим одну строку кода к последнему примеру и посмотрим, что случится с элементами ArrayList:

ArrayList baseballTeams = new ArrayList(); baseballTeams.Add("St.Louis Cardinals");

baseballTeams.Add("Seattle Mariners"); baseballTeams.Add("Florida Marlins");

string[] myStringArray = new string[2]; myStringArray[0] = "San Francisco  Giants"; myStringArray[1] = "Los Angeles Dodgers";

baseballTeams.AddRange(myStringArray); baseballTeams.RemoveRange(2,2);

foreach(string item in baseballTeams)

{Console.Write(item +   "\n");}

Console.Readline();

Вызов метода RemoveRange( ) устанавливает, что элементы, подлежащие удалению, должны начинаться с позиции с индексом 2 ("Florida Marines"), и что нужно удалить два элемента. То есть из нашей коллекции ArrayList будут удалены строки Florida Marines и San Francisco Giants.

Еще один интересный метод объекта ArrayList — это GetRange( ). Он используется, когда необходимо скопировать определенный диапазон позиций из ArrayList. Например, представим, что у нас есть описанный выше объект ArrayList, который содержит наименования пяти бейсбольных команд. Если вам нужен новый объект ArrayList, который будет содержать только подмножество этих пяти команд, вы можете поступить следующим образом:

ArrayList secondArray=
new ArrayList(baseballTeams.GetRange(2, 2));

Метод GetRange( ) работает аналогично методу AddRange( ). Он принимает два параметра: первый — индекс места, начиная с которого нужно начать копирование, а второй — количество копируемых элементов. В данном примере создается новый объект ArrayList, в который копируется два элемента из предыдущего объекта ArrayList. Два копируемых элемента находятся в позициях 2 и 3, а их значения соответственно: Florida Marines и San Francisco Giants.

В дополнение к методу GetRange( ) для помещения элементов в ArrayList имеются также методы SetRange( ) и InsertRange( ).

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

Класс Stack

Stack (стек) — это другой тип коллекции, идеально подходящий для работы с элементами, которые рассматриваются, как временные и удаляются после использования в приложении. Класс Stack создает коллекцию в структуре, работающей по алгоритму «последним пришел — первым обслужен» (Last In First OutLIFO). Это значит, что элементы извлекаются из коллекции в порядке, обратном тому, в котором они в нее помещались. Это проиллюстрировано на рис. 9.1.

Рис. 6.1. Принцип действия стека

Элементы помещаются в Stack методом Push( ), а извлекаются — методом Pop( ). В этом разделе мы начнем с наполнения объекта Stack с последующим обратным чтением значений. Это иллюстрирует следующий пример консольного приложения:

Stack alphabet = new Stack();

alphabet.Push("A");

alphabet.Push("B");

alphabet.Push("C");

foreach (string item in alphabet)

{Console.Write(item);}

Console.Headline();

Чтобы закодировать этот пример, необходимо обратиться к пространству имен System.Collections — для доступа к классу Stack. В данном примере создается экземпляр объекта Stack и с помощью метода Push( ) наполняется буквами А, В и С (в таком порядке). Затем выполняется проход по элементам коллекции Stack и они выводятся на экран консоли. В результате получается:

СВА

В этом примере элементы читаются в порядке СВА, потому что из коллекции Stack элементы читаются всегда в порядке, противоположном порядку вставки.

Для извлечения элементов из объекта Stack также можно использовать метод Pop( ). Однако существенное отличие такого способа чтения элементов от простой итерации заключается в том, что при вызове метода Pop( ) элементы извлекаются из коллекции навсегда (то есть удаляются). Рассмотрим следующий пример, использующий метод Pop ( ):

Stack alphabet = new Stack ();

alphabet.Push("A");

alphabet.Push("B");

alphabet.Push("C");

Console.Write("Первая итерация:   ");

foreach(string item in alphabet)

{Console.Write(item);}

Console.WriteLine("\пЭлемент, извлеченный из:"+ “коллекции”+ alphabet.Pop().ToString());

Console.Write("Вторая итерация:   ");

foreach(string item in alphabet)

{Console.Write(item);}

Console.ReadLine();

В этом случае элементы А, В и С помещаются в коллекцию и отображаются на экране консоли. После этого один элемент из коллекции выталкивается:

alphabet.Pop()

Значение вытолкнутого элемента отображается на экране. Затем опять выполняется итерация по коллекции, но на этот раз буква С не отображается — теперь она там отсутствует. Причина в том, что она вытолкнута из коллекции навсегда. Запуск этого приложения выдаст следующий результат на экран консоли:

Первая  итерация: СВА

Элемент, извлеченный из коллекции: С

Вторая итерация: ВА

Относительно метода Pop( ) нужно сказать, что если вы заинтересованы в сохранении элементов в коллекции, то всегда можете использовать метод Peek( ), который позволяет получить последний вставленный в коллекцию элемент, не удаляя его оттуда, как это делает метод Pop( ).

В табл. 6.1 детализированы некоторые методы класса Stack.

Таблица 6.1. Некоторые методы класса Stack

Метод

Описание

Clear

Удаляет все элементы из коллекции.

Clone

Создает теневую копию коллекции.

СоруТо

Копирует коллекцию в существующий одномерный массив.

GetEnumerator

Возвращает объект-перечислитель коллекции.

Peek

Возвращает самый верхний объект из Stack без удаления его из коллекции.

Pop

Возвращает самый верхний объект из Stack и удаляет его из коллекции.

Push

Помещает элементы в stack.

ToArray

Копирует stack в новый массив.

Класс Queue

Работа с классом Queue (очередь) и создаваемой им коллекцией похожа на работу с классом Stack. Главное отличие состоит в том, что класс Queue создает коллекции, работающие по алгоритму «первым пришел — первым обслужен» (First In First OutFIFO). То есть элементы помещаются в коллекцию и извлекаются из нее в одинаковом порядке. Это иллюстрирует рис. 6.2.

Рис. 6.2. Принцип действия очереди

Элементы помещаются в коллекцию с помощью метода Enqueue( ), а извлекаются — с помощью метода Dequeue( ). Если мы возьмем пример с объектом Stack и модифицируем его так, чтобы использовать вместо него объект Queue, то получим отличающийся результат. Код измененного примера представлен ниже:

Queue alphabet = new Queue();

alphabet.Enqueue("A");

alphabet.Enqueue("B");

alphabet.Enqueue("C");

Console.Write("Первая итерация:   ");

foreach(string item in alphabet)

{Console.Write(item);}

Console.WriteLine("Элемент, извлеченный из коллекции:"+ alphabet.Dequeue( ).ToString());

Console.Write("Вторая итерация:   ");

foreach(string item in alphabet)

{Console.Write(item);}

Console.ReadLine( );

В этом примере класс Queue используется для сохранения значений А, В и С. После того, как они помещены в коллекцию, выполняется итерация по всем этим элементам с выводом на экран. Затем один из элементов удаляется из коллекции. Подобно тому, как в классе Stack используется метод Pop( ) , класс Queue использует метод Dequeue( ) для окончательного удаления элемента из коллекции. Это иллюстрирует вторая итерация по коллекции. Результат запуска этого приложения показан ниже:

Первая  итерация:   ABC

Элемент, извлеченный из коллекции: А

Вторая  итерация : ВС

В табл. 6.2 перечислены некоторые из методов класса Queue.

Таблица 6.2. Некоторые методы класса Queue

Метод

Описание

Clear

Удаляет все элементы из коллекции.

Clone

Создает теневую копию коллекции.

Contains

Определяет, содержится ли элемент в коллекции.

СоруТо

Копирует коллекцию в существующий одномерный массив.

Dequeue

Возвращает первый помещенный в Queue элемент и удаляет его из коллекции.

Enqueue

Помещает элементы в коллекцию.

GetEnumerator

Возвращает объект-перечислитель коллекции.

Peek

Возвращает самый верхний объект Queue, но не удаляет его из коллекции.

ToArray

Копирует Queue в новый массив.

Класс SortedList

Класс SortedList (сортированный список) создает коллекции, которые работают иначе, чем рассмотренные выше, реализующие алгоритмы LIFO и FIFO. При работе с элементами коллекции SortedList каждому из них при помещении в коллекцию присваивается идентифицирующий ключ, который позднее используется для обращения к элементу.

Подобно классу ArrayList, когда создается экземпляр объекта SortedList, то он имеет емкость в 16 элементов. Элементы помещаются в коллекцию методом Add( ):

SortedList footballTeams = new SortedList();

footballTeams.Add(l,"St.Louis Rams");

footballTeams.Add(2,"Kansas City Chiefs");

footballTeams.Add(3,"Indianapolis Colts");

for(int i = 0;i < footballTeams.Count; i++)

{Console.WriteLine("КЛЮЧ: "+

footballTeams.GetKey(i)+

"ЗНАЧЕНИЕ: "+footballTeams.GetBylndex(i));}

Console.ReadLine();

В этом примере в коллекцию SortedList добавляется три строковых элемента. Каждой помещаемой в коллекцию строке назначается ключ. В данном случае ключи имеют тип int: 1, 2 и 3. Однако с тем же успехом можно применять значения типа string для ключей, как показано ниже:

footballTeams.Add("Winner", "St. Louis Rams");

Можно выполнять итерацию по значениям и ключам коллекции SortedList, используя методы GetKeyList( ) или GetValueList(). Например, выполнить итерацию по элементам показанной в примере коллекции footballTeams можно следующим образом:

foreach(string item in footballTeams.GetValueList())

{ Console.WriteLine(item); }

А так выполняется итерация по ключам коллекции SortedList:

foreach(int item in footballTeams.GetKeyList())

{ Console.WriteLine(item);}

Обратите внимание, что в этом примере мы приводим значения ключей SortedList к типу int, поскольку именно такого типа ключи используются в данном случае. Если бы использовались ключи типа string, то, конечно, их бы следовало привести к типу string.

В табл. 6.3 перечислены методы класса SortedList.

Таблица 6.3. Методы класса SortedList

Метод

Описание

Add

Добавляет элемент в коллекцию.

Clear

Удаляет все элементы из коллекции.

Clone

Создает теневую копию коллекции.

ContainsKey

Определяет по ключу, содержится ли элемент в коллекции.

ContainsValue

Определяет по значению, содержится ли элемент в коллекции.

CopyTo

Копирует коллекцию в существующий одномерный массив.

GetBylndex

Возвращает значение в позиции индекса.

GetEnumerator

Возвращает объект-перечислитель коллекции.

GetKey

Возвращает значение элемента по его ключу.

GetKeyList

Возвращает коллекцию ключей объектов SortedList.

GetValueList

Возвращает коллекцию значений объектов SortedList.

Remove

Удаляет элемент из коллекции на основе значения ключа.

RemoveAt

Удаляет из коллекции элемент, расположенный в определенной позиции индекса.

SetBylndex

Заменяет значение элемента в определенной позиции индекса.

Словари и хеш-таблицы

Словари (dictionaries) представляют собой сложные структуры данных, позволяющие обеспечить доступ к элементам по ключу, который может быть любого типа. Они так же известны, как карты (maps) или хеш-таблицы (hash-tables). Словари очень удобны для тех случаев, когда необходимо сохранять элементы так, как это делается в массивах, но индексом служат значения произвольного типа (а не только целочисленные). Они так же позволяют легко добавлять и удалять элементы — подобно тому, как это делается в ArrayList, но без затрат, связанных с перемещением элементов в памяти.

Тип ситуаций, в которых может оказаться удобным использовать словари, проиллюстрирован примером MortimerPhonesEmployee, который мы разберем в этой лекции. Предположим, что компания сотовой связи Mortimer Phones нуждается в программном обеспечении управления персоналом. Для этого нам понадобится структура данных, в чем-то подобная массиву, которая будет содержать информацию о сотрудниках. Предположим, что каждый из сотрудников Mortimer Phones идентифицируется табельным номером — ID, состоящим из набора символов, такого как В342 или W435, и сохраняется в виде объекта класса EmployeeID. Вся подробная информация о сотруднике, например, его ID, имя и зарплата, сохраняется в объекте EmployeeData.

Предположим, у нас есть EmployeelD:

EraployeelD id = new EmployeelD("W435");

и переменная employees, которую можно синтаксически трактовать как массив объектов EmployeeData. На самом деле это будет не массив, а словарь, и поскольку это словарь, то из него можно будет получить подробную информацию о сотруднике по идентификатору:

EmployeeData theEmployee = employees[id];

В этом и проявляется мощь словарей. Они выглядят как массивы (но более развиты, чем массивы; скорее, они похожи на ArrayList, поскольку, добавляя элементы, можно изменять их емкость). Однако в качестве индекса не обязательно использовать целые числа. Вы можете применять для этого данные любого типа. Когда речь идет о словарях, то чаще говорят о ключах, а не об индексах. Грубо говоря, когда вы обращаетесь к элементу словаря по ключу (как в предыдущем примере — по объекту id), то словарь выполняет некоторую обработку значения ключа и возвращает целое, зависящее от значения этого ключа и используемое всякий раз, когда нужно поместить или извлечь значение элемента.

Приведем короткий список других случаев, когда может понадобиться использовать словари для хранения объектов:

  •  Если нужно хранить информацию о сотрудниках или других людях на основе номеров их карточек социального страхования. Хотя этот номер — целое число, его невозможно использовать в качестве обычного индекса массива, поскольку номера карточек социального страхования в США теоретически могут достигать значения 999999999. На 32-разрядных системах невозможно разместить в памяти массив такого размера. К тому же большая часть элементов этого массива будет пустой. Используя словарь, можно применять номер социального страхования в качестве индекса, при этом оставляя его небольшим.
  •  Если нужно хранить адреса, индексированные по zip-коду. В США zip-коды -это просто числа, но в Канаде и Великобритании они могут включать и буквы и цифры.
  •  Если нужно сохранять любые данные об объектах или людях, индексируя их по имени.

Хотя с точки зрения клиентского кода словари ведут себя во многом подобно динамическим массивам с очень гибким механизмом индексации, большая часть работы, обеспечивающей их функциональность, происходит «за кулисами». В принципе в качестве индексов можно использовать объекты любого класса. Однако при этом, прежде чем использовать класс в качестве ключа, вы обязаны реализовать в нем некоторые средства. Это касается метода GetHashCode( ), который все классы и структуры наследуют от System.Object.

Словари в реальной жизни

Термин словарь применяется потому, что эта структура очень похожа на реальные словари, часто используемые в повседневной жизни. В реальных словарях обычно нужно найти значение слова (в случае словаря иностранного языка — перевод слова). Совокупность строк текста, объясняющего значение слова (или его перевод) — это те данные, которые вас интересуют. Тот факт, что большой словарь может состоять из десятков тысяч единиц данных, не представляет проблему, когда вы ищете значение конкретного слова, поскольку слова упорядочены по алфавиту. По сути, слово, которое вы ищете, служит некоторым эквивалентом ключа, который используется для поиска ассоциированных с ним данных. Слово просто представляет собой средство для нахождения требуемой позиции в словаре. Таким образом, чтобы построить словарь, необходимы три вещи:

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

Алгоритм — критически важная часть словаря. Знать ключ недостаточно; также нужен способ, с помощью которого по ключу можно найти местоположение элемента в структуре данных. В словарях из реальной жизни такой алгоритм представляется расположением слов в алфавитном порядке.

Словари в .NET

В .NET базовый словарь представлен классом Hashtable, который работает по принципу реального словаря за исключением того, что предполагает, что как значение, так и ключ относятся к типу Object. Это значит, что в Hashtable можно сохранять данные любых необходимых вам структур. В отличие от этого словари из реальной жизни используют в качестве ключей только строки.

Хотя Hashtable представляет словарь общего назначения, который может хранить все, что угодно, кроме него можно определять свои собственные, более специализированные классы словарей. Microsoft предлагает абстрактный базовый класс DictionaryBase, представляющий базовую функциональность, от которого разработчики могут наследовать собственные классы.

Кроме того, в .NET предусмотрен готовый к использованию класс System.Collections.Specialized.StringDictionary, который можно использовать вместо Hashtable, когда ключами служат строки.

При создании объекта Hashtable можно указать его начальную емкость:

Hashtable  employees  = new Hashtable(53);

Как обычно, у этого класса есть много других конструкторов, но данный используется чаще всего. Обратите внимание на необычное значение начальной емкости: 53. На то имеется своя причина. Из-за алгоритма, используемого словарями, наиболее эффективно они работают, когда имеют емкость, заданную простым числом.

Добавление элементов в Hashtable осуществляется методом Add( ), однако Hashtable.Add( ) принимает два параметра, оба — объектные ссылки. Первый параметр — ссылка на ключ, вторая — ссылка на данные. Применяя классы EmployeeID и EmployeeData из примера, можно написать следующее:

EmployeelD id;

EmployeeData data;

// Инициализировать id и data, чтобы они ссылались на // одного сотрудника.

// Предполагается, что employees - экземпляр Hashtable,

// который содержит ссылки на EmployeeData

employees.Add(id, data);

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

EmployeeData data = employees[id];

Вы можете также удалять элементы массива, указывая ключ объекта, который следует удалить:

employees.Remove(id);

Чтобы узнать, сколько элементов в данный момент содержится в хеш-таблице, можно воспользоваться свойством Count:

int nEmployees = employees.Count;

Отметим, однако, что существует также метод Insert( ). Пока мы не видели, как внутри организована работа словаря, но скажем, что между добавлением и вставкой данных нет разницы. В отличие от массива или ArrayList, вы не найдете один большой блок данных в начале структуры и пустой блок — в конце. Вместо этого ситуация выглядит примерно так, как показано на рис. 6.3, где непомеченные части словаря — пусты.

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

Рис. 9.3. Пример словаря

Следует отметить, что диаграмма на рис. 6.3 существенно упрощена. Каждая пара ключ-значение на самом деле не сохраняется внутри структуры словаря; поскольку речь идет о ссылочных типах, то, что сохраняется в нем — это объектные ссылки, указывающие, где именно в куче находятся объекты.

Как работают словари

До сих пор мы видели, что словари (хеш-таблицы) чрезвычайно удобны в использовании, но есть одна загвоздка: HashTable (а также все другие классы словарей) используют для вычисления местоположения объекта некоторый алгоритм, который не представлен полностью самим классом HashTable. Он состоит из двух стадий, и код одной из них должен быть представлен классом объекта-ключа. Если вы используете класс, разработанный Microsoft, и который может быть использован в качестве ключа (например, String), то никаких проблем не возникает (Microsoft уже разработала весь необходимый код). Однако если вы сами разрабатываете класс ключа, то обязаны написать эту часть алгоритма самостоятельно.

На компьютерном жаргоне часть алгоритма, реализованная классом ключа, называется хешем (hash), отсюда термин хеш-таблица, и класс HashTable ищет этот алгоритм в совершенно определенном месте. Он обращается к методу GetHashCode( ) вашего класса, который унаследован от System.Object. Всякий раз, когда класс словаря собирается определить местоположение элемента, он вызывает метод GetHashCode( ) ключа. Вот почему, говоря о System.Object, мы подчеркивали, что если вы переопределяете GetHashCode( ), то должны это делать в соответствии с очень строгими требованиями, поскольку для того, чтобы классы словарей могли правильно работать, ваша реализация должна вести себя определенным образом.

GetHashCode( ) должен возвращать int, каким-то образом используя значение ключа для генерации этого int. HashTable принимает этот int и выполняет некоторую дополнительную обработку, включающую сложные математические вычисления, после чего возвращает индекс местоположения объекта в словаре. Мы не будем погружаться в эту часть алгоритма — она уже закодирована Microsoft, поэтому вам не обязательно знать о ней. О чем вам необходимо знать — так это о простых числах и причинах, по которым емкость хеш-таблицы должна быть задана простым числом.

Чтобы все работало правильно, при переопределении GetHashCode( ), как это уже упоминалось ранее, необходимо придерживаться строгих правил. Они могут показаться несколько абстрактными и устрашающими, но пусть это вас не слишком тревожит. Как демонстрирует пример MortimerPhomesExample, совершенно не трудно закодировать класс, отвечающий этим требованиям:

  •  он должен быть быстрым (поскольку нужно, чтобы вставка и извлечение элементов в словаре выполнялись быстро);
  •  он должен быть непротиворечивым; если два ключа представляют одно и то же значение, то они должны генерировать одно и то же значение хеша;
  •  в идеале он должен выдавать значения, равномерно распределяющие элементы в пределах диапазона значений int.

Причина существования последнего требования состоит в том, что существует потенциальная проблема; что будет, если в словаре окажется два элемента, для которых хеш выдаст одинаковый индекс?

Если такое случится, то класс словаря обязан будет найти ближайшее свободное место, чтобы сохранить второй элемент, а также ему придется позднее выполнять некоторый дополнительный поиск для извлечения такого элемента. Понятно, что это не может не отразиться на производительности, а также ясно, что если большое количество ключей будут иметь тенденцию выдавать одинаковые индексы при добавлении элементов, то вероятность такого рода конфликтов возрастет. Однако благодаря тому, что часть алгоритма, разработанная Microsoft, работает, этот риск сведен к минимуму, когда вычисляемые хеш-значения равномерно распределены между int.MinValue и int.MaxValue.

Риск конфликта между ключами также растет по мере наполнения словаря, поэтому обычно стоит обеспечить, чтобы емкость словаря была существенно больше, чем количество элементов, содержащихся в нем. По этой причине HashTable автоматически перераспределяет память для увеличения своей емкости задолго до того, как произойдет его наполнение. Отношение количества загруженных элементов к общей емкости словаря называют загрузкой (load), и вы можете указать в конструкторе максимальное значение этой величины, после достижения которого содержимое HashTable автоматически перераспределяется:

// емкость =50, максимальная загрузка =0.5

Hashtable employees = new Hashtable(50,0.5);

Чем меньше значение максимальной загрузки, тем более эффективно работает хеш-таблица и больше памяти она занимает. Кстати, когда хеш-таблица перемещается для увеличения емкости, то при этом всегда новое значение емкости выбирается из ряда простых чисел.

Другой важный момент из числа упомянутых ранее — это то, что алгоритм хеширования должен быть непротиворечивым. Если два объекта содержат одинаковые данные, то они должны генерировать одинаковые хеш-значения, и это является важным ограничением при переопределении методов Equals( ) и GetHashCode( ) класса System.Object. To есть для того, чтобы определить, что два ключа, А и В, равны, HashTable вызывает A.Equals(В). Это значит, что вы должны гарантировать, что следующее утверждение всегда будет истинно:

Если истинно A.Equals(В), значит, A.GetHashCode( ) и В.GetHashCode( ) должны возвращать одинаковые значения хеш-кода.

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

По этой причине компилятор С# всегда отображает предупреждения, если вы переопределяете Equals( ), по забываете переопределить GetHashCode( ).

Для System.Object приведенное утверждение верно, потому что Equals() просто сравнивает ссылки, a GetHashCode( ) возвращает значение, базирующееся на адресе объекта. Из этого следует, что хеш-таблицы на основе ключей, классы которых не переопределяют эти методы, будут работать корректно. Однако проблема такого подхода в том, что ключи трактуются как равные только в том случае, если они являются ссылками на один и тот же объект. Вы просто не сможете позднее создать экземпляр другого ключевого объекта с тем же значением, поскольку одинаковое значение ключа в этом случае означает один и тот же объект. То есть если не переопределить методы Equals( ) и GetHashCode( ), то ваш класс будет не слишком-то полезен для применения в хеш-таблицах. Поэтому имеет смысл так реализовать GetHashCode( ), чтобы он генерировал хеш-значение на базе значения объекта, а не его адреса в памяти. Вот почему вам неизбежно придется переопределять Equals( ) и GetHashCode( ) для всякого класса, который предполагается использовать в качестве ключа.

Кстати, класс System.String имеет соответствующие переопределения этих методов. Equals( ) переопределен для обеспечения сравнения значений строк, а GetHashCode( ) также соответствующим образом переопределен с тем, чтобы возвращать хеш-значение на базе содержимого строки. По этой причине удобно использовать строки в качестве ключей словаря.

Пример MortimerPhonesEmployees

Пример MortimerPhonesEmployees представляет собой программу, которая поддерживает словарь сотрудников компании. Как уже упоминалось ранее, словарь проиндексирован объектами EmployeeID, и каждый элемент, помещенный в словарь, является объектом EmployeeData, хранящим подробную информацию о сотруднике. Эта программа просто создает экземпляр словаря, вставляет в него группу записей о сотрудниках, а затем приглашает пользователя вводить идентификаторы (табельные номера) сотрудников. Каждый идентификатор, введенный пользователем, программа пытается использовать в качестве индекса и извлечь по нему подробную информацию о сотруднике. Этот процесс повторяется до тех пор, пока пользователь не введет X. Сеанс работы с этим примером выглядит следующим образом:

MortimerPhonesEmployees

Введите ID сотрудника (формат: А999, X — для  выхода)>  В001

Сотрудник: В001: Mortimer  $100,000.00

Введите ID сотрудника (формат:А999, X — для  выхода)> W234

Сотрудник: W234:Arabel Jones $10,000.00

Введите ID сотрудника (формат:А999, X — для  выхода)> X

Этот пример должен включать в себя несколько классов. В частности, нам понадобится класс EmployeeID, который будет ключом, идентифицирующим сотрудника, а также класс EmployeeData, хранящий данные о нем. Сначала мы рассмотрим класс EmployeeID, поскольку в нем нужно будет реализовать все необходимое, чтобы сделать его ключом словаря. Определение этого класса представлено ниже:

class  EmployeelD

{

 private readonly char prefix;

 private readonly int number;

 public EmployeelD(string id)

 {

    prefix =   (id.ToUpperO) [0] ;

    number = int.Parse(id.Substring(1,3));

 }

 public override string ToString()

{

 return prefix.ToString()+

 string.Format("{0,3:000}", number);

}

 public override int GetHashCode()

   {

return ToString().GetHashCode () ;

}

 public override bool Equals(object obj)

 {

EmployeelD rhs = obj as EmployeelD;

if(rhs==null)

 return  false;

if(prefix==rhs.prefix && number==rhs.number)

 return true;

    return false;

 }

}

Первая часть определения класса просто сохраняет текущий ID. Напомним, что ID имеет формат, подобный В001 или W234. Он состоит из одной буквы-префикса с последующими цифровыми символами. Мы будем хранить его в виде char для префикса и int — для остальной части.

Конструктор просто принимает строку и разбивает ее на два поля. Обратите внимание, что для простоты примера здесь не предусматривается никакой обработки ошибок. Просто предполагается, что строка параметра поступает в конструктор в правильном формате. Метод ToString( ) возвращает ID в форме строки:

return prefix.ToString() +

string.Format("{0,3:000)", number);

Обратите внимание на спецификатор формата (3:000), который гарантирует, что числовая часть идентификатора будет выдана с дополняющими нулями, то есть мы получим В001, а не В1.

После этого следуют два переопределенных метода, которые будут необходимы словарю, чтобы использовать данный класс в качестве ключа. Во-первых, Equals( ) переопределяется таким образом, чтобы сравнивать два значения экземпляров EmployeeID:

public override bool Equals(object  obj)

{

 EmployeelD rhs = obj as EmployeelD;

 if(rhs == null)

   return false;

 if(prefix == rhs.prefix && number == rhs.number)

   return true;

 return  false; }

Здесь мы впервые видим пример переопределения Equals( ). Отметим, что наша первая задача — проверить, действительно ли переданный параметр является объектом EmployeeID. Если это не так, то понятно, что метод не сможет сравнивать объекты, поэтому возвращается false. Проверка типа реализуется попыткой приведения переданного объекта к типу EmployeeID с помощью ключевого слова С# as. Как только установлено, что мы имеем дело с объектом EmployeeID, после этого просто сравниваются значения соответствующих полей.

Реализация GetHashCode( ) короче, хотя на первый взгляд и трудно понять, что именно в ней происходит:

public override int GetHashCode()

{

  string str = this.ToString();

  return str.GetHashCode();

 }

Мы уже говорили о строгих ограничениях, которым должен удовлетворять вычисляемый хеш-код. Конечно, существуют разные способы реализации простых и эффективных алгоритмов хеширования. В общем случае, если взять значения всех полей, умножить на некоторое большое простое число, а затем сложить вместе полученные результаты — выйдет хороший способ сделать это. Однако для удобства разработчиков Microsoft уже позаботилась о реализации сложного, но эффективного алгоритма хеширования для класса String, поэтому мы вполне можем этим воспользоваться. String.GetHashCode( ) генерирует равномерно распределенные числа на основе содержимого строк. Это отвечает всем требованиям, предъявляемым к хеш-кодам.

Единственный недостаток применения этого метода заключается в том, что при первом преобразовании класса EmployeeID в string теряется производительность. Если вы озабочены этим и не желаете мириться даже с малейшими затратами в алгоритме хеширования, то в таком случае вам придется спроектировать свой собственный хеш.

Проектирование алгоритмов хеширования — достаточно сложная тема, в которую мы не станем углубляться в рамках этой книги. Однако можем предложить один простой подход к проблеме, который состоит в умножении значений полей-компонентов класса на разные простые числа (по причинам математического характера умножение на разные простые числа позволяет избежать ситуации, когда разные комбинации значений полей дают один и тот же хеш-код). Следующий фрагмент кода демонстрирует подходящую реализацию GetHashCode( ):

public override int GetHashCode()

// альтернативная реализация 

{

 return (int)prefix*13 + (int)number*53;

}

Этот частный пример работает быстрее, чем алгоритм на базе
ToString( ), используемый в примере, но имеет тот недостаток, что значения хеш-кодов, сгенерированные на основе различных значений EmployeeID, вероятно, будут менее равномерно распределены по диапазону допустимых значений int. Кстати, примитивные числовые типы имеют определенные методы GetHashCode( ), но эти методы просто возвращают значение переменной, а потому не очень удобны. Примитивные типы вообще-то не предназначены для того, чтобы служить ключами.

Отметим, что реализации Equals( ) и GetHashCode( ) удовлетворяют требованию эквивалентности, упомянутому ранее. Приведенная перегрузка Equals( ) гарантирует, что два объекта EmployeeID рассматриваются как равные тогда и только тогда, когда у них одинаковые значения полей prefix и number. Однако в этом случае ToString( ) возвращает одинаковые значения для них обоих, а отсюда и одинаковые хеш-коды. Это — критичное требование, которое должно быть удовлетворено.

Теперь перейдем к классу, содержащему информацию о сотруднике. Его определение достаточно просто и интуитивно понятно:

class EmployeeData

{

 private string name;

 private decimal salary;

 private EmployeelD id;

 public EmployeeData(EmployeelD id, string name,

                     decimal salary)

 {

    this.id = id;

    this.name = name;  

    this.salary = salary;

 }

 public override string ToString()

 {

   StringBuilder sb = new StringBuilder(id.ToString(),

                                          100);

   sb.Append(": ");

   sb.Append(string.Format("{0,-20}", name));

   sb.Append(" ");

   sb.Append(string.Format("{0:C}",   salary));

   return sb.ToString();

 }

}

Здесь, опять-таки из соображений производительности, используется объект StringBuilder для генерации строкового представления объекта EmployeeData. И, наконец, нам понадобится тестовое окружение. Определим его в виде класса

TestHarness:

class TestHarness

{

 Hashtable employees = new Hashtable(3l);

 public void Run()

 {

   EmployeeID idMortimer = new EmployeelD("B001");

   EmployeeData mortimer =

   new EmployeeData(idMortimer,"Mortimer",100000.OOM);

   EmployeeID idArabel = new EmployeelD("W234");

   EmployeeData arabel= new

   EmployeeData(idArabel,"Arabel Jones",10000.OOM);

   employees.Add(idMortimer, mortimer);

   employees.Add(idArabel, arabel);

   while (true)

   {

     try 

     {

        Console.Write("Введите ID сотрудника”+

        “(формат:А999, Х для завершения}> ");

        string userlnput = Console.Headline();

        userlnput = userlnput.ToUpper();

       if (userlnput = "X")

           return;

        EmployeelD id = new EmployeelD(userlnput);

        DisplayData(id); }

     catch (Exception e)

     {

       Console.WriteLine("Возникло исключение.”+

      “Указали ли вы ID сотрудника в правильном формате?");

      Console.WriteLine(e.Message);

      Console.WriteLine();

     }

     Console.WriteLine();

   }

 }

 private void DisplayData(EmployeelD id)

 {

   object empobj = employees[id];

   if (empobj != null)

   {

      EmployeeData employee = (EmployeeData)empobj;

       Console.WriteLine("Сотрудник:"+

       employee.ToString()); }

   else

       Console.WriteLine("Сотрудник не найден: ID =  "

                          + id);

 }

}

Класс TestHarness содержит поле-член, которое и является словарем.

Как это обычно делается со словарями, мы указываем начальную емкость простым числом — в данном случае, 31. Собственно тестирование происходит в методе Run( ). Этот метод первым делом устанавливает подробную информацию о двух сотрудниках — mortimer и arabel — и добавляет ее в словарь:

employees.Add(idMortimer, mortimer);

employees.Add(idArabel, arabel);

Затем запускается цикл while, который многократно запрашивает у пользователя ввести идентификатор сотрудника — employeeID. Внутри цикла находится блок try, который нужен здесь, чтобы перехватывать все проблемы, связанные с неправильным форматом ввода идентификатора EmployeeID, что заставляет конструктор EmployeeID выбрасывать исключение, когда тот пытается сконструировать ID из строки:

string userlnput = Console.ReadLine();

userlnput = userlnput.ToUpper();

if(userlnput  ==  "X")

  return;

EmployeelD id = new EmployeelD(userlnput);

Если же EmployeeID сконструирован корректно, то вызовом метода DisplayData( ) отображается информация о сотруднике. Именно в этом методе осуществляется доступ к элементу словаря посредством синтаксиса массива. В самом деле, первое, что делает этот метод — это извлекает информацию о сотруднике по его идентификатору:

private void DisplayData(EmployeelD id)

{

  object empobj  = employees[id];

}

Если в словаре нет информации о сотруднике с таким ID, то employees [id] вернет null, поэтому объектная ссылка проверяется на равенство null и, в случае истинности, выдается сообщение об ошибке. В противном случае ссылка приводится к типу EmployeeData (напомним, что HashTable — это самый общий класс словаря; он сохраняет объекты, а потому извлеченные элементы следует приводить к типу ссылок на объекты класса, которые в действительности были сохранены в нем). Получив ссылку на EmployeeData, информацию о сотруднике можно просто отобразить на экране, используя метод EmployeeData.ToString( ):

EmployeeData employee = (EmployeeData)empobj;

Console.WriteLine("Сотрудник: "+employee.ToString());

Финальная часть кода — метод Main( ) — запускает весь пример на выполнение. Он просто создает экземпляр класса TestHarness и запускает его:

static void Main()

{

 TestHarness harness = new TestHarness();

 harness.Run ();

}

Резюме

В этой лекции был представлен обзор применения различного рода коллекций. Были рассмотрены списки-массивы, словари и коллекции. Если вы заглянете в документацию SDK, посвященную классам, реализующим интерфейсы ICollection и IEnumerable, то найдете там длинный список классов, имеющихся в ваше распоряжение. Во время реализации коллекций в своем коде следует принимать во внимание размер, тип и производительность, которые от них требуются, дабы сделать оптимальный выбор. .NET Framework предоставляет для этого широчайшие возможности.


 

А также другие работы, которые могут Вас заинтересовать

15890. Что такое мироощущение опыт осмысления 381.55 KB
  В.К. Шрейбер к. филос. н. доц. Челябинский государственный университет ЧТО ТАКОЕ МИРООЩУЩЕНИЕ: ОПЫТ ОСМЫСЛЕНИЯ Мироощущение относят к феноменам мировоззренческого круга. Но что подразумевается под мироощущением Ощущение о каких бы его типах не говорить...
15891. Механизмы интерпретации классического текста в «Чайке» Б. Акунина 44.5 KB
  Механизмы интерпретации классического текста в Чайке Б. Акунина Н. А. Кузьмина Омский государственный университет Интерпретация вторичный текст римейк прототекст Summary. The article deals with the specific genre of postmodernistic fiction – remake – and peculiarities of interpretation classic literary prototexts. Аксиомой...
15892. ПРИМЕРНЫЕ СХЕМЫ АНАЛИЗА ЛИТЕРАТУРНЫХ ПРОИЗВЕДЕНИЙ 83 KB
  ПРИМЕРНЫЕ СХЕМЫ АНАЛИЗА ЛИТЕРАТУРНЫХ ПРОИЗВЕДЕНИЙ Анализ художественного произведения Анализ прозаического литературного произведения Концептуальный уровень художественного произведения Уровень организации произведения как художественного целого Урове...
15893. «СВОЕ» И «ЧУЖОЕ» В ЭПИЧЕСКОМ ТЕКСТЕ К вопросу о «родовых» структурных признаках 86 KB
  Н.Д.ТАМАРЧЕНКО СВОЕ И ЧУЖОЕ В ЭПИЧЕСКОМ ТЕКСТЕ К вопросу о родовых структурных признаках Слово эпос в русской культурной традиции обозначает и литературный род и один из жанров относимых к этому роду – эпопею иногда в этом случае употребляется термин
15894. Структура художественного произведения и ее анализ 48.5 KB
  Структура художественного произведения и ее анализ Художественное произведение – сложноорганизованное целое. Необходимо познать его внутреннюю структуру то есть выделить отдельные его составляющие и осознать связи между ними. В современном литературоведении с
15895. Бахтин как парадигма мышления 71.5 KB
  В.И. Тюпа Бахтин как парадигма мышления To the 100 birth anniversary of Michael Bakhtin we publish Valery Tjupa's paper Bakhtin as a Paradigm of Mentality with the author`s attempt to reconstruct the axiomatics of Bakhtin discourses of scientific and philosophical nature. Three axiomatic complexes discovered by the author: personalism eventfulness responsibility can be subdivided into three more special axioms. Бахтин теперь моден....
15896. Эстетический анализ художественного текста (Часть первая: Сюжет Фаталиста М.Лермонтова) 73.5 KB
  В.И. Тюпа Эстетический анализ художественного текста Часть первая: Сюжет Фаталиста М.Лермонтова Конечная цель преподавания литературы в школе формирование культуры художественного восприятия. В этом собственно говоря и состоит общественное назначение литератур...
15897. Природа художественной целостности комедий А.П. Чехова Чайка и Вишнёвый сад 87 KB
  О.С. Рощина Природа художественной целостности комедий А.П. Чехова Чайка и Вишнёвый сад Olga Roschina in her work The Nature of Artistic Integrity of Chekhov`s Comedies Seagull and Cherry Orchard treats them from the point of aesthetic analises of the text. The ironical nature of Chekhov`s texts is proved which are often interpreted in a pseudoChekhov`s elegical way. В чеховедении доста
15898. ТЕОРЕТИЧЕСКАЯ ПОЭТИКА: понятия и определения 2.04 MB
  Цель предлагаемой хрестоматии — предоставить в распоряжение преподавателя и студента отобранные из различных, не связанных друг с другом источников и в то же время систематизированные определения основных понятий теоретической поэтики