Как добавить элемент в список c
Перейти к содержимому

Как добавить элемент в список c

  • автор:

Коллекции

Хотя в языке C# есть массивы, которые хранят в себе наборы однотипных объектов, но работать с ними не всегда удобно. Например, массив хранит фиксированное количество объектов, однако что если мы заранее не знаем, сколько нам потребуется объектов. И в этом случае намного удобнее применять коллекции. Еще один плюс коллекций состоит в том, что некоторые из них реализует стандартные структуры данных, например, стек, очередь, словарь, которые могут пригодиться для решения различных специальных задач. Большая часть классов коллекций содержится в пространстве имен System.Collections.Generic .

Класс List<T> из пространства имен System.Collections.Generic представляет простейший список однотипных объектов. Класс List типизируется типом, объекты которого будут хранится в списке.

Мы можем создать пустой список:

В данном случае объект List типизируется типом string . А это значит, что хранить в этом списке мы можем только строки.

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

В данном случае в список помещаются три строки

Также можно при создании списка инициализировать его элементами из другой коллекции, например, другого списка:

Можно совместить оба способа:

В данном случае в списке employees будет четыре элемента ( < "Tom", "Bob", "Sam", "Mike" >) — три добавляются из списка people и один элемент задается при инициализации.

Подобным образом можно работать со списками других типов, например:

Установка начальной емкости списка

Еще один конструктор класса List принимает в качестве параметра начальную емкость списка:

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

Также начальную емкость можно установить с помощью свойства Capacity , которое имеется у класса List.

Обращение к элементам списка

Как и массивы, списки поддерживают индексы, с помощью которых можно обратиться к определенным элементам:

Длина списка

С помощью свойства Count можно получить длину списка:

Перебор списка

C# позволяет осуществить перебор списка с помощью стандартного цикла foreach :/p>

Также можно использовать другие типы циклов и в комбинации с индексами перебирать списки:

Методы списка

Среди его методов можно выделить следующие:

void Add(T item) : добавление нового элемента в список

void AddRange(IEnumerable<T> collection) : добавление в список коллекции или массива

int BinarySearch(T item) : бинарный поиск элемента в списке. Если элемент найден, то метод возвращает индекс этого элемента в коллекции. При этом список должен быть отсортирован.

void CopyTo(T[] array) : копирует список в массив array

void CopyTo(int index, T[] array, int arrayIndex, int count) : копирует из списка начиная с индекса index элементы, количество которых равно count, и вставляет их в массив array начиная с индекса arrayIndex

bool Contains(T item) : возвращает true , если элемент item есть в списке

void Clear() : удаляет из списка все элементы

bool Exists(Predicate<T> match) : возвращает true , если в списке есть элемент, который соответствует делегату match

T? Find(Predicate<T> match) : возвращает первый элемент, который соответствует делегату match. Если элемент не найден, возвращается null

T? FindLast(Predicate<T> match) : возвращает последний элемент, который соответствует делегату match. Если элемент не найден, возвращается null

List<T> FindAll(Predicate<T> match) : возвращает список элементов, которые соответствуют делегату match

int IndexOf(T item) : возвращает индекс первого вхождения элемента в списке

int LastIndexOf(T item) : возвращает индекс последнего вхождения элемента в списке

List<T> GetRange(int index, int count) : возвращает список элементов, количество которых равно count, начиная с индекса index.

void Insert(int index, T item) : вставляет элемент item в список по индексу index. Если такого индекса в списке нет, то генерируется исключение

void InsertRange(int index, collection) : вставляет коллекцию элементов collection в текущий список начиная с индекса index. Если такого индекса в списке нет, то генерируется исключение

bool Remove(T item) : удаляет элемент item из списка, и если удаление прошло успешно, то возвращает true. Если в списке несколько одинаковых элементов, то удаляется только первый из них

void RemoveAt(int index) : удаление элемента по указанному индексу index. Если такого индекса в списке нет, то генерируется исключение

