Tại sao danh sách không được sử dụng thường xuyên trong cờ vây?


82

Tôi mới chơi cờ vây và khá hào hứng với nó. Tuy nhiên, trong tất cả các ngôn ngữ mà tôi đã làm việc rộng rãi: Delphi, C #, C ++, Python - Danh sách rất quan trọng vì chúng có thể được thay đổi kích thước động, trái ngược với mảng.

Trong Golang, thực sự có một list.Listcấu trúc, nhưng tôi thấy rất ít tài liệu về nó - dù trong Go By Example hay ba cuốn sách cờ vây mà tôi có - Summerfield, Chisnal và Balbaert - tất cả đều dành nhiều thời gian cho các mảng và lát cắt và sau đó chuyển đến bản đồ. Trong các ví dụ về mã souce, tôi cũng thấy ít hoặc không sử dụng list.List.

Có vẻ như, không giống như Python, Rangekhông được hỗ trợ cho List - IMO có nhược điểm lớn. Tui bỏ lỡ điều gì vậy?

Các lát chắc chắn là đẹp, nhưng chúng vẫn cần dựa trên một mảng có kích thước được mã hóa cứng. Đó là nơi Danh sách xuất hiện. Có cách nào để tạo một mảng / lát cắt trong Go mà không có kích thước mảng được mã hóa cứng không? Tại sao Danh sách bị bỏ qua?


9
Lưu ý rằng listkiểu của Python không được triển khai bằng danh sách liên kết: nó hoạt động tương tự như lát Go, đôi khi yêu cầu các bản sao dữ liệu để mở rộng.
James Henstridge

@JamesHenstridge - đã ghi nhận và sửa chữa hợp lệ.
Vector

2
C ++ không sử dụng danh sách một cách rộng rãi. std::listhầu như luôn luôn là một ý tưởng tồi. std::vectorlà những gì bạn muốn quản lý một chuỗi các mục. Vì những lý do tương tự std::vectorđược ưa thích, cờ vây cũng được ưa thích hơn.
deft_code

@deft_code - đã hiểu. Trong câu hỏi của tôi std::vector<T>đã được bao gồm trong listdanh mục vì nó không yêu cầu giá trị không đổi để khởi tạo và có thể được thay đổi kích thước động. Khi tôi đặt câu hỏi, tôi không rõ là Go slicecó thể được sử dụng tương tự hay không - mọi thứ tôi đọc được vào thời điểm đó giải thích rằng một lát cắt là "chế độ xem của một mảng" và giống như trong hầu hết các ngôn ngữ khác, mảng vani đơn giản trong Go cần được khai báo với kích thước không đổi. (Nhưng cảm ơn vì đã quan tâm.)
Vectơ

Câu trả lời:


83

Luôn luôn khi bạn nghĩ đến một danh sách - thay vào đó hãy sử dụng một lát cắt trong Go. Các lát được thay đổi kích thước động. Bên dưới chúng là một phần bộ nhớ liền kề có thể thay đổi kích thước.

Chúng rất linh hoạt như bạn sẽ thấy nếu bạn đọc trang wiki SliceTricks .

Đây là một đoạn trích: -

Sao chép

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

Cắt

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

Xóa bỏ

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

Xóa mà không bảo lưu thứ tự

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

Pop

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

Đẩy

a = append(a, x)

Cập nhật : Đây là một liên kết đến một bài đăng trên blog về các lát cắt từ chính nhóm go, làm rất tốt việc giải thích mối quan hệ giữa các lát cắt và mảng và các lát cắt nội bộ.


1
OK - đây là những gì tôi đang tìm kiếm. Tôi đã có một sự hiểu lầm về các lát cắt. Bạn không cần phải khai báo một mảng để sử dụng một lát cắt. Bạn có thể phân bổ một lát và phân bổ kho dự trữ. Nghe tương tự như các luồng trong Delphi hoặc C ++. Bây giờ tôi hiểu tại sao tất cả những gì hoopla về các lát.
Vector

2
@ComeAndGo, lưu ý rằng somethimes tạo một lát trỏ vào một mảng "tĩnh" là một thành ngữ hữu ích.
kostix

2
@FelikZ, các lát cắt tạo ra "một khung nhìn" vào mảng hỗ trợ của chúng. Thường thì bạn biết trước rằng dữ liệu mà một hàm sẽ hoạt động sẽ có kích thước cố định (hoặc sẽ có kích thước không dài hơn một lượng byte đã biết; điều này khá phổ biến đối với các giao thức mạng). Vì vậy, bạn chỉ có thể khai báo một mảng để chứa dữ liệu này trong chức năng của bạn và sau đó cắt nó như ý muốn - đi qua những lát để gọi chức năng vv
kostix

52

Tôi đã hỏi câu hỏi này vài tháng trước, khi tôi lần đầu tiên bắt đầu tìm hiểu về cờ vây. Kể từ đó, hàng ngày tôi đều đọc về cờ vây và viết mã trong cờ vây.

Bởi vì tôi không nhận được câu trả lời rõ ràng cho câu hỏi này (mặc dù tôi đã chấp nhận một câu trả lời) Bây giờ tôi sẽ tự trả lời nó, dựa trên những gì tôi đã học được, kể từ khi tôi hỏi nó:

Có cách nào để tạo mảng / lát cắt trong Go mà không có kích thước mảng được mã hóa cứng không?

Đúng. Slices không yêu cầu một mảng được mã hóa cứng để slicetừ:

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

Mã này phân bổ lát cắt sl, có kích thước lenvới dung lượng cap- lencaplà các biến có thể được chỉ định trong thời gian chạy.

Tại sao list.Listbị bỏ qua?

Có vẻ như những lý do chính list.Listdường như ít được chú ý trong cờ vây là:

  • Như đã được giải thích trong câu trả lời của @Nick Craig-Wood, hầu như không thể thực hiện được gì với các danh sách không thể thực hiện với các lát cắt, thường hiệu quả hơn và với cú pháp gọn gàng, thanh lịch hơn. Ví dụ, cấu trúc phạm vi:

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

    không thể được sử dụng với danh sách - một kiểu C cho vòng lặp là bắt buộc. Và trong nhiều trường hợp, cú pháp kiểu sưu tập C ++ phải được sử dụng với danh sách: push_backv.v.

  • Có lẽ quan trọng hơn, list.Listnó không được đánh mạnh - nó rất giống với danh sách và từ điển của Python, cho phép trộn nhiều loại với nhau trong bộ sưu tập. Điều này dường như trái ngược với cách tiếp cận mọi thứ của cờ vây. Go là một ngôn ngữ được đánh máy rất mạnh - ví dụ: chuyển đổi kiểu ngầm không bao giờ được phép trong Go, ngay cả upCast từ intđến int64cũng phải rõ ràng. Nhưng tất cả các phương thức cho list.List đều có giao diện trống - mọi thứ đều ổn.

    Một trong những lý do mà tôi từ bỏ Python và chuyển sang Go là vì loại điểm yếu này trong hệ thống kiểu của Python, mặc dù Python tuyên bố là "được gõ mạnh" (IMO thì không). Go list.Listdường như là một loại "lai tạp", sinh ra từ C ++ vector<T>và Python List(), và có lẽ hơi lạc lõng trong chính Go.

Tôi sẽ không ngạc nhiên nếu tại một thời điểm nào đó trong tương lai không xa, chúng tôi tìm thấy danh sách.List không được dùng trong Go, mặc dù có lẽ nó sẽ vẫn còn, để phù hợp với những tình huống hiếm hoi , trong đó, ngay cả khi sử dụng các phương pháp thiết kế tốt, một vấn đề có thể được giải quyết tốt nhất với một bộ sưu tập chứa nhiều loại khác nhau. Hoặc có lẽ nó ở đó để cung cấp một "cầu nối" cho các nhà phát triển họ C để họ cảm thấy thoải mái với cờ vây trước khi họ tìm hiểu các sắc thái của các lát cắt, đặc trưng của cờ vây, AFAIK. (Ở một số khía cạnh, các lát cắt có vẻ tương tự như các lớp luồng trong C ++ hoặc Delphi, nhưng không hoàn toàn.)

Mặc dù đến từ nền tảng Delphi / C ++ / Python, nhưng trong lần đầu tiếp xúc với Go, tôi thấy list.Listquen thuộc hơn các lát của Go, vì tôi đã trở nên thoải mái hơn với Go, tôi đã quay lại và thay đổi tất cả danh sách của mình thành các lát. Tôi chưa tìm thấy bất cứ thứ gì slicevà / hoặc mapkhông cho phép tôi làm, như vậy mà tôi cần sử dụng list.List.


@Alok Go là một ngôn ngữ đa dụng được thiết kế với mục đích lập trình hệ thống. Nó được đánh rất mạnh ... - Họ cũng không biết họ đang nói về cái gì? Việc sử dụng kiểu suy luận không có nghĩa là GoLang không được gõ mạnh. Tôi cũng đã đưa ra một minh họa rõ ràng về điểm này: Các chuyển đổi kiểu ngầm không được phép trong GoLang, ngay cả khi truyền tải lên. (Dấu chấm than không làm cho bạn chính xác hơn. Hãy lưu chúng cho blog dành cho trẻ em.)
Véc tơ vào

@Alok - các mod đã xóa bình luận của bạn, không phải tôi. Đơn giản là nói ai đó "không biết họ đang nói về cái gì!" là vô ích trừ khi bạn đưa ra lời giải thích và bằng chứng. Ngoài ra, đây được coi là một địa điểm chuyên nghiệp, vì vậy chúng tôi có thể bỏ qua các dấu chấm than và cường điệu - lưu chúng cho các blog dành cho trẻ em. Nếu bạn gặp vấn đề, chỉ cần nói "Tôi không hiểu làm thế nào bạn có thể nói GoLang được đánh mạnh như vậy khi chúng ta có A, B và C dường như mâu thuẫn với điều đó." Có lẽ OP sẽ đồng ý, hoặc giải thích tại sao họ nghĩ rằng bạn sai. Đó sẽ là một bình luận nghe có vẻ hữu ích và chuyên nghiệp,
Vector

4
ngôn ngữ được kiểm tra tĩnh, thực thi một số quy tắc trước khi chạy mã. Các ngôn ngữ, chẳng hạn như C cung cấp cho bạn một hệ thống kiểu nguyên thủy: mã của bạn có thể nhập kiểm tra chính xác nhưng bị hỏng khi chạy. Bạn cứ tiếp tục phổ này, bạn nhận được Go, mang lại cho bạn sự đảm bảo tốt hơn C. Tuy nhiên, nó không ở đâu gần với các hệ thống loại bằng các ngôn ngữ như OCaml (đó cũng không phải là điểm cuối của phổ). Nói "Go có lẽ là ngôn ngữ được đánh máy mạnh mẽ nhất hiện có" là hoàn toàn sai. Điều quan trọng là các nhà phát triển phải hiểu các thuộc tính an toàn của các ngôn ngữ khác nhau để họ có thể đưa ra lựa chọn sáng suốt.
Alok

4
Ví dụ cụ thể về những thứ còn thiếu trong cờ vây: việc thiếu các yếu tố chung buộc bạn phải sử dụng các phân loại động. Việc thiếu bảng liệt kê / khả năng kiểm tra tính hoàn chỉnh của công tắc càng có nghĩa là kiểm tra động trong đó các ngôn ngữ khác có thể cung cấp đảm bảo tĩnh.
Alok

@ Alok-1 Tôi) đã nói có lẽ 2) Chúng ta đang nói về các ngôn ngữ được sử dụng khá phổ biến. Ngày nay, Go không mạnh lắm, nhưng Go có 10545 câu hỏi được gắn thẻ, ở đây OCaml có 3.230. 3) Những thiếu sót trong cờ vây mà bạn trích dẫn IMO không liên quan nhiều đến "được đánh máy mạnh", (một thuật ngữ ngớ ngẩn không nhất thiết phải liên quan đến việc biên dịch kiểm tra thời gian). 4) "Nó quan trọng .." - xin lỗi nhưng điều đó không có ý nghĩa - nếu ai đó đang đọc nó, họ có thể đang sử dụng Go rồi. Tôi nghi ngờ có ai đó đang sử dụng câu trả lời này để quyết định xem cờ vây có dành cho họ hay không. IMO bạn nên tìm một cái gì đó quan trọng hơn để được "làm phiền sâu sắc" về ...
Vector

11

Tôi nghĩ đó là bởi vì không có nhiều điều để nói về chúng vì container/listgói này khá dễ hiểu một khi bạn đã hiểu thành ngữ Go chính để làm việc với dữ liệu chung là gì.

Trong Delphi (không có generic) hoặc trong C, bạn sẽ lưu trữ các con trỏ hoặc các con trỏ TObjecttrong danh sách, rồi chuyển chúng trở lại kiểu thực khi lấy từ danh sách. Trong C ++, danh sách STL là các mẫu và do đó được tham số hóa theo kiểu, và trong C # (những ngày này) danh sách là chung.

