Коллекции

Последовательность

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

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

В F# есть следующие коллекции: List (список), Array (массив), seq (последовательность), Map (словарь), Set (набор данных, основанный на бинарных деревьях)

Тип seq или последовательность представляет набор элементов одного типа. Последовательности удобно применять, когда необходимо выполнить операции над отдельной частью элементов каких-то больших коллекций. А последовательность предоставляет удобный способ извлечь часть коллекции и тем самым обеспечить большую производительность.

Для определения последовательности применяется оператор seq:

seq { элементы_последовательности}

После оператора seq в фигурных скобках идет определение элементов последовательности. Пример определения последовательности:

let numbers = seq {1; 2; 3; 4; 5}

Здесь последовательность numbers содержит числа 1, 2, 3, 4, 5

Динамическая генерация последовательности

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

seq { начало..конец }

Например, определим последовательность чисел от 1 до 6:

let numbers = seq {1..6}        // 1 2 3 4 5 6

Посмотрим, что внутри последовательности. И для этого мы можем перебрать ее с помощью цикла for..in:

let numbers = seq {1..6}        

for n in numbers do printf $"{n} "  // 1 2 3 4 5 6

При подобном определении конечное значение должно быть больше начального.

Шаг последовательности

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

начало..приращение..конец

Например, зададим приращение на 2:

let numbers = seq {0..2..10}        
for n in numbers do printf $"{n} "  // 0 2 4 6 8 10

Причем приращение может быть отрицательным. Тогда начальное значение должно быть больше конечного

let numbers = seq {6..-1..1}        // 6 5 4 3 2 1
for n in numbers do printf $"{n} "  // 6 5 4 3 2 1

Создание последовательности из цикла

Генерацию последовательности можно определить с помощью цикла:

let numbers = seq {for n in 1..5 do n * n} 

Здесь с помощью цикла for n in 1..5 do n * n перебирается набор чисел от 1 до 5 и возвращается квадрат каждого элемента этого набора. Таким образом, в создаваемой последовательности окажутся числа 1, 4, 9, 16, 25.

Причем в данном цикле оператор do также можно заменить на оператор ->:

let numbers = seq {for n in 1..5 -> n * n} 

Условная генерация

С помощью конструкции if..then можно задать условие для генерации элементов:

let numbers = seq {for n in 1..10 ->  if n%2=0 then n * n else n }        

for n in numbers do printf $"{n} "  // 1 4 3 16 5 36 7 64 9 100

Условная конструкция if n%2=0 then n * n else n говорит, что если элемент n делится на 2 без остатка (то есть если число четное), то передаем в последовательность квадрат числа n, иначе передаем само число n.

Причем в данном случае мы можем не использовать подвыражение else:

let numbers = seq {for n in 1..10 do if n%2=0 then n * n}        

for n in numbers do printf $"{n} "  // 4 16 36 64 100

В этом случае в последовательность будут передаваться только результат выражения после оператора then, то есть в данном случае только квадраты четных чисел. Однако при этом в цикле for вместо оператора -> применяется do.

Объединение и повторение последовательности и оператор yield!

Оператор yield! позволяет добавить в генерируемую последовательность другую последовательность. Например:

let numbers = seq {
    for n in 5..7 do 
        yield n
        yield! seq { 0; 1; 2}
} 
for n in numbers do printf $"{n} "  // 5 0 1 2 6 0 1 2 7 0 1 2

Здесь перебирается последовательность 5..7. Из нее в генерируемую последовательность мы передаем текущий перебираемый элемент с помощью оператора yield, а с помощью оператора yield! добавляем другую последовательность - { 0; 1; 2}. Таким образом, будет сгенерирована последовательность 5 0 1 2 6 0 1 2 7 0 1 2

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

let numbers = seq {for _ in 1..3 do yield! seq { 0; 1; 2}}        

for n in numbers do printf $"{n} "  // 0 1 2 0 1 2 0 1 2

Здесь перебирается набор 1..3 и для каждого элемента из этого набора в генерируемую последовательность добавляется последовательность { 0; 1; 2}. То есть в итоге будет сгенерирована последовательность 0 1 2 0 1 2 0 1 2

Создание поледовательности с помощью функций Seq

Также модуль Seq предоставляет ряд функций для создания последовательности. Так, функция Seq.empty создает пустую последовательность:

let items = Seq.empty

Функция Seq.singleton создает поледовательность из одного элемента:

let items = Seq.singleton 5

Еще одна функция - Seq.init с помощью функции создает определенное количество элементов:

let items = Seq.init 5 (fun n -> n)

for n in items do printf $"{n}"     // 0 1 2 3 4

Функция Seq.init принимает два параметра. Первый параметр - сколько элементов надо создать. Второй параметр - функция создания. Эта функция пробегается по ряду целых чисел от 0 до количества элементов - текущее перебираемое число передается в качестве параметра. И возвращает создаваемый элемент.

В данном случае создается 5 элементов последовательности. В качестве функции генерации элементов используется лямбда-выражение fun n -> n, которое пробегается по числам от 0 до 5 (не включая) и получает каждое число в виде параметра n и просто возвращает это число. То есть функция генерации последовательности возвратит следующий ряд элементов: 0, 1, 2, 3, 4.

Но естественно логика может быть более комплексная, например, возвратим ряд чисел 0 10 20 30 40:

let items = Seq.init 5 (fun n -> n * 10)

Или создадим последовательность, в которой повторяется три раза строка "Tom"

let items = Seq.init 3 (fun _ -> "Tom")     // Tom Tom Tom

Бесконечная последовательность

Для создания бесконечной последовательности применяется функция Seq.initInfinite. Она имеет один параметр - функцию генерации элемента. В качестве параметра функция генерации получает индекс элемента и создает элемент.

Может возникнуть вопрос, как вообще можно создать бесконечную последовательность? В данном случае элементы создаются по мере обращения к ним. Например, определим последовательность, которая содержит квадраты чисел:

let items = Seq.initInfinite (fun index -> index * index)  

Операции с последовательностью

Модуль Seq предоставляет ряд функций для выполнения различных операций над последовательностями. Рассмотрим основные из них.

Поиск в последовательности

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

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

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

    let numbers1 = seq {1; 3; 5; 7; 9}
    let result1 = Seq.exists (fun n ->  n%2=0) numbers1
    printfn $"result1 = {result1}"	// False
    
    let numbers2 = seq {for n in 1..9 -> n *n }    // 1 4 9 25 36 49 64 81
    let result2 = Seq.exists (fun n ->  n%2=0) numbers2
    printfn $"result2 = {result2}"	// True
    

    В данном случае применяется условие fun n -> n%2=0, которое проверяет является ли число четным, то есть делится на 2 без остатка.

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

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

    let numbers1 = seq {1; 3; 5; 7; 9}
    let numbers2 = seq {for n in 1..5 -> n }    // 1 2 3 4 5
    let result1 = Seq.exists2 (fun n m ->  n = m) numbers1 numbers2
    printfn $"result1 = {result1}"
    
    
    let numbers3 = seq {1; 2; 3; 4; 5}
    let numbers4 = seq {for n in 1..5 -> n }    // 1 2 3 4 5
    let result2 = Seq.exists2 (fun n m ->  n = m) numbers3 numbers4
    printfn $"result2 = {result2}"
    

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

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

    let numbers1 = seq {for n in 1..6 -> n }    // 1 2 3 4 5 6
    let result1 = Seq.tryFind (fun n ->  n % 2=0) numbers1
    if result1.IsSome 
    then printfn $"result1 = {result1.Value}"      // result1 = 2
    else printfn "Не найдено"
    
    
    let numbers2 = seq {1; 3; 5; 7; 9}
    let result2 = Seq.tryFind  (fun n ->  n % 2=0) numbers2
    if result2.IsSome 
    then printfn $"result1 = {result1.Value}"
    else printfn "Не найдено"
    

    В данном случае для обоих последовательностей ищем элементы, которые представляют четные числа. В первой последовательности такие элементы есть, и соответственно будет возвращен первый из этих элементов. А во второй последовательности таких элементов нет. Поэтому сначала проверяем, есть ли какое-то значение - в этом случае свойство IsSome должно возвратить true, и если значение есть, то через свойство Value обращаемся к этому значению.

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

    let numbers1 = seq {for n in 1..6 -> n }    // 1 2 3 4 5 6
    let result1 = Seq.tryFindIndex (fun n ->  n % 2=0) numbers1
    if result1.IsSome 
    then printfn $"индекс элемента = {result1.Value}"      // индекс элемента = 1
    else printfn "Не найдено"
    
    let numbers2 = seq {1; 3; 5; 7; 9}
    let result2 = Seq.tryFindIndex  (fun n ->  n % 2=0) numbers2
    if result2.IsSome 
    then printfn $"индекс элемента = {result1.Value}"
    else printfn "Не найдено"
    

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

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

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

  • Seq.filter: фильтрует элементы последовательности в сответствии с некоторым условием. Возвращает новую последовательность, которая содержит только те элементы, которые соответствуют этому условию.

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

    let numbers = seq {for n in 1..6 -> n }    // 1 2 3 4 5 6
    let filteredNumbers = Seq.filter (fun n ->  n % 2=0) numbers
    for n in filteredNumbers do printf $"{n} \t"        // 2 4 6
    

    В данном случае получаем в новую последовательность четные числа из исходной последовательности.

  • Seq.trancate и Seq.take: создают новую последовательность, которая содержит определенное количество элементов исходной последовательности.

    Первый параметр функции - количество элементов, а второй параметр - фильтруемая последовательность. Извлечение элементов идет с начала последовательности

    let numbers = seq {for n in 1..6 -> n }    // 1 2 3 4 5 6
    let truncatedNumbers = Seq.truncate 3 numbers
    for n in truncatedNumbers do printf $"{n} \t"        // 1 2 3
    
    printfn ""
    
    let takenNumbers = Seq.take 3 numbers
    for n in takenNumbers do printf $"{n} \t"        // 1 2 3
    

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

    let numbers = seq {for n in 1..6 -> n }    // 1 2 3 4 5 6
    
    let truncatedNumbers = Seq.truncate 10 numbers
    for n in truncatedNumbers do printf $"{n} \t"        // 1 2 3 4 5 6
    
    printfn ""
    
    let takenNumbers = Seq.take 10 numbers      // ! Ошибка
    for n in takenNumbers do printf $"{n} \t"       
    
  • Seq.skip: пропускает определенное количество элементов с начала последовательности.

    let numbers = seq {for n in 1..10 -> n }    // 1 2 3 4 5 6 7 8 9 10
    
    let skipedNumbers = Seq.skip 5 numbers      // пропускаем пять первых элементов
    for n in skipedNumbers do printf $"{n} \t"        // 5 6 7 8 9 10
    
  • Seq.skipWhile: пропускает определенное количество элементов с начала последовательности, которые соответствуют условию:

    let numbers = seq {for n in 1..10 -> n }    // 1 2 3 4 5 6 7 8 9 10
    
    let skipWhiledNumbers = Seq.skipWhile (fun n -> n < 4) numbers      // пропускаем числа меньше 4
    for n in skipWhiledNumbers do printf $"{n} \t"    // 4 5 6 7 8 9 10
    
  • Seq.takeWhile: оставляет определенное количество элементов с начала последовательности, которые соответствуют условию:

    let numbers = seq {for n in 1..10 -> n }    // 1 2 3 4 5 6 7 8 9 10
    
    let takeWhiledNumbers = Seq.takeWhile (fun n -> n < 4) numbers      // оставляем числа меньше 4
    for n in takeWhiledNumbers do printf $"{n} \t"    // 1 2 3 
    

Преобразование последовательности

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

let numbers = seq {for n in 1..5 -> n }    // 1 2 3 4 5
let transformedNumbers = Seq.map (fun n -> n * n) numbers	
for n in transformedNumbers do printf $"{n} "     // 1 4 9 16 25

В данном случае функция преобразования fun n -> n * n возвращает квадрат числа из исходной последовательности. Соответственно в созданной последовательности окажутся квадраты чисел исходной последовательности.

Сортировка

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

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

printfn ""

let numbers = seq {5; 2; 6; 9; 1; 3; 8}
let sortedNumbers = Seq.sortDescending numbers	// сортировка по убыванию
for n in sortedNumbers do printf $"{n} "     // 9 8 6 5 3 2 1

Связь с платформой .NET

Последовательности представлены типом seq<'T>, который соответствует на платформе .NET типу IEnumerable<T>. Поэтому любой тип .NET, который реализует интерфейс IEnumerable<T>, может использоваться как последовательность.

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