Cách ngắn gọn để tạo một lát 2D trong cờ vây là gì?


103

Tôi đang học cờ vây bằng cách thực hiện chuyến tham quan cờ vây . Một trong những bài tập ở đó yêu cầu tôi tạo một lát 2D gồm dycác hàng và dxcột có chứa uint8. Cách tiếp cận hiện tại của tôi, đang hoạt động, là:

a:= make([][]uint8, dy)       // initialize a slice of dy slices
for i:=0;i<dy;i++ {
    a[i] = make([]uint8, dx)  // initialize a slice of dx unit8 in each of dy slices
}

Tôi nghĩ rằng việc lặp lại từng lát để khởi tạo nó là quá dài dòng. Và nếu lát cắt có nhiều kích thước hơn, mã sẽ trở nên khó sử dụng. Có cách ngắn gọn nào để khởi tạo các lát cắt 2D (hoặc n-chiều) trong Go không?

Câu trả lời:


148

Không có cách nào ngắn gọn hơn, những gì bạn đã làm là cách "đúng"; bởi vì các lát cắt luôn là một chiều nhưng có thể được cấu tạo để xây dựng các đối tượng có chiều cao hơn. Xem câu hỏi này để biết thêm chi tiết: Đi: Cách biểu diễn bộ nhớ của mảng hai chiều .

Một điều bạn có thể đơn giản hóa nó là sử dụng for rangecấu trúc:

a := make([][]uint8, dy)
for i := range a {
    a[i] = make([]uint8, dx)
}

Cũng lưu ý rằng nếu bạn khởi tạo lát cắt của mình bằng một ký tự tổng hợp , bạn sẽ nhận được điều này "miễn phí", ví dụ:

a := [][]uint8{
    {0, 1, 2, 3},
    {4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]

Vâng, điều này có giới hạn của nó là dường như bạn phải liệt kê tất cả các yếu tố; nhưng có một số thủ thuật, cụ thể là bạn không phải liệt kê tất cả các giá trị, chỉ những giá trị không phải là giá trị 0 của kiểu phần tử của lát cắt. Để biết thêm chi tiết về điều này, hãy xem Các mục được khóa trong khởi tạo mảng golang .

Ví dụ: nếu bạn muốn một lát cắt trong đó 10 phần tử đầu tiên là số không, sau đó theo sau 12, nó có thể được tạo như thế này:

b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]

Cũng lưu ý rằng nếu bạn sử dụng mảng thay vì các lát , nó có thể được tạo rất dễ dàng:

c := [5][5]uint8{}
fmt.Println(c)

Đầu ra là:

[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

Trong trường hợp mảng, bạn không phải lặp lại mảng "bên ngoài" và khởi tạo mảng "bên trong", vì mảng không phải là bộ mô tả mà là giá trị. Xem bài đăng trên blog Mảng, lát (và chuỗi): Cơ chế 'nối thêm' để biết thêm chi tiết.

Hãy thử các ví dụ trên Go Playground .


Vì việc sử dụng một mảng đơn giản hóa mã, tôi muốn làm điều đó. Làm cách nào để xác định điều đó trong một cấu trúc? Tôi nhận được cannot use [5][2]string literal (type [5][2]string) as type [][]string in field valuekhi tôi cố gắng gán mảng cho những gì tôi đoán tôi đang nói với Go là một lát cắt.
Eric Lindsey

Tự mình tìm ra và chỉnh sửa câu trả lời để thêm thông tin.
Eric Lindsey

1
@EricLindsey Mặc dù bản chỉnh sửa của bạn tốt nhưng tôi vẫn sẽ từ chối nó vì tôi không muốn khuyến khích việc sử dụng mảng chỉ vì việc khởi tạo dễ dàng hơn. Trong cờ vây, mảng là thứ yếu, các lát cắt là con đường để đi. Để biết chi tiết, hãy xem Cách nhanh nhất để nối mảng này với mảng khác trong Go là gì? Mảng cũng có vị trí của chúng, để biết chi tiết, hãy xem Tại sao có mảng trong Go?
icza

công bằng, nhưng tôi tin rằng thông tin vẫn có giá trị. Những gì tôi đang cố gắng giải thích với bản chỉnh sửa của mình là nếu bạn cần sự linh hoạt của các kích thước khác nhau giữa các đối tượng thì các lát cắt là cách để thực hiện. Mặt khác, nếu thông tin của bạn được cấu trúc chặt chẽ và sẽ luôn giống nhau, thì các mảng không chỉ dễ khởi tạo hơn mà còn hiệu quả hơn. Làm cách nào để cải thiện bản chỉnh sửa?
Eric Lindsey

@EricLindsey Tôi thấy bạn đã thực hiện một chỉnh sửa khác đã bị người khác từ chối. Trong bản chỉnh sửa của mình, bạn đã nói sử dụng mảng để có quyền truy cập phần tử nhanh hơn. Lưu ý rằng Go tối ưu hóa nhiều thứ, và điều này có thể không đúng, các lát cắt có thể nhanh như nhau. Để biết chi tiết, hãy xem Array vs Slice: tốc độ truy cập .
icza

12

Có hai cách để sử dụng các lát cắt để tạo ma trận. Chúng ta hãy xem xét sự khác biệt giữa chúng.

Phương pháp đầu tiên:

matrix := make([][]int, n)
for i := 0; i < n; i++ {
    matrix[i] = make([]int, m)
}

Phương pháp thứ hai:

matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
    matrix[i] = rows[i*m : (i+1)*m]
}

