Конспект диалога между школьником (И.Р.) и преподавателем (В.А.)
И.Р.: Хотелось бы понять разницу в использовании массивов, индексаторов и коллекций на примерах.
В.А.: Хорошо. Возьмите из Задач для самостоятельного решения. Массивы и строки задачу 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: Наш Чат, в котором вы можете обсудить любые вопросы, идеи, поделиться опытом или связаться с администраторами.