void RemoveRange(int index, int count) : параметр index задает индекс, с которого надо удалить элементы, а параметр count задает количество удаляемых элементов.

int RemoveAll((Predicate<T> match)) : удаляет все элементы, которые соответствуют делегату match. Возвращает количество удаленных элементов

void Reverse() : изменяет порядок элементов

void Reverse(int index, int count) : изменяет порядок на обратный для элементов, количество которых равно count, начиная с индекса index

void Sort() : сортировка списка

void Sort(IComparer<T>? comparer) : сортировка списка с помощью объекта comparer, который передается в качестве параметра

Особенности реализации List в C#

List является одной из самых популярных коллекций в C#. Давайте разберёмся в некоторых особенностях работы с ним и посмотрим на внутреннюю реализацию его отдельных частей.

Введение

Данная статья будет посвящена полностью List<T> из пространства имён System.Collections.Generic, а если быть конкретнее, то его внутренней реализации и некоторым особенностям. Это самая часто используемая коллекция языка. И это не только моё мнение — так писали в своих книгах Эндрю Троелсен, Филипп Джепикс и Джон Скит. И это понятно – с List<T> легко работать. Он довольно гибкий и тем самым покрывает огромную часть повседневных задач программиста. С этим также помогает большое количество методов, идущих с ним в комплекте. А наличие LINQ ещё больше расширяет возможности данной коллекции.

Внутри List

Исходный код класса List<T> доступен на GitHub. Это значит, что мы можем взглянуть на его реализацию. Пройдёмся по важным аспектам.

Класс List<T> представляет последовательный список элементов с динамически изменяемым размером. Под капотом List<T> построен с использованием массива.

Класс List<T> содержит 3 основных поля:

T[] _items – внутренний массив, на основе которого строится список;

int _size – хранит информацию о количестве элементов в списке;

int _version – содержит версию коллекции.

Добавление элемента в список

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

В первую очередь значение поля _version увеличивается на 1 (смысл данного действия мы разберём чуть позже). После этого происходит создание двух локальных переменных – массива array с элементами типа T и size типа int. Им присваиваются соответствующие поля. Далее если в массиве ещё есть место для одного элемента, то происходит изменение элемента массива по индексу size + 1. Если же размер массива не позволяет добавить ещё один элемент, то вызывается метод AddWithResize.

Здесь вызывается метод Grow для увеличения текущего размера внутреннего массива. Далее производятся те же действия, что и в методе Add, для добавления при доступном месте.

Рассмотрим метод Grow подробнее:

Алгоритм работы метода Grow:

если внутренний массив пуст, то ёмкость списка будет равна 4, иначе удвоенной длине массива;

если новое значение ёмкости получается больше максимально возможной длины массива, то данная ёмкость станет равна Array.MaxLength;

если новое значение ёмкости коллекции получилось меньше текущего, то новая ёмкость станет равна текущей;

в конце newcapacity записывается в свойство Capacity.

Зачем нужно поле _version?

Но зачем же всё-таки нужно поле _version, значение которого менялось в методе Add? Как уже было написано ранее, это поле, которое позволяет отслеживать версию списка. Его значение проверяется при обходе списка. К примеру, рассмотрим метод ForEach:

Перед началом обхода значение поля _version сохраняется в переменную. Если во время обхода список будет изменён, то обход прекращается и выбрасывается исключение типа System.InvalidOperationException. Похожим образом _version отслеживается и в List<T>.Enumerator. Поэтому изменение списка при его обходе в foreach также приведёт к выбрасыванию исключения.

Capacity

У List<T> есть конструктор, который первым аргументом принимает число – начальную ёмкость.

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

Кстати, размером внутреннего массива можно управлять, ещё и используя свойство Capacity:

Рассмотрим код данного свойства:

Аксессор get возвращает значение _items.Length, то есть длину внутреннего массива.

Аксессор set действует по следующему алгоритму:

