Я новичок в Go и очень рад этому. Но на всех языках, с которыми я активно работал: Delphi, C #, C ++, Python - списки очень важны, потому что их можно динамически изменять размер, в отличие от массивов.
В Golang действительно есть list.List
структура, но я вижу очень мало документации по ней - будь то в Go By Example или в трех моих книгах по Go - Summerfield, Chisnal и Balbaert - все они тратят много времени на массивы, срезы и затем переходите к картам. В примерах исходного кода я также практически не использую list.List
.
Также кажется, что, в отличие от Python, Range
List не поддерживается - большой недостаток IMO. Я что-то упускаю?
Срезы, безусловно, хороши, но они все равно должны основываться на массиве с жестко заданным размером. Вот где на помощь приходит List. Есть ли способ создать массив / фрагмент в Go без жестко заданного размера массива? Почему список игнорируется?
list
тип Python не реализуется с использованием связанного списка: он ведет себя аналогично слайсу Go, иногда требуя расширения копий данных.std::list
почти всегда плохая идея.std::vector
это то, что вы хотите управлять последовательностью элементов. По тем же причинамstd::vector
предпочтительным является также Go slice.std::vector<T>
был включен вlist
категорию, потому что он не требует постоянного значения для инициализации и может быть изменен динамически. Когда я задал вопрос, мне было неясно, что Goslice
можно использовать аналогичным образом - все, что я читал в то время, объясняло, что срез был «представлением массива», и, как и в большинстве других языков, простые ванильные массивы в Go должны быть объявлены с постоянным размером. (Но спасибо за внимание.)Ответы:
Практически всегда, когда вы думаете о списке, используйте в Go срез. Размер срезов динамически изменяется. В их основе лежит непрерывный участок памяти, размер которого может изменяться.
Они очень гибкие, как вы увидите, если прочитаете вики-страницу SliceTricks .
Вот отрывок: -
Обновление : вот ссылка на сообщение в блоге о срезах от самой команды go, которое хорошо объясняет взаимосвязь между срезами и массивами, а также внутреннее устройство срезов.
источник
Я задал этот вопрос несколько месяцев назад, когда впервые начал изучать Go. С тех пор я каждый день читал о Go и программировании на Go.
Поскольку я не получил четкого ответа на этот вопрос (хотя я принял один ответ), я сейчас отвечу на него сам, основываясь на том, что я узнал, поскольку я его задал:
Да. Для срезов не требуется жестко запрограммированный массив
slice
из:var sl []int = make([]int,len,cap)
Этот код выделяет срез
sl
размеромlen
с емкостьюcap
-len
иcap
представляет собой переменные, которые могут быть назначены во время выполнения.Похоже, что основными причинами
list.List
, по которым в Go уделяется мало внимания, являются:Как было объяснено в ответе @Nick Craig-Wood, со списками практически ничего нельзя сделать, что нельзя сделать с помощью срезов, часто более эффективно и с более чистым и элегантным синтаксисом. Например, конструкция диапазона:
for i:=range sl { sl[i]=i }
не может использоваться со списком - требуется стиль C для цикла. И во многих случаях синтаксис стиля коллекции C ++ должен использоваться со списками:
push_back
и т. Д.Возможно, что еще более важно,
list.List
не является строго типизированным - он очень похож на списки и словари Python, которые позволяют смешивать различные типы вместе в коллекции. Кажется, это противоречит подходу Go к вещам. Go - это язык с очень строгой типизацией - например, неявные преобразования типов никогда не допускаются в Go, даже upCast изint
вint64
должен быть явным. Но все методы для list.List принимают пустые интерфейсы - все идет.Одна из причин, по которой я отказался от Python и перешел на Go, - это слабость такого рода в системе типов Python, хотя Python утверждает, что он «строго типизирован» (IMO это не так). Go
list.List
кажется чем-то вроде «дворняги», порожденным C ++vector<T>
и PythonList()
, и, возможно, немного неуместен в самом Go.Меня не удивит, если в какой-то момент в не столь отдаленном будущем мы обнаружим list.List, который устарел в Go, хотя, возможно, он останется, чтобы приспособиться к тем редким ситуациям, когда даже с использованием хороших методов проектирования проблема может быть решена лучше всего. с коллекцией, содержащей различные типы. Или, возможно, он нужен для того, чтобы предоставить разработчикам семейства C «мост», чтобы они могли освоиться с Go, прежде чем они узнают нюансы срезов, которые уникальны для Go, AFAIK. (В некоторых отношениях срезы кажутся похожими на классы потоков в C ++ или Delphi, но не полностью.)
Несмотря на то, что я пришел из среды Delphi / C ++ / Python, при первом знакомстве с Go я обнаружил,
list.List
что он более знаком, чем фрагменты Go, поскольку я стал более комфортно работать с Go, я вернулся и изменил все свои списки на фрагменты. Я еще ничего не нашелslice
и / илиmap
не разрешаю мне делать то, что мне нужно использоватьlist.List
.источник
Я думаю, это потому, что о них особо нечего сказать, поскольку
container/list
пакет довольно очевиден, если вы усвоили главную идиому Go для работы с общими данными.В Delphi (без дженериков) или в C вы должны хранить указатели или
TObject
s в списке, а затем возвращать их к их реальным типам при получении из списка. В C ++ списки STL являются шаблонами и, следовательно, параметризованы по типу, а в C # (в наши дни) списки являются общими.В Go
container/list
хранит значения типа,interface{}
который является особым типом, способным представлять значения любого другого (реального) типа - путем сохранения пары указателей: один на информацию о типе содержащегося значения и указатель на значение (или значение напрямую, если его размер не превышает размер указателя). Поэтому, когда вы хотите добавить элемент в список, вы просто делаете это, поскольку параметры функции типаinterface{}
принимают значения любого типа. Но когда вы извлекаете значения из списка и что работать с их реальными типами, вам нужно либо ввести их тип, либо переключить их - оба подхода - это просто разные способы сделать, по сути, одно и то же.Вот пример, взятый отсюда :
package main import ("fmt" ; "container/list") func main() { var x list.List x.PushBack(1) x.PushBack(2) x.PushBack(3) for e := x.Front(); e != nil; e=e.Next() { fmt.Println(e.Value.(int)) } }
Здесь мы получаем значение элемента, используя,
e.Value()
а затем утверждаем его какint
тип исходного вставленного значения.Вы можете прочитать об утверждениях типов и переключателях типов в «Effective Go» или в любой другой вводной книге. В
container/list
документации пакета перечислены все поддерживаемые списки методов.источник
range
это встроенный язык, который применим только к встроенным типам (массивы, срезы, строки и карты), потому что каждый «вызов» илиrange
фактически будет создавать другой машинный код для обхода контейнера, в котором он применительно к.container/list
это список с двойной связью. Это означает, что индексация - этоO(N)
операция (вы должны начинать с головы и перемещаться по каждому элементу к хвосту, считая), и одна из краеугольных парадигм дизайна Go не имеет скрытых затрат на производительность; с другой стороны, небольшая дополнительная нагрузка на программиста (реализация функции индексации для двусвязного списка - это простая задача из 10 строк). Таким образом, контейнер реализует только «канонические» операции, разумные для своего вида.TList
и ему подобных используют динамический массив внизу, поэтому расширение такого списка недешево, а индексирование - дешево. Итак, хотя «списки» Delphi выглядят как абстрактные списки, на самом деле они представляют собой массивы - для чего вы бы использовали срезы в Go. Что я хочу подчеркнуть, так это то, что Go стремится прояснить ситуацию без нагромождения «красивых абстракций», «скрывающих» детали от программиста. Подход Go больше похож на C, где вы явно знаете, как расположены ваши данные и как вы к ним обращаетесь.Обратите внимание, что фрагменты Go можно расширить с помощью
append()
встроенной функции. Хотя иногда для этого потребуется сделать копию резервного массива, это произойдет не каждый раз, поскольку Go будет завышать размер нового массива, придавая ему большую емкость, чем указанная длина. Это означает, что последующая операция добавления может быть завершена без дополнительной копии данных.Хотя в результате получается больше копий данных, чем в эквивалентном коде, реализованном со связанными списками, вы устраняете необходимость выделять элементы в списке по отдельности и обновлять
Next
указатели. Для многих применений реализация на основе массива обеспечивает лучшую или достаточно хорошую производительность, так что это то, что подчеркивается в языке. Интересно, что стандартныйlist
тип Python также поддерживается массивом и имеет аналогичные характеристики производительности при добавлении значений.Тем не менее, есть случаи, когда связанные списки являются лучшим выбором (например, когда вам нужно вставить или удалить элементы из начала / середины длинного списка), и поэтому предоставляется стандартная реализация библиотеки. Я предполагаю, что они не добавляли никаких специальных языковых функций для работы с ними, потому что эти случаи менее распространены, чем те, где используются срезы.
источник
append()
я объяснил, его можно динамически расширить с помощью операции (которая иногда включает в себя копию данных).Если фрагмент не обновляется слишком часто (удаление, добавление элементов в случайных местах), смежность фрагментов в памяти обеспечит отличное соотношение попаданий в кэш по сравнению со связанными списками.
Выступление Скотта Мейера о важности кеширования .. https://www.youtube.com/watch?v=WDIkqP4JbkE
источник
list.List
реализован в виде двусвязного списка. Списки на основе массивов (векторы в C ++ или фрагменты в golang) являются лучшим выбором, чем связанные списки в большинстве случаев, если вы не часто вставляете их в середину списка. Амортизированная временная сложность для добавления составляет O (1) как для списка массивов, так и для связанного списка, даже если список массивов должен увеличивать емкость и копировать существующие значения. Списки массивов имеют более быстрый произвольный доступ, меньший объем памяти и, что более важно, удобны для сборщика мусора из-за отсутствия указателей внутри структуры данных.источник
От: https://groups.google.com/forum/#!msg/golang-nuts/mPKCoYNwsoU/tLefhE7tQjMJ
Поэтому
используйте срез
1. Если порядок элементов в списке не важен, и вам нужно удалить, просто
используйте List, замените элемент, который нужно удалить, последним элементом и повторно срежьте срез на (длина-1)
2. когда элементов больше ( что бы то ни было еще)
There are ways to mitigate the deletion problem -- e.g. the swap trick you mentioned or just marking the elements as logically deleted. But it's impossible to mitigate the problem of slowness of walking linked lists.
Поэтому
используйте срез
1. Если вам нужна скорость обхода
источник