Go에서 목록이 자주 사용되지 않는 이유는 무엇입니까?


82

저는 Go를 처음 사용하며 매우 흥분됩니다. 그러나 내가 광범위하게 작업 한 모든 언어에서 : Delphi, C #, C ++, Python-목록은 배열이 아닌 동적으로 크기를 조정할 수 있기 때문에 매우 중요합니다.

Golang에는 실제로 list.List구조체가 있지만 Go By Example 에서든 내가 가지고있는 세 가지 Go 책 (Summerfield, Chisnal 및 Balbaert) 이든간에 이에 대한 문서는 거의 없습니다. 모두 배열과 슬라이스에 많은 시간을 소비합니다. 그런 다음지도로 건너 뜁니다. 소스 코드 예제에서는 list.List.

또한 Python과 달리 List- Range큰 단점 IMO에서 지원되지 않는 것으로 보입니다 . 내가 뭔가를 놓치고 있습니까?

슬라이스는 확실히 훌륭하지만 여전히 하드 코딩 된 크기의 배열을 기반으로해야합니다. 그것이 List가 들어오는 곳입니다. Go에서 하드 코딩 된 배열 크기없이 배열 / 슬라이스를 만드는 방법이 있습니까? 목록이 무시되는 이유는 무엇입니까?


9
Python의 list유형은 연결 목록을 사용하여 구현되지 않습니다. Go 슬라이스와 유사하게 작동하며 때때로 데이터 사본을 확장해야합니다.
James Henstridge 2014 년

@JamesHenstridge-정식으로 기록하고 수정했습니다.
Vector

2
C ++는 목록을 광범위하게 사용하지 않습니다. std::list거의 항상 나쁜 생각입니다. std::vector일련의 항목을 관리하려는 것입니다. 같은 이유로 std::vector선호되며 Go 슬라이스도 선호됩니다.
deft_code

@deft_code-이해했습니다. 내 질문 std::vector<T>에는 list초기화에 상수 값이 필요하지 않고 동적으로 크기를 조정할 수 있기 때문에 범주 에 포함되었습니다 . 제가 질문을했을 때 Go가 slice비슷하게 사용될 수 있다는 것이 분명하지 않았습니다 . 그 당시 제가 읽은 모든 내용은 슬라이스가 "배열의보기"라고 설명했으며 대부분의 다른 언어와 마찬가지로 Go의 일반 바닐라 배열이었습니다. 일정한 크기로 선언해야합니다. (하지만 머리를 올려 주셔서 감사합니다.)
Vector

답변:


83

목록을 생각할 때 항상 Go에서 슬라이스를 사용하십시오. 슬라이스의 크기가 동적으로 조정됩니다. 그 밑에는 크기를 변경할 수있는 연속적인 메모리 조각이 있습니다.

SliceTricks 위키 페이지 를 읽으면 보게 될 것이므로 매우 유연 합니다 .

다음은 발췌입니다.

b = make([]T, len(a))
copy(b, a) // or b = append([]T(nil), a...)

절단

a = append(a[:i], a[j:]...)

지우다

a = append(a[:i], a[i+1:]...) // or a = a[:i+copy(a[i:], a[i+1:])]

순서를 유지하지 않고 삭제

a[i], a = a[len(a)-1], a[:len(a)-1]

x, a = a[len(a)-1], a[:len(a)-1]

푸시

a = append(a, x)

업데이트 : 다음은 go 팀 자체의 슬라이스에 대한 모든 블로그 게시물에 대한 링크이며, 슬라이스와 배열 및 슬라이스 내부 간의 관계를 잘 설명합니다.


1
OK-이것이 제가 찾던 것입니다. 슬라이스에 대해 오해가있었습니다. 슬라이스를 사용하기 위해 배열을 선언 할 필요가 없습니다. 슬라이스를 할당하고 백업 저장소를 할당 할 수 있습니다. Delphi 또는 C ++의 스트림과 비슷하게 들립니다. 이제 나는 왜 슬라이스에 대한 모든 소동을 이해합니다.
Vector

2
@ComeAndGo, "정적"배열을 가리키는 슬라이스를 만드는 것은 유용한 관용구입니다.
kostix 2014 년

2
@FelikZ, 슬라이스는 백업 배열에 "보기"를 만듭니다. 종종 함수가 작동 할 데이터의 크기가 고정되어 있다는 것을 미리 알고 있습니다 (또는 알려진 바이트 수보다 크지 않을 것입니다. 이것은 네트워크 프로토콜에서 매우 일반적입니다). 따라서 함수에이 데이터를 저장하는 배열을 선언 한 다음 원하는대로 슬라이스합니다.이 슬라이스를 호출 된 함수 등에 전달합니다.
kostix

52

몇 달 전에 Go를 처음 조사하기 시작했을 때이 질문을했습니다. 그 이후로 매일 Go에 대해 읽고 Go로 코딩하고 있습니다.

이 질문에 대한 명확한 답변을받지 못했기 때문에 (하나의 답변을 수락했지만) 이제 제가 질문 한 이후 배운 내용을 바탕으로 직접 답변하겠습니다.

하드 코딩 된 배열 크기없이 Go에서 배열 / 슬라이스를 만드는 방법이 있습니까?

예. 슬라이스는 하드 코딩 된 배열이 필요하지 않습니다 slice.

var sl []int = make([]int,len,cap)

이 코드 할당 슬라이스 sl크기에 len의 용량 cap- lencap런타임시에 할당 될 수있는 변수이다.

list.List무시됩니까?

list.ListGo에서 거의 주목을받지 못하는 주된 이유 는 다음과 같습니다.

  • @Nick Craig-Wood의 답변에서 설명했듯이 슬라이스로는 수행 할 수없는 목록으로 할 수있는 일이 거의 없습니다. 예를 들어 범위 구성 :

    for i:=range sl {
      sl[i]=i
    }
    

    목록과 함께 사용할 수 없습니다. C 스타일 for 루프가 필요합니다. 그리고 대부분의 경우 C ++ 컬렉션 스타일 구문은 목록과 함께 사용해야합니다 push_back.

  • 아마도 더 중요한 list.List것은 강력한 유형이 아니라는 것입니다. Python의 목록 및 사전과 매우 유사하여 컬렉션에서 다양한 유형을 함께 혼합 할 수 있습니다. 이것은 사물에 대한 Go 접근 방식과 반대되는 것 같습니다. Go는 매우 강력한 형식의 언어입니다. 예를 들어 Go에서 절대 허용되지 않는 암시 적 형식 변환은 upCast from intto int64도 명시 적이어야합니다. 그러나 list.List의 모든 메서드는 빈 인터페이스를 사용합니다.

    내가 Python을 포기하고 Go로 옮긴 이유 중 하나는 Python이 "강력한 형식"이라고 주장하지만 (IMO는 그렇지 않습니다) Python 형식 시스템의 이러한 종류의 약점 때문입니다. Go list.List는 C ++ vector<T>과 Python에서 태어난 일종의 "잡종"으로 보이며 List()Go 자체에서 약간 벗어난 것 같습니다.

그리 멀지 않은 미래의 어느 시점에서 우리가 list.List를 찾는다면 놀라지 않을 것입니다. List.List는 아마도 남아있을 것이지만 좋은 디자인 관행을 사용하더라도 문제를 가장 잘 해결할 수 있는 드문 상황 을 수용하기 위해 남아있을 것입니다. 다양한 유형의 컬렉션이 있습니다. 또는 C 제품군 개발자가 Go, AFAIK에 고유 한 슬라이스의 뉘앙스를 배우기 전에 Go에 익숙해 지도록 "브리지"를 제공 할 수도 있습니다. (일부 측면에서 슬라이스는 C ++ 또는 Delphi의 스트림 클래스와 비슷해 보이지만 완전히는 아닙니다.)

Delphi / C ++ / Python 배경에서 왔지만 Go를 처음 접했을 때 Go에 list.List익숙해 졌기 때문에 Go의 슬라이스보다 더 친숙하다는 것을 알게 되었고 모든 목록을 슬라이스로 변경했습니다. 나는 아직 아무것도 찾지 못했고 slice/ 또는 map내가하도록 허용하지 않았으므로 list.List.


@Alok Go는 시스템 프로그래밍을 염두에두고 설계된 범용 언어입니다. 강력하게 타이핑 된 ...- 그들이 무슨 말을하는지 전혀 몰라요? 유형 추론을 사용한다고해서 GoLang이 강력한 유형 이 아니라는 의미는 아닙니다 . 또한이 점에 대한 명확한 설명을 제공했습니다. GoLang에서는 업 캐스팅시에도 암시 적 형식 변환이 허용되지 않습니다. (느낌표가 더 정확하지 않는 아동 블로그 그들을 저장합니다..)
벡터

@Alok-모드가 내가 아닌 댓글을 삭제했습니다. 단순히 누군가 "그들이 무슨 말을하는지 모른다!" 설명과 증거를 제공하지 않으면 쓸모가 없습니다. 또한 이곳은 전문적인 장소 여야하므로 느낌표와 과장을 생략 할 수 있습니다. 아동용 블로그를 위해 저장하세요. 문제가 있으면 "A, B, C가 모순되는 것처럼 보이는 GoLang이 너무 강력하게 입력되었다고 말할 수 없습니다."라고 말하면됩니다. 아마도 OP는 동의하거나 당신이 틀렸다고 생각하는 이유를 설명 할 것입니다. 즉, 유용하고 전문적인 사운드 코멘트 것
벡터

4
코드가 실행되기 전에 몇 가지 규칙을 적용하는 정적으로 확인 된 언어. C와 같은 언어는 원시 유형 시스템을 제공합니다. 코드는 올바르게 유형 검사를 할 수 있지만 런타임에 폭발 할 수 있습니다. 이 스펙트럼을 계속 진행하면 C보다 더 나은 보장을 제공하는 Go를 얻을 수 있습니다. 그러나 OCaml과 같은 언어의 유형 시스템 (스펙트럼의 끝도 아님)에 가깝지 않습니다. "Go는 아마도 가장 강력한 유형의 언어 일 것입니다."라는 말은 명백히 잘못된 것입니다. 개발자가 정보에 입각 한 선택을 할 수 있도록 여러 언어의 안전 속성을 이해하는 것이 중요합니다.
Alok

4
Go에서 누락 된 것의 구체적인 예 : 제네릭이 없으면 동적 캐스트를 사용해야합니다. 열거 형 / 스위치 완전성을 확인하는 기능이 없다는 것은 다른 언어가 정적 보증을 제공 할 수있는 동적 확인을 의미합니다.
Alok

@ Alok-1 I) 아마도 2) 우리는 상당히 일반적으로 사용되는 언어에 대해 이야기하고 있습니다. Go는 요즘 그다지 강력하지 않지만 Go에는 10545 개의 질문이 태그되어 있고 OCaml에는 3,230 개의 질문이 있습니다. 3) Go의 결함은 IMO가 "강력한 유형"( 컴파일 시간 검사와 반드시 관련이 있는 것은 아닌 모호한 용어)과 관련이 많지 않다고 말합니다 . 4) "중요 .."-죄송하지만 말이 안됩니다. 누군가이 글을 읽고 있다면 이미 Go를 사용하고있을 것입니다. 나는 누군가 가이 대답을 사용하여 Go가 그들에게 적합한 지 결정하는 것을 의심합니다. IMO에 대해 "깊은 귀찮게"해야 할 더 중요한 것을 찾아야합니다 ...
Vector

11

으로 그들에 대해 말을 많이 없기 때문에 그의 생각 container/list패키지 오히려 자명하다 일단 당신이 일반 데이터 작업을위한 주요 이동 관용구 무엇 흡수.

Delphi (제네릭 없음) 또는 C에서는 포인터 또는 TObjects를 목록에 저장 한 다음 목록에서 가져올 때 실제 유형으로 다시 캐스팅합니다. C ++에서 STL 목록은 템플릿이므로 유형별로 매개 변수화되며 C # (현재) 목록은 일반적입니다.

들어가, container/list유형의 저장 값 interface{}(또는 값에 포함 된 값의 타입 정보에 하나, 그리고 포인터 : 포인터들의 쌍을 저장 형식으로 다른 (진짜)의 값을 나타낼 수있는 특수한 형태 인 값이 포인터의 크기보다 크지 않은 경우 직접 값). 따라서 목록에 요소를 추가하고 싶을 때 유형의 함수 매개 변수가 interface{}모든 유형의 값을 수용 하므로 그렇게하면됩니다 . 당신이 그들의 실제 유형의 작업은 목록에서 값 및 무엇을 추출 할 때 당신이 중 하나에이 유형 asert 그들이나 할 형 스위치 에 그들을-두 가지 접근 방식은 본질적으로 같은 일을 단지 다른 방법이 있습니다.

다음은 여기 에서 가져온 예입니다 .

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패키지의 문서 요약 모든 방법 목록을 지원합니다.


글쎄요, Go 목록은 다른 목록이나 벡터처럼 작동하지 않기 때문에 색인을 생성 할 수 없습니다 (List [i]) AFAIK (아마도 뭔가 빠졌을 수도 있습니다 ...) 그리고 Range도 지원하지 않습니다. 순서대로 될 것입니다. 그러나 유형 단언 / 스위치에 감사드립니다.
Vector

@ComeAndGo, 예, range각 "호출"또는 range실제로 컨테이너를 순회하기 위해 다른 기계 코드를 생성 하기 때문에 내장 유형 (배열, 슬라이스, 문자열 및 맵)에만 적용되는 언어 내장 이기 때문에 범위를 지원하지 않습니다. 적용.
kostix 2014 년

2
@ComeAndGo, 인덱싱에 관해서 ... 패키지 문서 container/list에서 이중 링크 목록 을 제공하는 것이 분명 합니다. 즉, 인덱싱은 O(N)작업 (머리에서 시작하여 꼬리쪽으로 각 요소를 횡단해야 함)이며 Go의 초석 설계 패러다임 중 하나는 숨겨진 성능 비용 이 없다는 것입니다. 다른 하나는 프로그래머에게 약간의 추가 부담을주는 것입니다 (이중 링크 목록에 대한 인덱싱 기능을 구현하는 것은 10 줄의 쉬운 작업 임). 따라서 컨테이너는 해당 종류에 적합한 "정규"작업 만 구현합니다.
kostix 2014 년

@ComeAndGo, 델파이주의 TList와 동류의 다른 동적 배열 아래를 사용, 그래서 그것이 색인 동안 같은 목록이 싸지 않다 확장 입니다 저렴. 따라서 Delphi의 "목록"은 추상 목록처럼 보이지만 실제로는 배열입니다. Go에서 슬라이스를 사용하는 것입니다. 제가 강조하고 싶은 것은 Go가 프로그래머의 세부 사항을 "숨겨진" "아름다운 추상화"를 쌓지 않고 명확하게 배치하려고 노력한다는 것입니다. Go의 접근 방식은 데이터가 배치되는 방식과 데이터에 액세스하는 방식을 명시 적으로 알고있는 C와 비슷합니다.
kostix 2014 년

3
@ComeAndGo, 길이와 용량을 모두 가진 Go의 슬라이스로 정확히 무엇을 할 수 있는지.
kostix 2014 년

6

Go 슬라이스는 append()내장 함수 를 통해 확장 할 수 있습니다 . 백업 어레이의 복사본을 만들어야하는 경우도 있지만 Go는 새 어레이의 크기를 초과하여보고 된 길이보다 더 큰 용량을 제공하므로 매번 발생하지 않습니다. 이는 후속 추가 작업을 다른 데이터 복사없이 완료 할 수 있음을 의미합니다.

연결된 목록으로 구현 된 동등한 코드보다 더 많은 데이터 복사본이 생성되지만 목록의 요소를 개별적으로 할당 할 필요와 Next포인터 를 업데이트 할 필요가 없습니다 . 많은 용도에서 배열 기반 구현은 더 좋거나 충분한 성능을 제공하므로 언어에서 강조됩니다. 흥미롭게도 Python의 표준 list유형도 배열 기반이며 값을 추가 할 때 유사한 성능 특성을 가지고 있습니다.

즉, 연결 목록이 더 나은 선택 (예 : 긴 목록의 시작 / 중간에 요소를 삽입하거나 제거해야하는 경우)이 있으며 이것이 표준 라이브러리 구현이 제공되는 이유입니다. 이러한 경우는 슬라이스가 사용되는 경우보다 덜 일반적이기 때문에 특수 언어 기능을 추가하지 않은 것 같습니다.


그래도 슬라이스는 하드 코딩 된 크기의 배열로 되돌아 가야합니다. 맞습니까? 그게 내가 싫어하는 것입니다.
Vector

3
슬라이스의 크기는 프로그램 소스 코드에 하드 코딩되어 있지 않습니다. append()설명했듯이 작업을 통해 동적으로 확장 할 수 있습니다 (때로는 데이터 복사가 포함됨).
James Henstridge 2014 년

4

슬라이스가 너무 자주 업데이트되지 않는 한 (삭제, 임의의 위치에 요소 추가) 슬라이스의 메모리 연속성은 연결된 목록에 비해 우수한 캐시 적중률을 제공합니다.

캐시의 중요성에 대한 Scott Meyer의 이야기 .. https://www.youtube.com/watch?v=WDIkqP4JbkE


4

list.List이중 연결 목록으로 구현됩니다. 배열 기반 목록 (C ++의 벡터 또는 golang의 슬라이스)은 목록 중간에 자주 삽입하지 않는 경우 대부분의 조건에서 연결 목록보다 더 나은 선택입니다. 추가에 대한 상각 된 시간 복잡도는 어레이 목록이 용량을 확장하고 기존 값을 복사해야하지만 어레이 목록과 연결 목록 모두에 대해 O (1)입니다. 배열 목록은 데이터 구조 내부에 포인터가 없기 때문에 더 빠른 임의 액세스, 더 작은 메모리 풋 프린트, 더 중요한 것은 가비지 수집기에 친숙합니다.


3

보낸 사람 : https://groups.google.com/forum/#!msg/golang-nuts/mPKCoYNwsoU/tLefhE7tQjMJ

목록의 요소 수에 따라 크게 달라집니다.
 진정한 목록 또는 슬라이스가 더 효율적인지 여부
 목록의 '중간'에서 많은 삭제를해야 할 때.

#1
요소가 많을수록 슬라이스의 매력이 떨어집니다. 

# 2
요소의 순서가 중요하지 않은 경우
 슬라이스를 사용하는 것이 가장 효율적이며
 슬라이스의 마지막 요소로 대체하여 요소를 삭제하고
 슬라이스를 복제하여 len을 1만큼 축소
 (SliceTricks 위키에 설명 된대로)

따라서
슬라이스
1을 사용 하십시오. 목록의 요소 순서가 중요하지 않고 삭제해야하는
경우 List를 사용하여 마지막 요소로 삭제할 요소를 교체하고 (length-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을 사용합니다 . 순회 속도가 필요한 경우

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.