Trong Go, container/listlưu trữ các giá trị của kiểu interface{}là một kiểu đặc biệt có khả năng đại diện cho các giá trị của bất kỳ kiểu (thực) nào khác — bằng cách lưu trữ một cặp con trỏ: một con trỏ tới thông tin kiểu của giá trị được chứa và một con trỏ tới giá trị (hoặc trực tiếp, nếu kích thước của nó không lớn hơn kích thước của một con trỏ). Vì vậy, khi bạn muốn thêm một phần tử vào danh sách, bạn chỉ cần làm điều đó như các tham số hàm của kiểu interface{}chấp nhận các giá trị giống như bất kỳ kiểu nào. Nhưng khi bạn trích xuất các giá trị từ danh sách và những gì để làm việc với các kiểu thực của chúng, bạn phải nhập chúng hoặc thực hiện chuyển đổi kiểu trên chúng — cả hai cách tiếp cận chỉ là những cách khác nhau để thực hiện cùng một việc.

Đây là một ví dụ được lấy từ đây :

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))
    }
}

Ở đây, chúng tôi lấy giá trị của một phần tử bằng cách sử dụng e.Value()và sau đó nhập-khẳng định nó là intmột kiểu của giá trị được chèn ban đầu.

Bạn có thể đọc các xác nhận kiểu và gõ chuyển trong "Bắt đầu hiệu quả" hoặc bất kỳ cuốn sách giới thiệu nào khác. Các container/listtóm tắt tài liệu của gói tất cả các hỗ trợ phương pháp liệt kê.


Chà, vì danh sách Go không hoạt động giống như các danh sách hoặc vectơ khác: chúng không thể được lập chỉ mục (Danh sách [i]) AFAIK (có thể tôi đang thiếu thứ gì đó ...) và chúng cũng không hỗ trợ Phạm vi, một số giải thích sẽ theo thứ tự. Nhưng cảm ơn về các xác nhận / chuyển đổi loại - đó là điều tôi đã thiếu cho đến bây giờ.
Vector

@ComeAndGo, vâng, chúng không hỗ trợ phạm vi vì rangelà nội trang ngôn ngữ chỉ áp dụng cho các loại nội trang (mảng, lát, chuỗi và bản đồ) vì mỗi "lệnh gọi" hoặc rangetrên thực tế sẽ tạo ra mã máy khác nhau để truyền qua vùng chứa nó áp dụng cho.
kostix

2
@ComeAndGo, cũng như lập chỉ mục ... Từ tài liệu của gói, rõ ràng là container/listcung cấp danh sách liên kết đôi. Điều này có nghĩa là lập chỉ mục là một O(N)hoạt động (bạn phải bắt đầu từ đầu và lướt qua từng phần tử về phía đuôi, đếm) và một trong những mô hình thiết kế nền tảng của Go là không có chi phí hiệu suất ẩn; với một vấn đề khác là đặt thêm một số gánh nặng nhỏ cho lập trình viên (thực hiện chức năng lập chỉ mục cho danh sách liên kết đôi là một điều không cần bàn cãi về 10 dòng) là OK. Vì vậy, vùng chứa chỉ thực hiện các hoạt động "chuẩn" phù hợp với loại của nó.
kostix

@ComeAndGo, lưu ý rằng trong Delphi TListvà những người khác của ilk của nó sử dụng một bên dưới mảng năng động, vì vậy kéo dài một danh sách như vậy không phải là giá rẻ trong khi lập chỉ mục nó giá rẻ. Vì vậy, trong khi các "danh sách" của Delphi trông giống như danh sách trừu tượng nhưng thực tế chúng là các mảng - những gì bạn sẽ sử dụng các lát cắt trong Go. Điều tôi muốn nhấn mạnh là Go cố gắng trình bày mọi thứ rõ ràng mà không chất đống "những điều trừu tượng đẹp đẽ" "che giấu" các chi tiết với lập trình viên. Cách tiếp cận của Go giống với phương pháp C hơn khi bạn biết rõ ràng cách dữ liệu của bạn được sắp xếp và cách bạn truy cập nó.
kostix

3
@ComeAndGo, chính xác là những gì có thể được thực hiện với các lát cờ của Go có cả độ dài và dung lượng.
kostix

6

Lưu ý rằng các lát Go có thể được mở rộng thông qua append()chức năng nội trang. Mặc dù điều này đôi khi sẽ yêu cầu tạo một bản sao của mảng hỗ trợ, nhưng điều này sẽ không xảy ra mọi lúc, vì Go sẽ kích thước quá lớn cho mảng mới khiến nó có dung lượng lớn hơn độ dài được báo cáo. Điều này có nghĩa là thao tác nối tiếp theo có thể được hoàn thành mà không cần bản sao dữ liệu khác.