если value меньше количества элементов в коллекции, то будет выброшено исключение;

если value не равно длине внутреннего массива и value больше 0, то будет создан новый массив с ёмкостью, равной value;

если количество элементов в списке больше 0, то будет выполнено копирование элементов из старого массива в новый;

если value равно 0, то полю, которое представляет собой внутренний массив, будет присвоен пустой массив.

Прочие особенности методов List
Insert

Метод Insert позволяет вставить элемент в коллекцию только в рамках начала и конца этой коллекции. Если количество элементов в коллекции будет равно размерности внутреннего массива, то произойдёт увеличение ёмкости массива с помощью метода Grow(_size + 1). При попытке вставить элемент на индекс, который больше list.Count, будет выброшено исключение System.ArgumentOutOfRangeException.

Подобное поведение останется даже при явном управлении размером внутреннего массива.

В свойство Capacity присваивается 8, что приводит к изменению размера внутреннего массива. Однако это не даёт возможности вставить элемент на позицию, превышающую list.Count. Результатом выполнения приведённого кода будет выбрасывание исключения.

Clear

Данный метод производит очистку коллекции. В результате этой операции свойство Count будет иметь значение 0. Элементы коллекции ссылочного типа получают значение по умолчанию. Если элементы коллекции являются структурами и имеют поля ссылочного типа, то данные поля тоже получат значение по умолчанию. Стоит заметить, что размер внутреннего массива остаётся неизменным. Если до вызова Clear свойство Capacity было равно 8, то и после Clear размер массива останется равным 8. Для освобождения памяти, выделяемой под сам массив, необходимо после Clear вызвать метод TrimExcess.

TrimExcess

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

Sort и OrderBy

Между двумя этими методами есть несколько различий:

метод Sort принадлежит классу List<T>, а метод OrderBy является методом расширения из LINQ;

метод Sort модифицирует исходную коллекцию, а OrderBy возвращает отсортированную копию с типом IOrderedEnumerable<TSource>;

метод OrderBy производит устойчивую сортировку, а Sort – нет. Если вы используете метод Sort, то эквивалентные элементы могут быть переупорядочены.

Немного о производительности

List против ArrayList

List<T> является обобщённым, а это значит, что мы должны при создании списка указать, с объектами какого типа он работает.

Джеффри Рихтер в своей книге «CLR via C#» приводит следующие преимущества обобщений:

защита исходного кода;

более простой и понятный код;

В той же книге в начале 12-ой главы про обобщения имеется хороший пример сравнения List<T> и его необобщённого аналога ArrayList. Суть теста заключается в добавлении элемента в список и присваивании этого же элемента из списка в переменную 10 миллионов раз.

Пример кода для тестирования ArrayList со значимым типом:

Тестирование производилось с объектами значимых (Int32) и ссылочных (String) типов.

Переписав приведённый в книге код и протестировав его с помощью BenchmarkDotNet, я получил следующие результаты:

Из результатов видно, что c Int32 алгоритм List<T> работает гораздо быстрее, чем ArrayList. В целых 13 раз! Плюс с List<T> в 4 раза меньше выделяется память.

Из-за того что при работе ArrayList производится множество операций упаковки, увеличивается и число сборок мусора. При этом получение элемента требует выполнения распаковки. Всё это приводит к снижению производительности.

Разница при использовании ссылочных типов несущественная, так как нет операций упаковки и распаковки, которые являются очень тяжёлыми. Судя по коду, небольшая разница в скорости появляется из-за операции преобразования типов.

Преимущества задания Capacity

Как уже было сказано ранее, если разработчик заранее знает размер списка, то он может указать его.

Проведём небольшой тест.

В данном случае происходит добавление в list 150 000 элементов. Для наглядности проведём эту операцию 1000 раз. И сравним производительность с таким же методом, но с указанным capacity, который равен количеству операций добавления.

