Массивы, индексаторы и коллекции. Пример обучения в диалоге

Конспект диалога между школьником (И.Р.) и преподавателем (В.А.)

И.Р.: Хотелось бы понять разницу в использовании массивов, индексаторов и коллекций на примерах.
В.А.: Хорошо. Возьмите из Задач для самостоятельного решения. Массивы и строки задачу 4: «Задан одномерный массив целых чисел. Образуйте из него два отсортированных по возрастанию массива, содержащих четные и нечетные числа. Подсказка: четное число делится на 2 без остатка» и решите ее 4 способами.
И.Р.: Какими именно?
В.А.: 1) только используя массивы ; 2) массивы и класс System.Array ; 3) индексаторы ; 4) необобщенную коллекцию ArrayList .

Обсуждение

И.Р.: Начну с консольного приложения. Пространство имен назову Task4Array, как и проект в целом. Все 4 способа реализую в одном приложении (для удобства сравнения). Тогда метод Main() будет состоять из 5 блоков: 1) генерация исходных данных, 2)-5) буду соответствовать 1-4 способам. Постараюсь использовать знания и примеры, полученные при изучении статей и примеров сайта.
В.А.: Для кого будет предназначено приложение? Какие видите требования к нему?
И.Р.: Для начинающих программистов вроде меня. Должны быть видны различия в работе с указанными типами данных. Буду писать необходимые комментарии.
В.А.: Будем по ходу дела обсуждать возможные варианты решения.

И.Р.: Нужно задать n — число элементов  и задать массив, например, с помощью известного мне генератора случайных чисел, например:

Console.Write("Введите число элементов массива: ");
int n = Console.ReadLine();
int[] M = new int[n];
Random r = new Random();
for (int k=0; k<n; k++)
   M[k] = r.Next(100);

В.А.: Что произойдет, если пользователь при вводе n укажет 0, 1000, 10.5,  qqq?

И.Р.: Случай, когда n<2, не интересен. При n>100 результат будет трудно проанализировать. Ошибки формата при вводе приведут к не обрабатываемым исключениям. Про это я уже читал на Вашем сайте статью про свойства , где используется класс RIN.  Воспользуюсь Вашим примером. Тогда:

Console.Write("Введите число элементов массива: ");
string number = Console.ReadLine();
// обработка ввода числа 
RIN k = new RIN(number, 2, 20, 10, "число");
// анализ ошибки ввода
if (!k.n_bool)
   Console.WriteLine(k.info);
// получили размерность массива
int n = k.N;

Здесь параметрами конструктора являются введенное число number (как string), минимальное число — 2, максимальное — 20, число элементов (в случае ошибки формата) — 10,  последний параметр — имя результата. У объекта k кроме свойства N имеются поля n_bool (индикатор ошибки при вводе n) и info (информация об ошибке). Добавив класс RIN в наш проект, мы сможем выполнить его тестирование.

В.А.: Может быть,  ввод и вывод массива оформить как методы?

И.Р.: Можно, тогда:

// Генератор множества Nq целых чисел в диапазоне от 0 до Nm-1 в массив M.
// Общие исходные данные для всех четырех способов
static void GenArray(int Nq, int Nm, int[] M)
{
   Random r = new Random();
   for (int k=0; k<Nq; k++)
      M[k] = r.Next(Nm);
}
// вывод всех элементов массива с наименованием (info)
static void printM(int[] M, string info)
{
   foreach (int p in M)
      Console.Write(p + " ");
   Console.WriteLine(" - " + info);
}

а вызов метода и его отображение на экране:

// Исходный массив для всех способов
int[] M = new int[n];
GenArray(n, 91, M); // целые числа в [0,90]
printM(M, "исходный массив");

И.Р.: Протестировал, все работает. Теперь в классе Program мы задали n и массив M[].

В.А.: Пишем код дальше для решения поставленной задачи?

И.Р.: Очевидно, нужно будет объявить два целочисленных массива: M2[] — для четных (even) и M1[] —  для нечетных (odd) чисел, каждый из n элементов, так как заранее не знаем, сколько будет четных и нечетных чисел.

int[] M1 = new int[n];
int[] M2 = new int[n];
int k1 = 0;
int k2 = 0;

В.А.: Зачем Вам понадобились переменные k1 и k2?

И.Р.: Мне нужно будет знать фактическое число элементов в двух новых массивах.

В.А.: Нельзя ли использовать свойство Length, имеющегося у каждого массива?

И.Р.: Дело в том, что раз я объявил длину массивов М1 и М2 равной n, то изменить свойство Length=n  я не смог (проверил на практике, оказывается, оно доступно только для чтения). Но в дальнейшем для сортировки массивов и вывода результатов понадобится фактическое количество элементов каждого массива, причем k1+k2=n. Эти длины массивов буду получать из метода деления исходного массива на два, назову его evenOdd(int[] M, int[] M1, int[] M2, ref int k1, ref int k2). Передаче по ссылке необходима для того, чтобы после выполнения метода сохранились эти параметры. Также буду использовать метод пузырьковой сортировки SortB(k, M),  рассмотренный в статье «Сортировка данных». Для вывода массивов придется выполнить перегрузку метода printM(int[] M, string info), добавив метод с тем же именем, но с большим числом параметров printM(int[] M, int n, string info). Тогда:

// Формирование двух массивов четных и нечетных чисел для первого способа
static void evenOdd(int[] M, int[] M1, int[] M2, ref int k1, ref int k2)
{
for (int k = 0; k < M.Length; k++)
{
   if (M[k] % 2 == 1)
   {
      M1[k1] = M[k];
      k1++;
   }
   else
   {
      M2[k2] = M[k];
      k2++;
   }
}
// перегруженный метод вывода заданного количества элементов массива
static void printM(int[] M, int n, string info)
{
   for (int j = 0; j < n; j++)
      Console.Write(M[j] + " ");
   Console.WriteLine(" - " + info);
}

Вызов методов для работы с массивами в методе Main( )  выглядит так:

// Первый способ - через Массивы 
Console.WriteLine("\nПрямая работа с массивами"); 
// массивы для нечетных и четных чисел
// так как заранее не знаем, сколько четных и нечетных
int[] M1 = new int[n];
int[] M2 = new int[n];
int k1 = 0;
int k2 = 0;
evenOdd(M, M1, M2, ref k1, ref k2); 
SortB(k1, M1);
printM(M1, k1, "нечетные числа");
SortB(k2, M2);
printM(M2, k2, "нечетные числа");

И.Р.: Протестировал, все работает. Перехожу к использованию класса System.Array.

Оказалось, что поставленная задача практически полностью решается путем использования статических методов класса Array:
1) Array.Copy(M, 0, M3, 0, n) — копирование массива M -> M3;
2) M4 = Array.FindAll<int>(M3, IsEven) — выборка из M3 элементов, для которых предикат IsEven = true,
где:

// предикат проверки четности
private static bool IsEven(int numbers)
{
   if (numbers % 2 == 0)
      return true;
   else
   return false;
}

или, если вместо предиката IsEven использовать лямбда выражение:
M5 = Array.FindAll<int>(M3, element => (element %2 == 1));
причем второй прием мне представляется более коротким и энергичным.
3) вывод массивов четных и нечетных чисел M4 и M5 удалось выполнить с помощью метода printM(M, info);

Вот как полностью выглядит этот блок:

// Второй способ - с использованием класса System.Array 
Console.WriteLine("\nИспользование класса System.Array");
int[] M3 = new int[n]; // для копии массива
// Копирование 
Array.Copy(M, 0, M3, 0, n);
// Создать массив элементов, удовлетворяющих условию четности
int[] M4 = new int[0];
// IsEven - предикат четности
M4 = Array.FindAll<int>(M3, IsEven);
Array.Sort(M4); // сортировка
printM(M4, "четные числа");  // вывод
// Создать массив элементов, удовлетворяющих условию нечетности
int[] M5 = new int[0];
// В качестве 2 параметра выбора - лямбда-выражение
M5 = Array.FindAll<int>(M3, element => (element % 2 == 1));
Array.Sort(M5);  // сортировка
printM(M5,"нечетные числа");  // вывод

