Почему функция reverse () в стандартной библиотеке Swift возвращает ReverseRandomAccessCollection?

18

Теперь, когда я научился Swift (на разумном уровне), я пытаюсь справиться со стандартной библиотекой, но на самом деле это в основном ελληνικά для меня!

Итак, конкретный вопрос: у меня есть массив строк, и я могу вызвать reverse () на нем.

let arr = ["Mykonos", "Rhodes", "Naxos"].reverse()

Теперь наивно я подумал, что я вернусь от этого типа Массив. (Ruby, например, имеет аналогичный метод, по которому вы передаете массив и возвращаете массив)

Но теперь arr имеет тип

ReverseRandomAccessCollection<Array<String>>

, который на самом деле является структурой, которая соответствует CollectionType:

public struct ReverseRandomAccessCollection<Base : CollectionType where Base.Index : RandomAccessIndexType> : _ReverseCollectionType

Это означает, что я могу это сделать:

for item in arr {
  print(item)
}

, но я не могу сделать

print(arr[0])

Почему это так должно быть?

Словари в Swift также реализуют CollectionType, поэтому я могу это сделать:

let dict = ["greek" : "swift sometimes", "notgreek" : "ruby for this example"].reverse()

Но словари не упорядочены, как массивы, поэтому почему я могу вызвать reverse () на dicts?

Бонусные баллы, если кто-нибудь может указать мне в направлении, где я могу читать и улучшать свой Swift stdlib foo, Ευχαριστώ!

    
задан Brynjar 01.01.2016 в 18:35
источник

3 ответа

24

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

Вы можете обращаться к обратным элементам с индексами:

let el0 = arr[arr.startIndex]
let el2 = arr[arr.startIndex.advancedBy(2)]

или

for i in arr.indices {
    print(arr[i])
}

Вы также можете создать массив с помощью

let reversed = Array(["Mykonos", "Rhodes", "Naxos"].reversed())

Словарь также представляет собой последовательность пар ключ / значение. В

let dict = ["greek" : "swift sometimes", "notgreek" : "ruby for this example"].reverse()

вызывается совершенно другой метод reversed() :

extension SequenceType {
    /// Return an 'Array' containing the elements of 'self' in reverse
    /// order.
    ///
    /// Complexity: O(N), where N is the length of 'self'.
    @warn_unused_result
    public func reversed() -> [Self.Generator.Element]
}

В результате получается массив с парами Key / Value словаря в обратном порядке. Но это ограниченное использование, потому что заказ пар «ключ / значение» в словаре может быть произвольным.

    
ответ дан Martin R 01.01.2016 в 18:58
  • Как идет речь об изучении этого уровня знания Свифта, по-прежнему мне кажется греческим, хотя;) –  Brynjar 01.01.2016 в 19:46
  • Возможно, стоит обратить внимание на то, почему массивы используют ленивый реверс, тогда как словарь возвращает новый массив, потому что словарь имеет только прямой индекс, а это означает, что нет постоянного обращения времени. Лучшее, что они могут сделать, это линейное время. В то время как коллекции с двунаправленными или произвольными доступными индексами не платят штраф за их ленивый разворот. –  Airspeed Velocity 01.01.2016 в 21:04
  • (в отличие от карты и фильтра, за которые вы платите штраф за перепланировку каждый раз, когда вы их используете, следовательно, вы должны явно ссылаться на лень через .lazy) –  Airspeed Velocity 01.01.2016 в 21:05
  • @AirspeedVelocity Не могли бы вы кратко рассказать о том, что означает «словарь имеет только прямой индекс»? –  Brynjar 04.01.2016 в 14:35
  • Тип индекса в коллекции определяет, как вы можете перемещаться по коллекции. Все коллекции имеют индексы вперед - вы можете начинать с startIndex и вызывать .successor (), пока не достигнете endIndex, но вы не сможете переместить индекс назад (в том числе из endIndex - так что вы не можете начать в конце и вернуться назад спереди). Следующий уровень вверх - это двунаправленный индекс, который добавляет метод .predecessor (). Там вы можете двигаться назад, так что вы можете начать с endIndex и вернуться назад. Это то, что позволяет ReverseCollection обертывать коллекцию и предоставлять лёгкую версию в обратном порядке. –  Airspeed Velocity 05.01.2016 в 18:29
3

Из языковых документов ReverseCollention (результат .reverse() ):

  

Метод reverse () всегда ленив, когда применяется к коллекции с   двунаправленные индексы , но не подразумевает предоставления лени   алгоритмы, применяемые к его результату.

     

Другими словами, для обычных коллекций c, имеющих двунаправленные индексы:

     
  • c.reverse () делает не создание нового хранилища
  •   

...

Следовательно, вы можете увидеть свой ReverseRandomAccessCollection в качестве обертки произвольного доступа по вашему массиву еще не отменив (т. е. ваш исходный массив arr еще не был скопирован и изменен на новую позицию в памяти).

Естественно, из вышесказанного вы не можете напрямую индексировать обратный сбор, так как Array дает доступ как указатель на память, которая содержит массив, а индексирование соответствует продолжению побитового (в зависимости от типа) форварда в Память. Тем не менее, мы можем получить доступ к элементам «обратного массива» в стиле индекса массива, используя ReverseRandomAccessIndex :

let arr = ["Mykonos", "Rhodes", "Naxos"]
let arrReverse = arr.reverse()
    /* ReverseRandomAccessCollection access "wrapper" over
       the 'arr' data in memory. No new storage allocated */

let myIndex = arrReverse.startIndex.advancedBy(2)
    /* bIndex type: 
       ReverseRandomAccessIndex<ReverseRandomAccessIndex<Index>> */

print(arrReverse[myIndex]) // "Mykonos"

И наоборот, мы можем явно выделить память для нашего обратного массива и рассматривать ее так же, как и любой другой массив. На данный момент arrReverse представляет собой отдельный массив, чем arr , и не имеет никакого отношения к первому другому, кроме (один раз), создаваемому с его использованием.

let arr = ["Mykonos", "Rhodes", "Naxos"]
let arrReverse = Array(arr.reverse())
    /* Array<String> */

let myIndex = arrReverse.startIndex.advancedBy(2)
    /* bIndex type: Int */

print(arrReverse[myIndex]) // "Mykonos"
Мартин R победил меня, поэтому см. его заметку о словарях .

    
ответ дан dfri 01.01.2016 в 19:06
  • Малая поправка: поскольку массив имеет индекс RandomAccessIndexType, результатом reverse () является swiftdoc.org/v2.0/type/ReverseRandomAccessCollection. Вы бы получили ReverseCollection, например. при обращении к символам строки. –  Martin R 01.01.2016 в 19:16
1

С Swift 3.0 вы можете напрямую получить доступ к значению массива через индекс.

var streets = ["Albemarle", "Brandywine", "Chesapeake"]
streets = streets.reversed()
print("Array at index is \(streets[0])")

Это напечатает «Chesapeake»

    
ответ дан Sabby 18.01.2017 в 09:36