Mặc dù bạn kết thúc với nhiều bản sao dữ liệu hơn so với mã tương đương được triển khai với danh sách được liên kết, bạn loại bỏ nhu cầu phân bổ các phần tử trong danh sách riêng lẻ và nhu cầu cập nhật Nextcon trỏ. Đối với nhiều trường hợp sử dụng, triển khai dựa trên mảng cung cấp hiệu suất tốt hơn hoặc đủ tốt, vì vậy đó là điều được nhấn mạnh trong ngôn ngữ. Điều thú vị là listkiểu chuẩn của Python cũng được hỗ trợ bởi mảng và có các đặc điểm hiệu suất tương tự khi nối các giá trị.

Điều đó nói rằng, có những trường hợp danh sách được liên kết là lựa chọn tốt hơn (ví dụ: khi bạn cần chèn hoặc xóa các phần tử khỏi đầu / giữa danh sách dài) và đó là lý do tại sao triển khai thư viện tiêu chuẩn được cung cấp. Tôi đoán họ đã không thêm bất kỳ tính năng ngôn ngữ đặc biệt nào để làm việc với chúng vì những trường hợp này ít phổ biến hơn những trường hợp sử dụng các lát cắt.


Tuy nhiên, các lát cắt phải được trở lại bởi một mảng có kích thước được mã hóa cứng, đúng không? Đó là điều tôi không thích.
Vector

3
Kích thước của một lát cắt không được mã hóa cứng trong mã nguồn chương trình, nếu đó là ý của bạn. Nó có thể được mở rộng động thông qua append()hoạt động, như tôi đã giải thích (đôi khi sẽ liên quan đến bản sao dữ liệu).
James Henstridge

4

Trừ khi lát cắt được cập nhật quá thường xuyên (xóa, thêm phần tử ở các vị trí ngẫu nhiên), sự liên tục trong bộ nhớ của các lát cắt sẽ cung cấp tỷ lệ truy cập bộ nhớ cache tuyệt vời so với danh sách được liên kết.

Cuộc nói chuyện của Scott Meyer về tầm quan trọng của bộ nhớ cache .. https://www.youtube.com/watch?v=WDIkqP4JbkE


4

list.Listđược triển khai dưới dạng danh sách được liên kết kép. Danh sách dựa trên mảng (vectơ trong C ++ hoặc các lát trong golang) là lựa chọn tốt hơn danh sách được liên kết trong hầu hết các điều kiện nếu bạn không thường xuyên chèn vào giữa danh sách. Độ phức tạp theo thời gian khấu hao cho phần phụ là O (1) cho cả danh sách mảng và danh sách liên kết mặc dù danh sách mảng phải mở rộng dung lượng và sao chép qua các giá trị hiện có. Danh sách mảng có khả năng truy cập ngẫu nhiên nhanh hơn, dung lượng bộ nhớ nhỏ hơn và quan trọng hơn là thân thiện với trình thu gom rác vì không có con trỏ bên trong cấu trúc dữ liệu.


3

Từ: https://groups.google.com/forum/#!msg/golang-nuts/mPKCoYNwsoU/tLefhE7tQjMJ

Nó phụ thuộc rất nhiều vào số lượng phần tử trong danh sách của bạn,
 liệu một danh sách đúng hay một phần sẽ hiệu quả hơn
 khi bạn cần thực hiện nhiều thao tác xóa ở 'giữa' danh sách.

# 1
Càng nhiều yếu tố, một lát cắt càng trở nên kém hấp dẫn. 

# 2
Khi thứ tự của các phần tử không quan trọng,
 hiệu quả nhất là sử dụng một lát cắt và
 xóa một phần tử bằng cách thay thế nó bằng phần tử cuối cùng trong lát và
 tiếp tục lát cắt để thu nhỏ len 1
 (như được giải thích trong wiki SliceTricks)

Vì vậy, hãy
sử dụng lát cắt
1. Nếu thứ tự của các phần tử trong danh sách không quan trọng và bạn cần xóa, chỉ cần
sử dụng Danh sách hoán đổi phần tử cần xóa với phần tử cuối cùng, & lát lại thành (chiều dài-1)
2. khi các phần tử nhiều hơn ( bất cứ điều gì có nghĩa là)


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.

Vì vậy, hãy
sử dụng lát
1. Nếu bạn cần tốc độ khi di chuyển

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.