Результаты тестирования 2 способа совпадают с результатами первого.

В.А.: Думаю, что получены интересные результаты:

1) Методами класса Array решены вопросы копирования, выборки и сортировки данных.

2) Освоены предикаты и лямбда выражения.

3) При выборке данных методом FindAll<int>( М3, …) свойству Length было присвоено правильное значение (отметим неудобства первого способа, когда приходится хранить фактическое количество элементов новых массивов k1  и k2). Это позволило использовать первый метод printM(M, info).

Теперь разбирайтесь с индексаторами.

И.Р.: Способ 3. Через индексаторы.  Воспользуюсь классом из Вашей статьи «Индексаторы«, переименовав класс в Mind.

// Класс с индексатором
class Mind
{
   int[] ar;
   public int Length;
   public Mind(int Size)
   {
      ar = new int[Size];
      Length = Size;
   }
   // Создание простейшего индексатора
   public int this[int index]
   {
      set
      {
         ar[index] = value;
      }
      get
      {
         return ar[index];
      }
   }
}

Добавлю в этот класс очевидные методы:
1) сортировка Sort3();
2) вывод содержимого индексатора print3(string info) — с одним параметром;
3) разделение на два индексатора: split3(Mind a1, Mind a2), аналог evenOdd(int[] M, int[] M1, int[] M2, ref int k1, ref int k2) в первом способе, однако с существенно с меньшим числом параметров — двумя.

// Пузырьковая сортировка элементов индексатора
public void Sort3()
{
   int p;
   for (int i = Length-1; i > 0; i--)
      for (int j = 0; j < i; j++)
         if (ar[j] > ar[j + 1])
         {
            p = ar[j];
            ar[j] = ar[j + 1];
            ar[j + 1] = p;
         }
}
// Вывод индексатора
public void print3(string info)
{
   for (int i = 0; i < Length; i++)
   Console.Write(ar[i] + " ");
   Console.WriteLine(info);
}
// Разделение на два индексатора
public void split3(Mind a1, Mind a2)
{
   int j1 = 0;
   int j2 = 0;
   for (int i = 0; i < Length; i++)
      if (ar[i] % 2 == 1)
      {
         a1[j1] = ar[i];
         j1++;
      }
      else
      {
         a2[j2] = ar[i];
         j2++;
      };
}

И.Р.: Перед разделением индексатора подсчитаем количество нечетных элементов в исходных данных:

// Найдем число нечетных элементов в индексаторе
int kn = 0;
for (int i = 0; i < n; i++)
if (MI[i] % 2 == 1)
kn++;

Тогда с учетом этого вычисления зададим точные длины массивов MI_1 и MI_2. Вызов методов, включая эти операторы выглядит так:

// Третий способ - через Индексаторы 
Console.WriteLine("\nИспользование индексатора с сортировкой из класса Mind");
Mind MI = new Mind(Size: n); // объявление индексатора
for (int i=0; i<n; i++) // пересылка
   MI[i] = M[i];
// Найдем число нечетных элементов в индексаторе
int kn = 0;
for (int i = 0; i < n; i++)
   if (MI[i] % 2 == 1)
      kn++;
// индексаторы с нечетными и четными числами - объявление
Mind MI_1 = new Mind(Size: kn);
Mind MI_2 = new Mind(Size: n - kn); 
// Разделение на два индексатора
MI.split3(MI_1, MI_2);
// Сортировки
MI_1.Sort3(); 
MI_2.Sort3();
// вывод
MI_1.print3(" нечетные элементы");
MI_2.print3(" четные элементы");

Протестировал, работает.

В.А.: Мои комментарии:
1) Важно, что удалось освоить первые навыки работы с индексаторами.
2) Альтернативный подсчет количества нечетных элементов позволил точно указать длины массивов (свойство Length) через конструктор:  Mind MI_1 = new Mind(Size: k); Очевидно, такой же прием мог быть использован и в первом способе.
3) В поддержку инкапсуляции, как одного из принципов ООП: Когда действия выполняются с полями класса, то список параметров методов минимален.
Осталось применить методы класса ArrayList ?

И.Р.: Воспользуюсь делегатами для последующего их определения их через лямбда оператора. Для этого в классе Program объявлю два делегата:

// делегаты для 4 способа через коллекцию ArrayList
delegate void DPrint(ArrayList a, string info);
delegate void D_1to2(ArrayList a, ArrayList b, ArrayList c);

Первый из них задает шаблон метода вывода коллекции (print),  второй — метод расщепления (split) коллекции на две.
Для добавления элементов в коллекции используем метод Add( ), для сортировки — метод Sort( ). В целом последний блок выглядит так:

// Четвертый способ через коллекцию в классе ArrayList О
Console.WriteLine("\nИспользование коллекции ArrayList");
ArrayList a = new ArrayList();
// print - экземпляр делегата Dprint
// Для вывода элементов коллекции используется делегат, определяемый через лямбда оператора
DPrint print = (b, info) =>
{
   foreach (object o in b)
      Console.Write(o + " ");
   Console.WriteLine(info);
};
// а - пустая коллекция
Console.WriteLine("Число элементов до: " + a.Count);
// заполнение исходной коллекции
for (int i = 0; i < n; i++)
   a.Add(M[i]);
// вывод на печать
print(a," исходная коллекция");
Console.WriteLine("Число элементов после: " + a.Count);
// d12 - экземпляр делегата D_1to2
// Для разделения исходной коллекции на две (четные и нечетные числа)
D_1to2 d12 = (b, b1, b2) => // лямбда оператора
{
   for (int i=0; i<a.Count; i++)
   if ((int)b[i]%2==0)
      b2.Add(a[i]);
   else 
      b1.Add(a[i]);
};
ArrayList a1 = new ArrayList();
ArrayList a2 = new ArrayList();
d12(a, a1, a2); // вызов метода через экземпляр делегата
a1.Sort();
a2.Sort();
print(a1, " коллекция нечетных чисел после сортировки");
print(a2, " коллекция четных чисел после сортировки");

Результат тестирования всех четырех способов решения задачи:

И.Р.: Результат получен для 18 целых чисел. Наглядно видно, что все 4 способа работают.

В.А.: И.Р., Спасибо за совместную работу. Надеюсь, что разница в применении массивов, индексаторов и коллекций стала понятной.

И.Р.: Приведу полный текст файла Program.cs:

/* 
Пример решения Задачи 4 из задач для самостоятельного решеия:
"Задан одномерный массив целых чисел. Образуйте из него два отсортированных 
по возрастанию массива, содержащих четные и нечетные числа". 
(C) Илья, 11 класс, 27.11.2018

Задание выполнено 4 способами:
1) прямое объявление массива http://c-sharp.pro/?p=508 
2) с использованием класса System.Array http://c-sharp.pro/?p=1055
3) с использованием индексаторов http://c-sharp.pro/?p=1086
4) с использованием обобщенной коллекции ArrayList http://c-sharp.pro/?p=1156

Использован класс Restricted Integer Number (RIN) - Ограниченное целое число
из статьи: Свойства. Пример "Ввод целого числа в заданном диапазоне" http://c-sharp.pro/?p=1126
а также Мind - класс, в котором задан индексатор http://c-sharp.pro/?p=1086
*/
using System;
using System.Collections; // необобщенные коллекции - добавить

