Список List

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

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

Для определения списка применяются квадратные скобки []:

let people = []

Здеь определен пустой список, который называется people.

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

let people = ["Tom"; "Sam"; "Bob"]

В данном случае список people состоит из пяти элементов типа string. Важно, что все элементы представляют один и тот же тип.

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

let people = [
    "Tom"
    "Sam"
    "Bob"
]

Можно определить элементы списка с помощью оператора списка ..:

let numbers = [ 1..5 ]  // [1; 2; 3; 4; 5]

Можно сгенерировать элементы с помощью цикла:

let squares = [ for i in 1..5 -> i * i ]  // [1; 4; 9; 16; 25]

Создание списка из других списков

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

let people = ["Tom"; "Sam"; "Bob"]
let employees = "Alice" :: people   // ["Alice"; "Tom"; "Sam"; "Bob"]

Здесь список employees создается путем объединения строки "Alice" и списка people. Причем значение указывается слева от оператора ::, а список - слева.

А для объединения двух списков применяется оператор @:

let men = ["Tom"; "Sam"]
let women = ["Alice"; "Kate"]
let people = men @ women    // ["Tom"; "Sam"; "Alice"; "Kate"]

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

Для перебора элементов списка можно использовать цикл for..in:

let people = ["Tom"; "Sam"; "Bob"]

for person in people do
    printfn "%s" person 

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

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

let people = ["Tom"; "Bob"; "Alice"; "Mike"; "Sam"]

List.iter (fun p -> printf "%s " p) people

Здесь функция fun p -> printf "%s " p получает каждый элемент списка people через параметр и выводит его на консоль

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

Каждый элемент в списке имеет индекс - порядковый номер, который отсчитывается с нуля (то есть первый элемент имеет индекс 0, второй элемент- индекс 1 и так далее). Для обращения к элементу по индексу применяются квадратные скобки, в которые передается индекс элемента:

let people = ["Tom"; "Sam"; "Bob"]

printfn "%s" people[1]  // получаем элемент с индексом 1 - "Sam"

Разложение списка

F# позволяет разложить список на отдельные значения с помощью привязки let:

let people = ["Tom"; "Bob"; "Sam"]
let [a; b; c] = people 
printfn "%s" b      // Bob

В данном случае список people раскладывается на 3 переменных a, b и c, каждая из которых будет представлять отдельный элемент списка согласно их позиции.

Если какие-то элементы не важны, то для них можно использовать символ подстановки _:

let people = ["Tom"; "Bob"; "Sam"]
let [_; b;_] = people 
printfn "%s" b      // Bob

Свойства

Списки представляют класс List, который определяет ряд свойств:

  • Head: первый элемент

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

  • Item: элемент по указанному индексу.

  • Length: количество элементов.

  • Tail: список без первого элемента.

Применение:

let people = ["Tom"; "Sam"; "Bob"]

printfn "Список пуст? %b" (people.IsEmpty)
printfn "Количество элементов: %d" (people.Length)
printfn "Первый элемент: %s" (people.Head)
printfn "Второй элемент %s" (people.Tail.Head)
printfn "Третий элемент %s" (people.Item(2))

Операции со списком

Модуль List предоставляет ряд функций для выполнения различных операций над списками. Рассмотрим основные из них. В F# список представляет неизменяемую структуру данных, тем не менее для списка определен ряд функций для добавления и удаления элементов, только результат этих функций - представляет новый список (старый при этом не изменяется):

  • List.append(): добавляет элементы одного списка в другой

  • List.insertAt(): добавляет элемент по определенному индексу

  • List.removeAt(): удаляет элемент по определенному индексу

  • List.updateAt(): изменяет элемент по определенному индексу

Применение функций:

let people = ["Tom"; "Bob"]
let people1 = List.append people  ["Alice"; "Mike"; "Sam"]
for p in people1 do printf "%s  " p      // Tom  Bob  Alice  Mike  Sam
printfn ""

let people2 = List.updateAt 0 "Tomas" people1   // по индексу 0  устанавливаем значение  "Tomas"
for p in people2 do printf "%s  " p      // Tomas  Bob  Alice  Mike  Sam
printfn ""

let people3 = List.removeAt 1 people2   // удаляем элемент по индексу 1
for p in people3 do printf "%s  " p      // Tomas  Alice  Mike  Sam
printfn ""

let people4 = List.insertAt 0 "Alex" people3    // добавляем по индексу 0 строку "Alex"
for p in people4 do printf "%s  " p      // Alex  Tomas  Alice  Mike  Sam

Проверка элементов

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

  • List.exists: проверяет, есть ли в списке элементы, удовлетворяют некоторому условию. Если такие имеются, тогда возвращает true, иначе возвращает false.

    Первый параметр функции - функция условия, а второй параметр - список для поиска

    let people = ["Tom"; "Sam"; "Bob"]
    let result1 = List.exists (fun p ->  p = "Sam") people
    printfn $"result1 = {result1}"  // True
    
    let result2 = List.exists (fun p ->  p = "Alex") people
    printfn $"result2 = {result2}"  // False
    

    В первом случае применяется условие fun p -> p = "Sam", которое проверяет значение элемента, что элемент равен строке "Sam". Во втором случае проверяем на равенство строке "Alex". И если хотя бы один элемент с таким значением присутствует в списке, то возвращается true.

  • List.forall: также проверяет, есть ли в списке элементы, удовлетворяют некоторому условию, только возвращает true, если все элементы соответствуют этому условию

    let people = ["Tom"; "Sam"; "Bob"]
    let result1 = List.forall (fun p ->  p = "Sam") people
    printfn $"result1 = {result1}"  // False
    
    let result2 = List.forall (fun (p:string) ->  p.Length=3) people
    printfn $"result2 = {result2}"  // True
    

    Во втором случае проверяем, все ли элементы имеют длину в 3 символа. Поскольку обращаемся к свойству объекта string, то явным образом типизируем параметр условия типом string.

  • List.exists2: принимает два списка и проверяет, соответствуют ли в обоих списках элементы по одному индексу некоторому условию. Если хотя бы одна пара элементов соответствуют, то возвращает true, иначе возвращает false.

    Первый параметр функции - функция условия, а второй и третий параметры - сравниваемые списки

    let people = ["Tom"; "Sam"; "Bob"]
    let employees = ["Tom"; "Alice"; "Bob"]
    let result1 = List.exists2 (fun p e ->  p = e) people employees
    printfn $"result1 = {result1}"  // True
    
    let students = ["Alex"; "Mike"; "Kate"]
    let result2 = List.exists2 (fun p s ->  p = s) people students
    printfn $"result2 = {result2}"  // False
    

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

  • List.forall2: возвращает true, если элементы с одинаковыми индексами обоих списков соотвутствуют условию.

    let people = ["Tom"; "Sam"; "Bob"]
    let employees = ["Tom"; "Sam"; "Bob"]
    let result1 = List.forall2 (fun p e ->  p = e) people employees
    printfn $"result1 = {result1}"  // True
    
    let students = ["Tom"; "Mike"; "Kate"]
    let result2 = List.forall2 (fun p s ->  p = s) people students
    printfn $"result2 = {result2}"  // False
    

Поиск в списке

Ряд функций выполняют поиск элемента в списке. Функция List.tryFind возвращает из списка первый элемент, который соответствует некоторому условию. Поскольку такого элемента может и не быть в списке, то возвращается в реальности не сам элемент, а его обертка в виде объекта Option. С помощью свойства Value можно получить значение.

let people = ["Tom"; "Sam"; "Bob"]

let printOption option =
    match option with 
    | Some value -> printfn $"{value}"
    | None -> printfn "Не найдено"

let result1 = List.tryFind (fun (p:string) ->  p.StartsWith("T")) people
printOption result1     // Tom

let result2 = List.tryFind (fun (p:string) ->  p.EndsWith("e")) people
printOption result2     // Не найдено

В первом случае ищем в списке элемент, который начинается на букву "T" и для этого применяем метод StartsWith(). Во втором случае ищем элемент, который заканчивается на букву "e" и для этого используем метод EndsWith(). Оба метода определены у типа string.

Для вывода результата определяем функцию printOption, которая получает объект Option и с помощью конструкции match сопоставляет его с двумя паттернами - Some и None. Если элемент найден, тогда объект option успешно сопоставляется с Some, а его значение помещается в переменную value:

match option with 
    | Some value -> printfn $"{value}"

Для списков также доступна функция List.find, которая также возвращает первый элемент, который соответствует по критерию. Но в отличие от List.tryFind() возвращается непосредственно сам элемент:

let people = ["Tom"; "Sam"; "Bob"]

let result1 = List.find (fun (p:string) ->  p.StartsWith("T")) people
printfn $"{result1}"     // Tom

Однако если в списке нет элементов, которые соответствуют критерию, то генерируется ошибка. Поэтому функция List.tryFind() может представлять более предпочтительный способ поиска.

List.tryFindIndex: возвращает из списка индекс первого элемента, который соответствует некоторому условию. Работает аналогично функции tryFind() - поскольку такого элемента может и не быть в списке, то возвращается не сам индекс элемента, а его обертка в виде объекта Option. С помощью свойства Value можно получить значение.

let people = ["Tom"; "Sam"; "Bob"]
let printOption option =
    match option with 
    | Some value -> printfn $"Index: {value}"
    | None -> printfn "Не найдено"

let result1 = List.tryFindIndex (fun (p:string) ->  p.StartsWith("T")) people
printOption result1     // Index: 0

let result2 = List.tryFindIndex (fun (p:string) ->  p.EndsWith("e")) people
printOption result2     // Не найдено

Стоит не забывать, что индексация элементов начинается с нуля.

Фильтрация списка

С помощью функции List.filter можно отфильтровать элементы списка в сответствии с некоторым условием. Функция возвращает новый список, которая содержит только те элементы, которые соответствуют этому условию.

Первый параметр функции - функция условия, а второй параметр - фильтруемый список

let people = ["Tom"; "Alice"; "Sam"; "Kate"; "Bob"]

let result = List.filter (fun (p:string) ->  p.Length = 3) people
for p in result do printf $"{p}; "        // "Tom"; "Sam"; "Bob"

В данном случае получаем в новый список из исходного списка все строки, длина которых равна 3 символам.

Сортировка

Ряд функций выполняют сортировку элементов спика. Например, функция List.sort сортирует элементы по возрастанию, а List.sortDescending - по убыванию:

let people = ["Tom"; "Sam"; "Bob"; "Alice"; "Mike"]
let sortedPeople = List.sort people         // сортировка по возрастанию
for p in sortedPeople do printf $"{p} "     // Alice Bob Mike Sam Tom

printfn ""

let sortedPeopleDesc = List.sortDescending people   // сортировка по убыванию
for p in sortedPeopleDesc do printf $"{p} "         // Tom Sam Mike Bob Alice

Стоит отметить, что сортируемые значения должны реализовать интерфейс System.IComparable, в частности, его функцию System.IComparable.CompareTo(), которая применяется при сравнении. Эта функция сравнивает текущий элемент с параметром и возвращает число. Если текущий элемент должен стоять перед значением из параметра, то возвращается отрицательное значение. Если текущий элемент должен находиться после элемента из параметра, то должно возвращаться положительное значение. Например:

type Person(name:string, age:int) = 
    member this.Name = name
    member this.Age = age 
    interface System.IComparable with
        member this.CompareTo obj = 
            let person = obj :?> Person      // преобразуем к типу Person
            this.Age - person.Age               // сравниваем по возрасту

let people = [ Person("Tom", 39); Person("Sam", 28); Person("Bob", 43);
                Person("Alice", 34); Person("Mike", 31)]

let sortedPeople = List.sort people  
for p in sortedPeople do printf $"{p.Name}({p.Age}) "     // Sam(28) Mike(31) Alice(34) Tom(39) Bob(43)

Здесь реализуем функцию CompareTo для класса Person. В эту функцию передается объект, который сравнивается с текущим. В данном случае выполняем сравнение по возрасту - свойству Age. Причем метод получает значение типа Object, поэтому нам надо его преобразовать к типу Person, чтобы произвести сравение.

let person = obj :?> Person      // преобразуем к типу Person

Затем формируем результат метода:

this.Age - person.Age               // сравниваем по возрасту

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

sortBy

Функция List.sortBy() принимает функцию, которая возвращает значение-критерий сортировки. Особенно полезна эта функция при сравнении комплексных объектов. Например:

type Person(name:string, age:int) = 
    member this.Name = name
    member this.Age = age 

let people = [ Person("Tom", 39); Person("Sam", 28); Person("Bob", 43);
                Person("Alice", 34); Person("Mike", 31)]
// сортировка по имени
let sortedByName = List.sortBy (fun (p:Person) -> p.Name) people    
for p in sortedByName do printf $"{p.Name} "     // Alice Bob Mike Sam Tom

// сортировка по возрасту
let sortedByAge = List.sortBy (fun (p:Person) -> p.Age) people  
for p in sortedByAge do printf $"{p.Name}({p.Age}) "     // Sam(28) Mike(31) Alice(34) Tom(39) Bob(43)

Здесь сортируется список объектов Person, которые состоят из двух свойств - Name и Age. В первом случае сортируем по имени - свойству Name, поэтому для определения критерия сортировки используем функцию:

fun (p:Person) -> p.Name

В эту функцию передается объект списка, и у него выбирается значение свойства Name.

Во втором случае аналогично сортируем по имени.

sortWith

Еще одна функция - List.sortWith() в качестве первого параметра принимает функцию сравнения и в качестве второго - сортируемый список:

type Person(name:string, age:int) = 
    member this.Name = name
    member this.Age = age

let people = [ Person("Tom", 39); Person("Sam", 28); Person("Bob", 43);
                Person("Alice", 34); Person("Mike", 31)]
let comparePeople (person1:Person) (person2:Person) = 
    if person1.Name > person2.Name then 1
    elif person1.Name < person2.Name then -1
    else 0

let sortedPeople = List.sortWith comparePeople people  
for p in sortedPeople do printf $"{p.Name} "     // Alice Bob Mike Sam Tom

Здесь применяется функция comparePeople, которая принимает два объекта Person и сравнивает их по имени.

Агрегатные операции со списком

Для списка доступен ряд функций, которые выполняют агрегатные операции над списком:

  • sum: вычисляет сумму элементов

  • min: вычисляет минимальное значение

  • max: вычисляет максимальное значение

  • average: вычисляет среднее значение (элементы должны представлять тип float)

Применение:

let numbers = [1f; 2f; 3f; 4f; 5f]
printfn "Sum: %.1f" (List.sum numbers)              // Sum: 15.0
printfn "Min: %.1f" (List.min numbers)              // Min: 1.0
printfn "Max: %.1f" (List.max numbers)              // Max: 5.0
printfn "Average: %.1f" (List.average numbers)      // Average: 3.0

Двойники этих функций с суффиксом By в качестве первого параметра принимают функцию, которая определяет критерий для вычисления значения:

  • sumBy

  • minBy

  • maxBy

  • averageBy

  • countBy (вычисляет количество элементов, которые соответствуют некоторому критерию)

Например:

let people = [ ("Tom", 39); ("Bob", 43); ("Sam", 28)]

let projection = fun (name, age) -> age

let sum = List.sumBy projection people
printfn $"Сумма возрастов: {sum}"

let min =  List.minBy projection people
printfn $"С мин. возрастом: {min}"

let max =  List.maxBy projection people
printfn $"С макс. возрастом: {max}"

let average = List.averageBy (fun (name, age) -> float age) people
printfn "Средний возраст: %.1f" average

let result = List.countBy (fun (name, age) -> age > 30) people
printfn $"Больше 30 лет: {result}"

Здесь имеется список people, где каждый элемент представляет кортеж из двух значений. Будем условно считать, что каждый такой кортеж представляет пользователя, где первый элемент - имя, а второй - возраст. В качестве функции-проекции, которая определяет критерий подсчета, определена функция projection:

let projection = fun (name, age) -> age

Она получает в качестве параметра - элемент списка - кортеж из двух элементов и возвращает второе значение из кортежа. То есть вычисления будем производить относительно возраста. Далее применяем эту функцию в функциях List.sumBy/List.minBy/List.maxBy. Функция List.sumBy возращает сумму (в данном случае сумму возрастов), List.minBy - элемент списка с минимальным значением, а List.maxBy - элемент с максимальным значением.

List.averageBy возвращает среднее значение, но это значение вычисляется на наборе чисел float, поэтому преобразуем возраст в значение float:

let average = List.averageBy (fun (name, age) -> float age) people

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

let result = List.countBy (fun (name, age) -> age > 30) people

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

Сумма возрастов: 110
С мин. возрастом: (Sam, 28)
С макс. возрастом: (Bob, 43)
Средний возраст: 36.7
Больше 30 лет: [(True, 2); (False, 1)]

Извлечение подсписка

Ряд функций модуля List позволяют получить часть списка. Так, функции List.take и List.truncate создают новый список, который содержит определенное количество элементов исходного списка. Первый параметр функции - количество элементов, а второй параметр - список. Извлечение элементов идет с начала списка

let people = ["Tom"; "Alice"; "Bob"; "Mike"; "Sam"]

let items = List.take 3 people
for item in items do printf $"{item} \t"        // Tom Alice Bob

let items2 = List.truncate 3 people
for item in items2 do printf $"{item} \t"        // Tom Alice Bob

Здесь извлекаем три первых элемента. Обе функции работают похожим образом за тем исключением, что функция List.take генерирует исключение System.InvalidOperationException, если количество извлекаемых элементов больше количество элементов списка.

Функция List.skip пропускает определенное количество элементов с начала списка.

let people = ["Tom"; "Alice"; "Bob"; "Mike"; "Sam"]

let items = List.skip 2 people      // пропускаем первых 2 элемента
for item in items do printf $"{item} \t"        // Bob Mike Sam

Функция List.skipWhile пропускает определенное количество элементов с начала списка, которые соответствуют условию:

let people = ["Tom"; "Bob"; "Alice"; "Mike"; "Sam"]

let items = List.skipWhile (fun (p:string) -> p.Length < 4) people 
for item in items do printf $"{item} \t"        // Alice Mike Sam

Здесь пропускаем сначала строки, длина которых меньше 4 символов. Строки пропускаются только сначала, пока не дойдем до строки, которая не соответствует условию.

Функция List.takeWhile оставляет определенное количество элементов с начала списка, которые соответствуют условию:

let people = ["Tom"; "Bob"; "Alice"; "Mike"; "Sam"]

let items = List.takeWhile (fun (p:string) -> p.Length < 4) people 
for item in items do printf $"{item} \t"        // Tom Bob

Здесь добавляем в новый список строки, длина которых меньше 4 символов. Строки добавляются только сначала, пока не дойдем до строки, которая не соответствует условию.

Проекция списка

Функция List.map позволяет преобразовать список. Первый параметр - функция преобразования, которая применяется к каждому элементу, а второй параметр - исходный список. Результат функции List.map - новый список из преобразованных элементов:

let people = [("Tom", 39); ("Bob", 43); ("Alice", 34); ("Mike", 31); ("Sam", 28)]

let items = List.map (fun (name, age) -> name) people 
for item in items do printf $"{item} \t"      

В данном случае функция преобразования fun (name, age) -> name получает каждый элемент списка в виде кортежа из двух значений - name и age и возвращает значение name. Соответственно из списка кортежей, где каждый элемент хранит строку и число, получим список из строк.

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