Коллекции

List

Последнее обновление: 07.04.2023

Определение списков

Список или тип List<T> представляет набор значений. Для определения списка применяются квадратные скобки. Например, определение пустого списка:

var list = [];

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

var list = ["Tom", "Bob", "Sam"];

В данном случае в списке три элемента - три строки. При определении списка можно явно указать тип:

List<String> list = ["Tom", "Bob", "Sam"];

После названия типа - List в угловых скобкахт указывается тип элементов, которые будут содержаться в списке. В примере выше это тип String, а список содержит данные этого типа.

Функция print позволяет вывести содержимое списка на консоль:

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
	print(list);    // ["Tom", "Bob", "Sam"]
} 

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

Для обращения к элементам массива применяются индексы. Индекс представляет номер элемента в массиве, при этом нумерация начинается с нуля, поэтому индекс первого элемента будет равен 0. А чтобы обратиться к третьему элементу в массиве, нам надо использовать индекс 2, к примеру: list[2]. Используем индексы для получения и установки значений элементов массива:

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
    var first = list[0];    // получаем первый элемент
    print(first);           //Tom
    list[0] = "Alice";      // изменяем первый элемент
	print(list);            // ["Alice", "Bob", "Sam"]
} 

Если мы попытаемся обратиться по несуществующему индексу, то мы столкнемся с ошибкой:

var list = ["Tom", "Bob", "Sam"];
list[9] = "Alice";      // ! Ошибка - элемента с индексом 9 не существует

В данном случае в списке только 3 элемента, поэтому элемента с индексом 9 в списке не существует.

Свойства списков

Основные свойства списков:

  • first: возвращает первый элемент

  • last: возвращает последний элемент

  • length: возвращает длину списка

  • reversed: возвращает список, в котором все элементы расположены в противоположном порядке

  • isEmpty: возвращает true, если список пуст

  • isNotEmpty: возвращает true, если список имеет как минимум один элемент

Применение свойств:

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
	print(list.first);          // Tom
	print(list.last);           // Sam
	print(list.isEmpty);        // false
	print(list.isNotEmpty);     // true
	print(list.length);         // 3

    var reversed = list.reversed;   // получаем список в обратном порядке
    print(reversed);            // (Sam, Bob, Tom)
} 

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

Для управления списком Dart предоставляет богатую палитру методов. Отмечу лишь основные:

  • add(E value): добавляет элемент в конец списка

  • addAll(Iterable<E> iterable): добавляет в конец списка другой список

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

  • indexOf(E element): возвращает первый индекс элемента

  • insert(int index, E element): вставляет элемент на определенную позицию

  • remove(Object value): удаляет объект из списка (удаляется только первое вхождение элемена в список)

  • removeAt(int index): удаляет элементы по индексу

  • removeLast(): удаляет последний элемент списка

  • removeRange(int start, int end): удаляет диапазон элементов между индексами start и end

  • removeWhere(bool test(E element)): удаляет элементы, которые удовлетворяют условию, передаваемому в виде функции test

  • forEach(void f(E element)): производит над элементами списка некоторое действие, которое задается функцией-параметром, аналоги цикла for..in

  • sort(): сортирует список

  • sublist(int start, [ int end ]): возвращает часть списка от индекса start до индекса end

  • contains(Object element): возвращает true, если элемент содержится в списке

  • join([String separator = "" ]): объединяет все элементы списка в строку. Можно указать необязательный параметр separator, который будет раздлять элементы в строке

  • skip(int count): возвращает коллекцию, в которой отсутствуют первые count элементов

  • take(int count): возвращает коллекцию, которая содержит первые count элементов

  • where(bool test(E element)): возвращает коллекцию, элементы которой соответствуют некоторому условию, которое передается в виде функции

Это только краткий перечень основных функциональных возможностей класса List. Полный список полей/методов/конструкторов можно посмотреть в документации

h3>Перебор списков

Для перебора списка мы можем использовать встроенные циклы for/while:

void main (){
	
	var list = ["Tom", "Bob", "Sam"];
	for(int i=0; i < list.length; i++){
		print(list[i]);
	}
}

В данном случае переменная-счетчик i выполняет роль индекса. Увеличение индекса будет происходить, пока он не достигнет значения list.length, то есть длины списка.

Но также мы можем использовать специальную форму цикла для коллекций:

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
	for (var name in list){
		print(name);
	}
} 

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

Еще одну альтернативу представляет метод forEach():

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
    list.forEach(print);
} 

В метод forEach передается функция, которая вызывается для каждого элемента списка. В примере выше передаем функцию print. То есть при выполнении метда для каждого элемента вызывается функция print, которая выводит значения элемента списка на консоль. Таким образом можно передать любую функцию, которая принимает один параметр. Например, передадим анонимную функцию:

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
    list.forEach((name)=>print("Name: $name"));
} 

Консольный вывод:

Name: Tom
Name: Bob
Name: Sam

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

Списки позволяют динамически добавлять элементы. Для добавления одного элемента в конец списка применяется метод add(), в который передается добавляемый объект:

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
    list.add("Alice");
    print(list);   //  [Tom, Bob, Sam, Alice]
} 

Для добавления другого списка применяется метод addAll():

void main (){
	
    var people = ["Alice", "Kate"];
    var list = ["Tom", "Bob", "Sam"];
    list.addAll(people);
    print(list);   // [Tom, Bob, Sam, Alice, Kate]
} 

Для добавления объекта по какому-то определенному индексу применяется метод insert():

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
    list.insert(1, "Alice");    // вставляем "Alice" по индексу 1
    print(list);   // [Tom, Alice, Bob, Sam]
} 

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

Для удаления одного элемента применяется метод remove(), в который передается удаляемый элемент:

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
    list.remove("Bob");
    print(list);   // [Tom, Sam]
} 

Метод removeAt() удаляет по индексу, который передается в метод:

void main (){
	
    var list = ["Tom", "Bob", "Sam"];
    list.removeAt(2);
    print(list);   // [Tom, Bob]
} 

Если переданный индекс будет недействительным, то генерируется ошибка.

Для удаления по условию применяется метод removeWhere(), который передается функция-условие. Эта функция принимает элемент списка и должна возвращать значение типа bool - true, если элемент списка соответствует условию, и false, если не соответствует. Например, удалим из списка все строки, длина которых равна 3 символам:

void main (){
	
    var list = ["Tom", "Alice", "Bob", "Sam", "Kate"];
    list.removeWhere((item)=>item.length ==3);  // если длина строки - 3 символа
    print(list);   // [Alice, Kate]
} 

Метод removeRange() удаляет все элементы между двумя индексами:

void main (){
	
    var list = ["Tom", "Alice", "Bob", "Sam", "Kate"];
    list.removeRange(1, 3); // удаляем с 1 по 3 индексы
    print(list);   // [Tom, Sam, Kate]
} 

Объединение элементов списка

Метод join() объединяет все элементы списка в строку. В качестве параметра метод принимает разделитель между элементами списка:

void main (){
	
    var list = ["Tom", "Alice", "Bob", "Sam", "Kate"];
    String items = list.join(", ");
    print(items);   // Tom, Alice, Bob, Sam, Kate
} 

Здесь в качестве разделителя выступает запятая ", ". В итоге из списка ["Tom", "Alice", "Bob", "Sam", "Kate"] будет сформирована строка "Tom, Alice, Bob, Sam, Kate".

Сортировка

Для сортировки применяется метод sort():

void main (){
	
    var list = ["Tom", "Alice", "Bob", "Sam", "Kate"];
    list.sort();
    print(list);   // [Alice, Bob, Kate, Sam, Tom]
} 

По умолчанию элементы сортируются по возрастанию (строки - в алфавитном порядке). Но мы можем переопределить порядок сортировки. Для этого метод sort принимает функцию сравнения двух объектов:

int compare(E a, E b)

Если первый объект - a больше второго объекта - b, то возвращается число больше 0, если меньше - то возвращается число меньше 0. Если оба объекта равны, то возвращается 0.

Например, мы хотим чтобы строки сортировались не в алфавитном порядке, а в зависимости от их длины. То есть сначала строки с меньшей длиной, а потом с большей:

void main (){
	
    var list = ["Tom", "Alice", "Bob", "Sam", "Kate"];
    list.sort((item1, item2)=> item1.length - item2.length);
    print(list);   // [Tom, Bob, Sam, Kate, Alice]
} 

Здесь функция сравнения представляет анонимную функцию:

(item1, item2)=> item1.length - item2.length

Если длина первой строки item1 больше второй item2, то возвращается число больше 0. Что означает, что item1 больше, чем item2.

Неизменяемые списки

Если список определен как константа с помощью const, то его нельзя изменить:

void main (){
	
    const list = ["Tom", "Bob", "Sam"];
    // list = ["Alice", "Kate"];   // так нельзя
    // list.add("Alex");           // так нельзя
    // list[0] = "Tomas";          // так нельзя
    print(list);   // [Tom, Bob, Sam]
} 

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

Но если список определен как константа с помощью final, то у нас есть больше свободы для работы со списком:

void main (){
	
    final list = ["Tom", "Bob", "Sam"];
    // list = ["Alice", "Kate"];   // так нельзя
    list.add("Alex");           // так можно
    list[0] = "Tomas";          // так можно
    print(list);   // [Tomas, Bob, Sam, Alex]
} 

Мы не можем переустановить полностью значения final-списков, однако мы можем добавить в него, удалить или изменить его элементы.

Еще один способ создания неизменяемого списка представляет применение именнованного конструктора List.unmodifiable() - в него передается список, который не должен изменяться:

void main (){
	
    // создаем неизменяемый список
    final list = List.unmodifiable(["Tom", "Bob", "Sam"]);
    // list = ["Alice", "Kate"];   // так нельзя
    // list.add("Alex");           // так нельзя
    // list[0] = "Tomas";          // так нельзя
    print(list);   // [Tom, Bob, Sam]
} 

Стоит отметить, что в этом случае мы опять же не сможем добавлять, удалять и изменять элементы списка (как в случае с const-списками), однако в отличие от const-списков, если мы попробуем изменить элементы, то получим ошибку во время выполнения программы.

Фиксированные и нефиксированные списки

Списки могут быть фиксированными (с жестко определенным размером) и нефиксированные (могут увеличиваться в размерах). Неефиксированные списки по сути были рассмотрены ранее - мы можем динамически изменять их размер, например, с помощью метода add() добавить новые элементы.

А размер фиксированных список изменить нельзя. Для создания фиксированного списка можно использовать один из именнованных конструкторов класса List. Например, конструктор List.filled(количество_элементов, значение_по_умолчанию) принимает два параметра. Первый параметр - количество элементов в фиксированном списке. Второй параметр указывает на значение, которое будут иметь все элементы при создании списка:

void main (){

    // создаем фиксированный список
    var list = List.filled(3, "Tom");
    //list.add("Alice"); // нельзя добавить новый элемент
    print(list);   // [Tom, Tom, Tom]
} 

В данном случае создаем фиксированный список из трех элементов, каждый из которых представляет строку "Tom".

Хотя список и фиксированный, тем не менее мы можем менять значения отдельных элементов.

void main (){

    // создаем фиксированный список
    var list = List.filled(3, "Tom");
    list[1] = "Bob";
    list[2] = "Sam";
    print(list);   // [Tom, Bob, Sam]
} 

Еще один способ создания фиксированного списка представляет конструктор List.generate():

List.generate(количество_элементов, функция_наполнения, можно_ли_добавлять)

Первый параметр указывает на количество элементов. Второй параметр представляет функцию заполнения списка, в которую в качестве параметра передается индекс элемента. Третий параметр - булевое значение - если true, то список НЕфиксированный (расширяемый), если false, то список фиксированный.

Применение конструктора List.generate:

void main (){
    var list = List.generate(4, (int index) => index + 1, growable: false); // фиксированный список [1, 2, 3, 4]

    list[0] = 2o; 
    //list.add(1); // нельзя добавить новый элемент
     
    print(list);   // [20, 2, 3, 4]  
}

spread-оператор ...

spread-оператор ... позволяет разложить список на отдельные элементы. Это может быть удобно, если мы хотим наполнить один список элементами из другого списка. Например:

void main (){
    var employees = ["Tom", "Alice", "Kate"];
    var people = ["Bob", ...employees, "Sam"];

    print(people);  // [Bob, Tom, Alice, Kate, Sam]
}

После оператора ... указывается список, который мы хотим разложить на элемента. Например, ...employees - раскладываем на элементы список employees. А в записи:

var people = ["Bob", ...employees, "Sam"];

все элементы списка employees помещаются между элементами "Bob" и "Sam". Соответственно мы получим список [Bob, Tom, Alice, Kate, Sam]

Для работы со списками, которые могут представлять значение null, есть специальная модификация данного оператора - null spread-оператор или ...?. После этого оператора указывается список, который может быть равен null. Если список не равен null, то он также раскладывается на элементы. Если он равен null, то ничего не происходит:

List<String> createList(String first, List<String>? otherList)
{
    return [first, ...?otherList];
}
void main (){
    List<String>? employees = ["Alice", "Kate"];
    var people = createList("Tom", employees);

    print(people);  // [Tom, Alice, Kate]
}

Здесь функция createList принимает первый элемент создаваемого списка и другой список, который может быть равен null. И чтобы элементы списка-параметра были успешно переданы в создаваемый список, применяется оператор ...?

Collection if

При установке элементов списка можно применять условия с помощью оператора if:

List<String> createProgram(bool web)
{
    return ["Dart", "Flutter", if(web) "JavaScript"];
}
void main (){
    var withWeb = createProgram(true);
    var withoutWeb = createProgram(false);

    print(withWeb);     // [Dart, Flutter, JavaScript]
    print(withoutWeb);  // [Dart, Flutter]
}

Здесь функция createProgram принимает булевый параметр и, если он равен true, добавляет в создаваемый список строку "JavaScript". Если параметр web равен false, то эта строка не добавляется.

Collection for

С помощью цикла for-in при установки элементов списка мы можем пройтись по другому списку и передать его элементы в текущий:

void main (){
    var employees = ["Tom", "Alice"];
    var people = ["Bob", "Kate", 
                    for(var emp in employees) "Employee " + emp
                ];
    print(people);  // [Bob, Kate, Employee Tom, Employee Alice]
}

Здесь в список people добавляются элементы списка employees. Для этого применяется цикл for-in, в котором проходим по всем строкам из списка employee и прибавляем к ним строку "Employee ".

for(var emp in employees) "Employee " + emp

Таким образом, финальный список будет преставлять [Bob, Kate, Employee Tom, Employee Alice]

Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850