namespace Task4Array
{
class Program
{
// Генератор множества Nq целых чисел в диапазоне от 0 до Nm-1 в массив M.
// Общие исходные данные для всех четырех способов
static void GenArray(int Nq, int Nm, int[] M)
{
Random r = new Random();
for (int k=0; k<Nq; k++)
M[k] = r.Next(Nm);
}

// Формирование двух массивов четных и нечетных чисел для первого способа
static void evenOdd(int[] M, int[] M1, int[] M2, ref int k1, ref int k2)
{
for (int k = 0; k < M.Length; k++)
{
if (M[k] % 2 == 1)
{
M1[k1] = M[k];
k1++;
}
else
{
M2[k2] = M[k];
k2++;
}
}
}
// Сортировка для первого способа работы с массивом для первого способа 
static void SortB(int k, int []M)
{
int p;
for (int i=k-1; i>0; i--)
for (int j=0; j<i; j++)
if (M[j] > M[j + 1])
{
p = M[j];
M[j] = M[j + 1];
M[j + 1] = p;
}
}
// вывод всех элементов массива с наименованием (info)
static void printM(int[] M, string info)
{
foreach (int p in M)
Console.Write(p + " ");
Console.WriteLine(" - " + info);
}
// перегруженный метод вывода заданного количества элементов массива
static void printM(int[] M, int n, string info)
{
for (int j = 0; j < n; j++)
Console.Write(M[j] + " ");
Console.WriteLine(" - " + info);
}
// предикат проверки четности, используется во втором способе
private static bool IsEven(int numbers)
{
if (numbers % 2 == 0)
return true;
else
return false;
}
// делегаты для 4 способа через коллекцию ArrayList
delegate void DPrint(ArrayList a, string info);
delegate void D_1to2(ArrayList a, ArrayList b, ArrayList c);

// Выполнение
static void Main(string[] args)
{
// Исходные данные 
Console.Write("Введите число элементов массива: ");
string number = Console.ReadLine();
// обработка ввода числа http://c-sharp.pro/?p=1126
RIN k = new RIN(number, 2, 20, 10, "число");
// анализ ошибки ввода
if (!k.n_bool)
Console.WriteLine(k.info);
// получили размерность массива
int n = k.N;
Console.WriteLine("Число элементов массива = {0}", n);
// Исходный массив для всех способов
int[] M = new int[n];
GenArray(n, 91, M); // целые числа в [0,90]
printM(M, "исходный массив");

// Первый способ - через Массивы http://c-sharp.pro/?p=508 
Console.WriteLine("\nПрямая работа с массивами"); 
// массивы для нечетных и четных чисел
// так как заранее не знаем, сколько четных и нечетных
int[] M1 = new int[n];
int[] M2 = new int[n];
int k1 = 0;
int k2 = 0;
evenOdd(M, M1, M2, ref k1, ref k2); 
SortB(k1, M1);
printM(M1, k1, "нечетные числа");
SortB(k2, M2);
printM(M2, k2, "нечетные числа");

// Второй способ - с использованием класса System.Array http://c-sharp.pro/?p=1055
Console.WriteLine("\nИспользование класса System.Array");
int[] M3 = new int[n]; // для копии массива
// Копирование с 0-го в нулевой, всего n элементов
Array.Copy(M, 0, M3, 0, n);
// Создать массив элементов, удовлетворяющих условию четности
int[] M4 = new int[0];
// IsEven - предикат, см.Делегаты и анонимные функции http://c-sharp.pro/?p=1156
M4 = Array.FindAll<int>(M3, IsEven);
Array.Sort(M4);
printM(M4, "четные числа");
// Создать массив элементов, удовлетворяющих условию нечетности
int[] M5 = new int[0];
// IsOdd - в качестве 2 параметра - лямбда-выражение, см.Делегаты и анонимные функции http://c-sharp.pro/?p=1156
M5 = Array.FindAll<int>(M3, element => (element %2 == 1));
Array.Sort(M5);
printM(M5,"нечетные числа");

// Третий способ - через Индексаторы http://c-sharp.pro/?p=1086
Console.WriteLine("\nИспользование индексатора с сортировкой из класса Mind");
Mind MI = new Mind(Size: n); // объявление
for (int i=0; i<n; i++) // пересылка
MI[i] = M[i];
// Найдем число нечетных элементов в индексаторе
int kn = 0;
for (int i = 0; i < n; i++)
if (MI[i] % 2 == 1)
kn++;
// индексаторы с нечетными и четными числами - объявление
Mind MI_1 = new Mind(Size: kn);
Mind MI_2 = new Mind(Size: n - kn); 
// Разделение на два индексатора
MI.split3(MI_1, MI_2);
// Сортировки
MI_1.Sort3(); 
MI_2.Sort3();
// вывод
MI_1.print3(" нечетные элементы");
MI_2.print3(" четные элементы");

// Четвертый способ через коллекцию в классе ArrayList http://c-sharp.pro/?p=1192
Console.WriteLine("\nИспользование коллекции ArrayList");
ArrayList a = new ArrayList();
// print - экземпляр делегата Dprint, см. http://c-sharp.pro/?p=1156
// Для вывода элементов коллекции используется делегат, определяемый через лямбда оператора
DPrint print = (b, info) =>
{
foreach (object o in b)
Console.Write(o + " ");
Console.WriteLine(info);
};
// а - пустая коллекция
Console.WriteLine("Число элементов до: " + a.Count);
// заполнение коллекции
for (int i = 0; i < n; i++)
a.Add(M[i]);
// вывод на печать
print(a," исходная коллекция");
Console.WriteLine("Число элементов после: " + a.Count);
// d12 - экземпляр делегата D_1to2
// Для разделения исходной коллекции на две (четные и нечетные числа)
D_1to2 d12 = (b, b1, b2) => // лямбда оператора
{
for (int i=0; i<a.Count; i++)
if ((int)b[i]%2==0)
b2.Add(a[i]);
else 
b1.Add(a[i]);

};
ArrayList a1 = new ArrayList();
ArrayList a2 = new ArrayList();
d12(a, a1, a2); // вызов метода через экземпляр делегата
a1.Sort();
a2.Sort();
print(a1, " коллекция нечетных чисел после сортировки");
print(a2, " коллекция четных чисел после сортировки");

Console.ReadKey();
} 
} // end of class Program

// Класс Restricted Integer Number (RIN) - Ограниченное целое число
// Из статьи: Свойства. Пример "Ввод целого числа в заданном диапазоне" http://c-sharp.pro/?p=1126
class RIN
{
int min = 0; // нижняя граница
int max = 100; // верхняя граница
public string info = "число"; // имя параметра
public bool n_bool; // успешность преобразования
// Ограниченное число: поле n и свойство N
int n;
public int N // свойство
{
get { return n; }
set
{
if (value < min)
{
n = min;
n_bool = false;
}
else if (value > max)
{
n = max;
n_bool = false;
}
else
{
n = value;
n_bool = true;
}
}
}
// конструктор
public RIN(string n_st, int n_min, int n_max, int n_def, string n_info)
{
min = n_min;
max = n_max;
info = n_info;
n_bool = true;
try
{
N = Convert.ToInt32(n_st);
if (!n_bool)
{
info = "Ошибка ввода параметра <<" + info + ">>. Число вне диапазона. Автоматически присваивается нижняя/верхняя граница. Для изменения введите целое число от " + min.ToString() + " до " + max.ToString();
n_bool = false;
}
}
catch
{
info = "Ошибка ввода параметра <<" + info + ">>. Введите целое число от " + min.ToString() + " до " + max.ToString() + ". По умолчанию параметр = " + n_def.ToString(); ;
n = n_def;
n_bool = false;
}
}
} // end class RIN

// Класс с индексатором
class Mind
{
int[] ar;
public int Length;
public Mind(int Size)
{
ar = new int[Size];
Length = Size;
}
// Создание простейшего индексатора
public int this[int index]
{
set
{
ar[index] = value;
}
get
{
return ar[index];
}
}
// Сортировка элементов индексатор
public void Sort3()
{
int p;
for (int i = Length-1; i > 0; i--)
for (int j = 0; j < i; j++)
if (ar[j] > ar[j + 1])
{
p = ar[j];
ar[j] = ar[j + 1];
ar[j + 1] = p;
}
}
// Вывод
public void print3(string info)
{
for (int i = 0; i < Length; i++)
Console.Write(ar[i] + " ");
Console.WriteLine(info);
}
// Разделение на два индексатора
public void split3(Mind a1, Mind a2)
{
int j1 = 0;
int j2 = 0;
for (int i = 0; i < Length; i++)
if (ar[i] % 2 == 1)
{
a1[j1] = ar[i];
j1++;
}
else
{
a2[j2] = ar[i];
j2++;
};
}
} // end of class MA (Индексатор)
}


NEW: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.


Помощь проекту:

Понравилась статья? Поделиться с друзьями:
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x
()
x