Liên quan đến phương pháp đầu tiên, việc thực hiện các makecuộc gọi liên tiếp không đảm bảo rằng bạn sẽ kết thúc với một ma trận liền kề, vì vậy bạn có thể có ma trận bị chia trong bộ nhớ. Hãy nghĩ về một ví dụ với hai thói quen Go có thể gây ra điều này:

  1. Quy trình # 0 chạy make([][]int, n)để lấy bộ nhớ được cấp phát matrix, nhận một phần bộ nhớ từ 0x000 đến 0x07F.
  2. Sau đó, nó bắt đầu vòng lặp và thực hiện hàng đầu tiên make([]int, m), nhận từ 0x080 đến 0x0FF.
  3. Trong lần lặp thứ hai, nó được bộ lập lịch ưu tiên.
  4. Bộ lập lịch đưa bộ xử lý về quy trình số 1 và nó bắt đầu chạy. Cái này cũng sử dụng make(cho các mục đích riêng của nó) và nhận từ 0x100 đến 0x17F (ngay bên cạnh hàng đầu tiên của quy trình # 0).
  5. Sau một thời gian, nó được ưu tiên và quy trình # 0 bắt đầu chạy lại.
  6. Nó thực hiện make([]int, m)tương ứng với lần lặp vòng lặp thứ hai và nhận từ 0x180 đến 0x1FF cho hàng thứ hai. Tại thời điểm này, chúng tôi đã có hai hàng được chia.

Với phương pháp thứ hai, quy trình thực hiện make([]int, n*m)để nhận được tất cả ma trận được phân bổ trong một lát duy nhất, đảm bảo tính liền kề. Sau đó, một vòng lặp là cần thiết để cập nhật con trỏ ma trận thành các chuỗi con tương ứng với mỗi hàng.

Bạn có thể chơi với mã hiển thị ở trên trong Sân chơi cờ vây để xem sự khác biệt về bộ nhớ được gán bằng cách sử dụng cả hai phương pháp. Lưu ý rằng tôi runtime.Gosched()chỉ sử dụng với mục đích cung cấp năng suất cho bộ xử lý và buộc bộ lập lịch chuyển sang một quy trình khác.

Cái nào để sử dụng? Hãy tưởng tượng trường hợp xấu nhất với phương pháp đầu tiên, tức là mỗi hàng không nằm trong bộ nhớ tiếp theo đến hàng khác. Sau đó, nếu chương trình của bạn lặp lại qua các phần tử ma trận (để đọc hoặc ghi chúng), có thể sẽ có nhiều lần bỏ lỡ bộ nhớ cache hơn (do đó độ trễ cao hơn) so với phương pháp thứ hai vì dữ liệu cục bộ kém hơn. Mặt khác, với phương pháp thứ hai, có thể không thể lấy được một phần bộ nhớ duy nhất được cấp cho ma trận, vì sự phân mảnh bộ nhớ (các phần trải rộng khắp bộ nhớ), mặc dù về mặt lý thuyết có thể có đủ bộ nhớ trống cho nó. .

Do đó, trừ khi có nhiều phân mảnh bộ nhớ và ma trận được cấp phát đủ lớn, bạn sẽ luôn muốn sử dụng phương pháp thứ hai để tận dụng vị trí dữ liệu.


2
golang.org/doc/effective_go.html#slices cho thấy một cách thông minh để thực hiện kỹ thuật bộ nhớ liền kề tận dụng cú pháp của slice-native (ví dụ: không cần tính toán rõ ràng các ranh giới của lát cắt bằng các biểu thức như (i + 1) * m)
Magnus

0

Trong các câu trả lời trước, chúng tôi đã không xem xét tình huống khi độ dài ban đầu là không xác định. Đối với trường hợp này, bạn có thể sử dụng logic sau để tạo ma trận

items := []string{"1.0", "1.0.1", "1.0.2", "1.0.2.1.0"}
mx := make([][]string, 0)
for _, item := range items {
    ind := strings.Count(item, ".")
    for len(mx) < ind+1 {
        mx = append(mx, make([]string, 0))
    }
    mx[ind] = append(mx[ind], item)

}

fmt.Println(mx)

https://play.golang.org/p/pHgggHr4nbB


1
Tôi không chắc liệu điều này có nằm trong ranh giới OP của "cách ngắn gọn" hay không, vì anh ấy đã nói "Tôi nghĩ rằng việc lặp lại từng lát để khởi tạo nó là quá dài dòng".
Marcos Canales Mayo
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.