Из результатов видно, что затраченное время на выполнение метода без capacity в 2 раза больше, чем с заранее установленным. Также памяти выделяется почти в 4 раза больше. Подобные действия убирают 17 ненужных операций копирования на каждой итерации внешнего цикла.

Как быстрее всего определить, что в списке есть элементы?

Возьмём три варианта определения того, что список непустой:

использовать метод Count из LINQ и сравнить результат с 0;

использовать свойство Count и сравнить результат с 0;

использовать метод расширения Any из LINQ.

Проведя тестирование, получаем следующие результаты для списка из 1 500 000 элементов:

Самым быстрым оказался доступ к свойству Count, так как оно просто возвращает значение поля _size.

Метод Count пытается преобразовать исходную коллекцию к ICollection. При успешном преобразовании метод вернёт значение свойства Count. В случае неудачи потребуется обойти всю коллекцию для высчитывания количества элементов. К счастью, List<T> реализует данный интерфейс.

Метод Any при обнаружении хотя бы одного элемента в коллекции вернёт true.

Заключение

Можно сказать, что List<T> является более удобной для работы версией массива. Например, со списком удобнее работать, когда заранее неизвестно количество элементов последовательности.

C# содержит ещё множество коллекций, которые помогают в работе разработчикам. Какие-то из них более специфичные, а какие-то менее. Надеюсь, что данная статья поможет вам в работе и сделает ваше понимание списков чуть лучше :).

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Artem Rovenskii. List in C#: implementation and features.

C# — List<T>

The List<T> is a collection of strongly typed objects that can be accessed by index and having methods for sorting, searching, and modifying list. It is the generic version of the ArrayList that comes under System.Collections.Generic namespace.

List<T> Characteristics

  • List<T> equivalent of the ArrayList, which implements IList<T>.
  • It comes under System.Collections.Generic namespace.
  • List<T> can contain elements of the specified type. It provides compile-time type checking and doesn’t perform boxing-unboxing because it is generic.
  • Elements can be added using the Add() , AddRange() methods or collection-initializer syntax.
  • Elements can be accessed by passing an index e.g. myList[0] . Indexes start from zero.
  • List<T> performs faster and less error-prone than the ArrayList .

Creating a List

The List<T> is a generic collection, so you need to specify a type parameter for the type of data it can store. The following example shows how to create list and add elements.

In the above example, List<int> primeNumbers = new List<int>(); creates a list of int type. In the same way, cities and bigCities are string type list. You can then add elements in a list using the Add() method or the collection-initializer syntax.

You can also add elements of the custom classes using the collection-initializer syntax. The following adds objects of the Student class in the List<Student> .

Adding an Array in a List

Use the AddRange() method to add all the elements from an array or another collection to List.

AddRange() signature: void AddRange(IEnumerable<T> collection)

Accessing a List

A list can be accessed by an index, a for/foreach loop, and using LINQ queries. Indexes of a list start from zero. Pass an index in the square brackets to access individual list items, same as array. Use a foreach or for loop to iterate a List<T> collection.

Accessing a List using LINQ

The List<T> implements the IEnumerable interface. So, we can query a list using LINQ query syntax or method syntax, as shown below.

Insert Elements in List

Use the Insert() method inserts an element into the List<T> collection at the specified index.

Insert() signature: void Insert(int index, T item);

Remove Elements from List

Use the Remove() method to remove the first occurrence of the specified element in the List<T> collection. Use the RemoveAt() method to remove an element from the specified index. If no element at the specified index, then the ArgumentOutOfRangeException will be thrown.

Remove() signature: bool Remove(T item)

RemoveAt() signature: void RemoveAt(int index)

Check Elements in List

Use the Contains() method to determine whether an element is in the List<T> or not.

List<T> Class Hierarchy

The following diagram illustrates the List<T> hierarchy.

List<T> Class Properties and Methods

The following table lists the important properties and methods of List<T> class:

Списки

C# имеет ряд классов для работы со списками. Они реализуют Интерфейс IList и наиболее популярной реализацией является общий список, часто называемый List&ltT>. T указывает Тип объектов, содержащихся в списке, который имеет дополнительное преимущество, что компилятор будет проверять и убедится, что вы добавите только объекты нужного типа в список — другими словами, List<T> является типобезопасным.

List очень похож на класс ArrayList, который был вариантом go-to List до C# поддерживаемых универсальных списков. Поэтому вы также увидите, что List может делать много того же, что и массив (который, кстати, также реализует Интерфейс IList), но во многих ситуациях с List проще и легче работать. Например, вам не нужно создавать список определенного размера — вместо этого вы можете просто создать его, и .NET автоматически развернет его в соответствии с количеством элементов по мере их добавления.

Как уже упоминалось, T обозначает тип и используется для указания типа объектов, которые должны содержаться в списке. В нашем первом примере, я покажу вам, как создать список, который должен содержать строки:

Это создает пустой список, но, легко добавляя в него впоследствии с помощью метода Add:

Однако, если вы попробуете добавить что-нибудь, что не является символьной строкой, компилятор немедленно пожалуется на это:

Инициализация списка с элементами

В приведенном выше примере мы просто создали список, а затем добавили в него элемент. Однако C# фактически позволяет создавать список и добавлять в него элементы в пределах одной инструкции, используя метод, называемый инициализаторами коллекций. Давайте посмотрим, как это делается:

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

Работа с элементами

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

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

Как только список готов, я использую цикл for для его выполнения — чтобы узнать, сколько итераций мы собираемся сделать, я использую свойство Count списка. На каждой итерации я обращаюсь к рассматриваемому пользователю через индексатор списка, используя синтаксис квадратных скобок (например, listOfUsers[i]). Так как у меня есть пользователь, я вывожу имя и возраст.

Добавление, вставка и удаление элементов

Мы уже пытались добавить один элемент в список, но есть больше возможностей для этого. Прежде всего, вы можете вставить элемент вместо добавления — разница в том, что в то время как метод Add всегда добавляет в конец списка, метод Insert позволяет вставить элемент в определенной позиции. Вот пример:

Сначала мы создаём один элемент в списке, потом добавляем еще два — один вверху списка, второй посередине. Первый параметр метода Insert означает индекс, который определяет порядок элемента. Второй же имеет само значение элемента. Осторожно — будет выдано исключение, если вставить элемент с индексом 3, если в списке меньше элементов.

Добавление нескольких элементов

Так же как у нас есть методы Add и Insert для добавления и вставки одного элемента, также есть соответствующие методы для добавления и вставки нескольких элементов. Они называются AddRange() и InsertRange() и принимают любой тип из коллекции, который как параметр реализует интерфейс IEnumerable — это, например, может быть массив элементов или список, элементы которого вы хотите добавить или вставить в текущий список.

Для примера метода Range давайте сделаем что-нибудь забавное — мы объединим метод AddRange с инициализатором коллекции, чтобы добавить несколько новых имен в существующий список в одном операторе:

Мы просто создали массив строк на-лету и немедленно добавили эти элементы в наш список имен из предыдущего примера.

Удаление элементов

Cуществуют три способа удаления одного или нескольких элементов из списка: Remove(), RemoveAt() and RemoveAll().

Метод Remove() принимает лишь один параметр: элемент, который нужно удалить. Это прекрасно подходит, например, для так списка строк или чисел, потому что вы можете просто вписать элемент, который хотите удалить. С другой стороны, если у вас есть список сложных объектов, вам придется сначала найти этот объект, чтобы была ссылка, которую можно передать в метод Remove (). «Давайте разберемся с этим позже» — вот очень простой пример того, как вы можете удалить один элемент с помощью метода Remove:

Метод Remove() просто перебирает список, пока не находит первое вхождение объекта вы указали для удаления и удаляет его — он удаляет только один экземпляр, и если указанный вами пункт в списке, не существует, ошибка не выдается. Метод возвращает true, если удалось удалить элемент, и false, если это не так.

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

