Как скопировать список в Scala - PullRequest
12 голосов
/ 18 сентября 2009

Я хочу поверхностно скопировать список в Scala.

Я хотел сделать что-то вроде:

val myList = List("foo", "bar")
val myListCopy = myList.clone

Но метод клонирования защищен.

Ответы [ 2 ]

14 голосов
/ 19 сентября 2009

Вот не ответ: не делай этого. List является неизменным, поэтому копировать его абсолютно бессмысленно.

Давайте рассмотрим несколько операций:

val list = List(1,2,3)
val l1 = 0 :: list
val l2 = "a" :: list

Ни l1, ни l2 не изменяют list, но они оба создают новые списки, которые ссылаются на list.

Давайте объясним это подробно. Конструктор List(1,2,3) создает три элемента, а также использует одноэлементный объект. В частности, он создает экземпляры этих элементов:

::(3, Nil)
::(2, reference to the previous element)
::(1, reference to the previous element)

И Nil является одноэлементным объектом. На что действительно указывает идентификатор list, тот последний элемент.

Теперь, когда вы присваиваете 0 :: list для l1, вы создаете экземпляр одного нового объекта:

::(0, reference to ::(1, etc))

Конечно, поскольку есть ссылка на list, вы можете думать о l1 как о списке из четырех элементов (или пяти, если вы считаете Nil).

Теперь l2 даже не того же типа list, но он также ссылается на него! Здесь:

::("a", reference to ::(1, etc))

Важным моментом во всех этих объектах является то, что они не могут быть изменены. Здесь нет сеттеров и методов, которые могут изменить какие-либо их свойства. У них всегда будут одинаковые значения / ссылки в их «голове» (это то, что мы называем первым элементом) и те же ссылки в их «хвосте» (это то, что мы называем вторым элементом).

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

val l3 = list map (n => n + 1)

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

val l4 = l2 filter (n => n.isInstanceOf[Int])

Хотя l4 имеет те же элементы, что и list (но другого типа), это также совершенно новый список. Метод filter создает новый список, основанный на правиле, которое вы передаете, чтобы сообщить ему, какие элементы входят, а какие нет. Он не пытается оптимизировать, если может вернуть существующий список.

val l5 = list.tail

Это не создает новый список. Вместо этого он просто присваивает l5 существующий элемент list.

val l6 = list drop 2

Опять же, новый список не создан.

val l7 = list take 1

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

Вот несколько дополнительных деталей реализации:

  • List - абстрактный класс. У него есть два потомка, класс :: (да, это имя класса) и одноэлементный объект Nil. List запечатан, поэтому вы не можете добавить к нему новые подклассы, а :: является окончательным, поэтому вы не можете подкласс его.

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

6 голосов
/ 18 сентября 2009

Чтобы отфильтровать список:

val list = List(1,2,3,4,5)
//only evens
val evens = list.filter(e=>e%2 == 0)

println(list)
//--> List(1, 2, 3, 4, 5)

println(evens) 
//--> List(2, 4)

Вы также можете использовать подстановочный знак для сохранения нескольких символов:

val evens = list.filter(_%2==0)

Обратите внимание, что, как отмечено выше, списки являются неизменяемыми. Это означает, что эти операции не изменяют исходный список, но фактически создают новый список.

...