2016-06-23 5 views
1

ListBuffer может эффективно присоединить элемент до конца и затем преобразовать в List.головоломка новичка об исходном коде + = метод класса ListBuffer в Scala

Я изучил исходный код метода добавления ListBuffer (+=), но было трудно понять его работу. Детальный исходный код: here.

Выдержки Исходный код:

final class ListBuffer[A] extends AbstractBuffer[A] { 
    private var start: List[A] = Nil 
    private var last0: ::[A] = _ 
    private var exported: Boolean = false 
    private var len = 0 

    def += (x: A): this.type = { 
    if (exported) copy() 
    if (isEmpty) { 
     last0 = new :: (x, Nil) 
     start = last0 
    } else { 
     val last1 = last0  // last1 is a local variable, is it necessary here? 
     last0 = new :: (x, Nil) 
     last1.tl = last0  // 
    } 
    len += 1 
    this 
    } 
} 

В else части, локальная переменная last1 определена и впоследствии построена, чтобы содержать в конце несколько элементов. last0 всегда указывает на последнюю ячейку.

Значит, last1 необходимо здесь? В конце концов, после блокировки он будет недоступен. Я не могу понять, почему автор должен определить last1 здесь.

ответ

1

То, что здесь происходит, по существу

last0.ti = new :: (x, Nil) // put new element x at end of list 

За исключением, что есть проблема с этим сокращением: last0 всегда необходимо «точку в» конце. Таким образом, last1 становится временной ссылкой на конец списка, так что можно получить доступ к end-of-the-list.t1 и добавить новый конец списка.

0

Итак, есть 3 линии.

last1 будет указывать на текущий last0.

last0 установлен на новый последний элемент.

последний1.t1 установлен на last0.

последний1 не новый объект. Он просто указывает на текущий последний. t1 Я предполагаю, что это указатель на его следующий элемент. Итак, что его делает, указывая текущий последний элемент на новый элемент.

Это может сбивать с толку в scala, поскольку оно использует mutables и vars, которых можно избежать, если это возможно, в scala.

2

Ну

val last1 = last0  // last1 is a local variable, is it necessary here? 
    last0 = new :: (x, Nil) 
    last1.tl = last0  // 

Как вы можете видеть, это действительно используется для изменения .tlпосле мы изменили last0.

Что такое .tl?

@SerialVersionUID(509929039250432923L) // value computed by serialver for 2.11.2, annotation added in 2.11.4 
final case class ::[B](override val head: B, private[scala] var tl: List[B]) extends List[B] { 
    override def tail : List[B] = tl 
    override def isEmpty: Boolean = false 
} 

Как вы можете видеть, класс :: не очень неизменны: есть ссылка на остальной части списка, который может быть изменен, что именно то, что делает в последней строке.

Теперь зачем вам это менять после изменения last0?Это потому, что нам нужна временная переменная, чтобы сохранить вновь созданный последний элемент, и нам нужно присвоить этому элементу last0, поэтому мы просто переназначим last0 и создадим новый последний элемент за один раз и свяжем старый последний элемент (удерживаемый в last1) после.

Надеюсь, это поможет.

+0

Спасибо. Все еще запутано. Почему «нужна временная переменная для хранения недавно созданного последнего элемента»? 'last0 = new :: (x, Nil)' будет хранить последнюю ячейку в поле класса 'last0'. Но 'last1' - это только локальная переменная, она исчезает после блока кода' else'. – Spring

+0

Недостаточно переместить 'last0'; вам также понадобится «tl» текущего предпоследнего элемента, чтобы указать на только что созданный элемент. Итак 'last0.tl = new :: (x, Nil); last0 = last0.tl' будет отлично работать, используя 'tl' вместо' last1'. Выбор между ними - это вопрос вкуса и/или тестов производительности. – alf

+0

Я обнаружил, что эта реализация метода '+ =' имеет некоторые проблемы. Приложенные элементы не могут быть сохранены в 'start'. Вот демонграмма выполнения: (1) 'val t = new ListBuffer [Int]'; (2) 't + = 1' // {' start = List (1) ',' last0 = List (1) '}; (3) 't + = 2'; {'start = List (1)', 'last0 = List (1)'} (4) 't.toList' {' List (1) '}. – Spring

Смежные вопросы