Или последний элемент списка как здесь:

Опять же, это удаляет только один элемент, и на этот раз вы должны быть осторожны при предоставлении индекса удаляемого элемента — если вы используете индекс, который выходит за пределы (ниже 0 или выше количества элементов), будет выдано исключение! Таким образом, если Вы не уверены в том, что вы делаете, вы можете обернуть метод RemoveAt() в блок try-catch для обработки исключения (подробно описано в других разделах этого руководства). Метод RemoveAt() ничего не возвращает, поэтому вам придется проверить количество элементов в списке до и после вызова, чтобы решить, был ли он успешным — с другой стороны, если вы знаете, что у вас есть индекс, который существует в списке, о котором вы всегда должны убедиться, то вы всегда можете ожидать, что RemoveAt () будет успешным.

Метод removeall() — это наиболее сложный из удаляющих-методов, но наверняка также самый мощный. Он принимает делегат метода в качестве параметра, и этот метод затем решает, следует ли удалить элемент или нет, возвращая true или false. Он позволяет применять собственную логику при удалении элементов, а также позволяет удалять несколько элементов одновременно. Делегаты будут рассмотрены в другом месте этого учебника, поскольку это большая и сложная тема, но я все равно хочу, чтобы вы почувствовали, насколько крут метод RemoveAll, поэтому вот пример:

В этом примере в качестве параметра метода RemoveAll используется анонимный метод (опять же слишком сложный для объяснения, но он будет рассматриваться в другой главе). Наш анонимный метод довольно прост — он будет вызываться для каждого элемента в списке и иметь параметр с именем name, который, конечно же, является текущим элементом в итерации. Он смотрит на это имя, и если оно начинается с «J», возвращаетсяtrue — в противном случаеfalse . Метод RemoveAll () использует этот ответ (true или false), чтобы решить, следует ли удалять каждый элемент. В конце концов, это оставляет наш первоначальный список только с одним членом Doe: Another Doe.

Сортировка элементов

До сих пор элементы списка, с которыми мы работали, использовались только в том порядке, в котором они были добавлены в список. Тем не менее, вы можете отсортировать элементы определенным образом, например, по алфавиту в случае нашего списка имен. List<T> имеет метод сортировки (), который мы можем использовать для этого:

Как вы увидите из выходных данных, элементы в списке теперь отсортированы по алфавиту, и если вы хотите, чтобы он был в порядке убывания (от Z до A), просто вызовите метод Reverse () после выполнения сортировки:

Итак, сортировать список было довольно легко, верно? Ну, это было в основном так просто, потому что у нас есть список строк, и .NET framework точно знает, как сравнивать две строки. Если у вас есть список чисел, .NET, конечно же, знает, как это сортировать. С другой стороны, у вас может быть список пользовательских объектов (поскольку список<T> может содержать любой объект), который .NET не имеет возможности сравнить. Есть несколько решений этой проблемы, например, реализация интерфейса IComparable или использование LINQ (мы рассмотрим оба позже в этом уроке), но в качестве быстрого исправления мы также можем просто предоставить метод для вызова метода Sort (), чтобы узнать, как два элемента складываются друг против друга, как здесь:

Это добавило довольно много кода в наш пример, но на самом деле это не слишком сложно. Если мы начнем снизу, я создал очень простой класс пользователя, состоящее из имени и возраста. В середине я объявил метод с именем CompareUsers () — он принимает двух пользователей в качестве параметров, а затем возвращает целое число, которое будет указывать, является ли один элемент «меньшим», «тем же» или «большим» (-1, 0 или 1). Эти значения будут использоваться методом Sort() для перемещения элементов таким образом, чтобы порядок элементов соответствовал тому, что мы хотим. В этом случае я просто использую свойство Age для сравнения, оставляя нам список пользователей, отсортированных по их возрасту.

Резюме

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *