Связанные списки

Связанные списки реализованы в Scala в виде класса List.

Однонаправленные связанные списки используются в тех случаях, когда необходимо эффективно добавлять элемент в начало списка, а также разделять список на головной элемент и "хвост" - временная сложность выполнения этих операций равна константе. Сложность большинства остальных операций - это O(N), включая вычисление размера списка.

Для того чтобы вычислить размер списка или добавить элемент в конец списка необходимо пройтись по всему списку. Временная сложность добавления или удаления элемента в заданном индексе может доходить до O(N) в случае индекса, близкого N.

Определение списка

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

Код:

В следующем определении:

enum LinkedList[+A]:
  case Nil
  case Cons(head: A, tail: LinkedList[A])

val emptyList: LinkedList[Int] = LinkedList.Nil
// Пустой список
val nonEmptyList: LinkedList[Int] = LinkedList.Cons(3, LinkedList.Cons(2, LinkedList.Cons(1, LinkedList.Nil)))
// Непустой список: (3, 2, 1)

Заглавный элемент

Оценка:

Код:

enum LinkedList[+A]:
  lazy val headOption: Option[A] = this match
    case Nil        => None
    case Cons(h, _) => Some(h)

emptyList.headOption    // None
nonEmptyList.headOption // Some(3)

Остаток списка без начального элемента

Оценка:

Код:

enum LinkedList[+A]:
  lazy val tailOption: Option[LinkedList[A]] = this match
    case Nil           => None
    case Cons(_, tail) => Some(tail)

emptyList.tailOption    // None
nonEmptyList.tailOption // Some(Cons(2,Cons(1,Nil)))

Последний элемент

Оценка:

Код:

enum LinkedList[+A]:
  lazy val lastOption: Option[A] = this match
    case Nil          => None
    case Cons(h, Nil) => Some(h)
    case Cons(h, t)   => t.lastOption

emptyList.lastOption    // None
nonEmptyList.lastOption // Some(1)

Остаток списка без последнего элемента

Оценка:

Код:

enum LinkedList[+A]:
  lazy val initOption: Option[LinkedList[A]] = this match
    case Nil          => None
    case Cons(h, Nil) => Some(Nil)
    case Cons(h, t)   => t.initOption.map(Cons(h, _))

emptyList.initOption    // None
nonEmptyList.initOption // Some(Cons(3,Cons(2,Nil)))

Проверка списка на пустоту

Оценка:

Код:

enum LinkedList[+A]:
  lazy val isEmpty: Boolean = this match
    case Nil => true
    case _   => false

emptyList.isEmpty       // true
nonEmptyList.isEmpty    // false

Вычисление длины списка

Оценка:

Код:

enum LinkedList[+A]:
  lazy val length: Int = this match
    case Nil           => 0
    case Cons(_, tail) => 1 + tail.length

emptyList.length        // 0
nonEmptyList.length     // 3

Добавление в начало списка

Оценка:

Код:

enum LinkedList[+A]:
  def prepend[B >: A](b: B): LinkedList[B] = Cons(b, this)

emptyList.prepend(5)    // Cons(5,Nil)
nonEmptyList.prepend(5) // Cons(5,Cons(3,Cons(2,Cons(1,Nil))))

Добавление в конец списка

Оценка:

Код:

enum LinkedList[+A]:
  def append[B >: A](b: B): LinkedList[B] = this match
    case Nil              => Cons(b, Nil)
    case Cons(head, tail) => Cons(head, tail.append(b))

emptyList.append(5)     // Cons(5,Nil)
nonEmptyList.append(5)  // Cons(3,Cons(2,Cons(1,Cons(5,Nil))))

Получение элемента по заданному индексу

Оценка:

Код:

enum LinkedList[+A]:
  def get(i: Int): Option[A] =
    if i < 0 then None
    else if i == 0 then headOption
    else tailOption.flatMap(_.get(i - 1))

emptyList.get(0)        // None
nonEmptyList.get(0)     // Some(3)

Проверяет, содержит ли список заданный элемент

Оценка:

Код:

enum LinkedList[+A]:
  def contains[B >: A](x: B): Boolean = this match
    case Nil                        => false
    case Cons(head, _) if head == x => true
    case Cons(_, tail)              => tail.contains(x)  

emptyList.contains(0)    // false
nonEmptyList.contains(2) // true

Объединение двух списков

Оценка:

Код:

enum LinkedList[+A]:
  def concat[B >: A](list: LinkedList[B]): LinkedList[B] = this match
    case Nil              => list
    case Cons(head, tail) => Cons(head, tail.concat(list))   

emptyList.concat(nonEmptyList)    // Cons(3,Cons(2,Cons(1,Nil)))
nonEmptyList.concat(nonEmptyList) // Cons(3,Cons(2,Cons(1,Cons(3,Cons(2,Cons(1,Nil))))))

Фильтрация элементов списка

Оценка:

Код:

enum LinkedList[+A]:
  def filter(p: A => Boolean): LinkedList[A] = this match
    case Nil                         => Nil
    case Cons(head, tail) if p(head) => Cons(head, tail.filter(p))
    case Cons(_, tail)               => tail.filter(p)  

emptyList.filter(_ % 2 == 0)    // Nil
nonEmptyList.filter(_ % 2 == 0) // Cons(2,Nil)

Преобразование элементов списка

Оценка:

Код:

enum LinkedList[+A]:
  def map[B](f: A => B): LinkedList[B] = this match
    case Nil              => Nil
    case Cons(head, tail) => Cons(f(head), tail.map(f))

emptyList.map(_ + 1)            // Nil
nonEmptyList.map(_ + 1)         // Cons(4,Cons(3,Cons(2,Nil)))

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

Оценка:

Код:

enum LinkedList[+A]:
  def fold[B](init: B)(op: (B, A) => B): B = this match
    case Nil              => init
    case Cons(head, tail) => tail.fold(op(init, head))(op)

emptyList.fold(100)(_ + _)      // 100
nonEmptyList.fold(100)(_ + _)   // 106

Список из первых n элементов

Оценка:

Код:

enum LinkedList[+A]:
  def take(n: Int): LinkedList[A] = this match
    case Cons(head, tail) if n > 0 => Cons(head, tail.take(n - 1))
    case _ => Nil

emptyList.take(2)    // Nil 
nonEmptyList.take(2) // Cons(3,Cons(2,Nil))

Список за исключением первых n элементов

Оценка:

Код:

enum LinkedList[+A]:
  def drop(n: Int): LinkedList[A] = this match
    case list if n <= 0            => list
    case Cons(head, tail) if n > 0 => tail.drop(n - 1)
    case _                         => Nil

emptyList.drop(1)    // Nil
nonEmptyList.drop(1) // Cons(2,Cons(1,Nil)) 

Список из первых элементов, удовлетворяющих предикату

Оценка:

Код:

enum LinkedList[+A]:
  def takeWhile(p: A => Boolean): LinkedList[A] = this match
    case Cons(head, tail) if p(head) => Cons(head, tail.takeWhile(p))
    case _ => Nil

emptyList.takeWhile(_ > 1)    // Nil
nonEmptyList.takeWhile(_ > 1) // Cons(3,Cons(2,Nil))

Список за исключением первых элементов, удовлетворяющих предикату

Оценка:

Код:

enum LinkedList[+A]:
  def dropWhile(p: A => Boolean): LinkedList[A] = this match
    case Cons(head, tail) if p(head) => tail.dropWhile(p)
    case _ => this  

emptyList.dropWhile(_ > 1)    // Nil
nonEmptyList.dropWhile(_ > 1) // Cons(1,Nil)

Ссылки: