Truy tìm kiểu dữ liệu đã biến mất
(hillelwayne.com)- Đồ thị xuất hiện khắp nơi trong phần mềm, như dependency, liên kết web, không gian trạng thái của model checker, khóa ngoại trong DB quan hệ, nhưng các ngôn ngữ lập trình phổ biến hầu như không có kiểu dựng sẵn hoặc hỗ trợ trong thư viện chuẩn
- Lý do đầu tiên khiến việc tạo kiểu đồ thị dựng sẵn trở nên khó là có quá nhiều loại đồ thị như có hướng/vô hướng, đơn/đa đồ thị, siêu đồ thị, và các tính chất cụ thể có thể làm thay đổi lớn lựa chọn thuật toán cũng như hiệu năng
- Mỗi cách biểu diễn như danh sách cạnh, danh sách kề, ma trận kề, cấu trúc tham chiếu có đặc tính bộ nhớ và hiệu năng truy vấn khác nhau, nên khó có một biểu diễn đa dụng đáp ứng mọi trường hợp sử dụng
- Thuật toán đồ thị khó triển khai và thường chạy trên đầu vào lớn, nên như trong các trường hợp Nosey Parker và Gecode, biểu diễn và cách duyệt được tùy biến theo bài toán có thể trở nên quan trọng hơn thư viện đa dụng
- Đồ thị hiếm xuất hiện trong thư viện chuẩn vì các đánh đổi về kiểu, biểu diễn, thuật toán, hiệu năng và gánh nặng bảo trì là rất lớn; thư viện bên thứ ba cũng có thể bị giới hạn hoặc chậm
Đồ thị phổ biến nhưng hỗ trợ từ ngôn ngữ còn thiếu
- Đồ thị gồm các nút và cạnh; nút và cạnh có thể chứa dữ liệu
- Trong kỹ nghệ phần mềm, đồ thị xuất hiện dưới nhiều dạng
- Dependency giữa các package và import giữa các module tạo thành đồ thị có hướng
- Internet là một đồ thị liên kết giữa các trang web
- Model checker khám phá không gian trạng thái của mọi cấu hình khả dĩ; nút là trạng thái và cạnh là chuyển tiếp hợp lệ
- Cơ sở dữ liệu quan hệ có thể được xem là các bản ghi là nút, khóa ngoại là cạnh
- Đồ thị có thể được xem là sự tổng quát hóa của danh sách liên kết, cây nhị phân và bảng băm
- Trong logic nghiệp vụ, đồ thị cũng thường xuất hiện, như quan hệ trích dẫn giữa các bài báo, tuyến đường trong mạng giao thông, kết nối trong mạng xã hội
- Dù đồ thị thường xuyên cần đến, hầu hết ngôn ngữ phổ biến không cung cấp đồ thị như kiểu dựng sẵn, và cũng hiếm khi đưa vào thư viện chuẩn
- Nhiều hệ sinh thái cũng thiếu thư viện đồ thị bên thứ ba đủ vững chắc, nên thường phải tự triển khai
Thiết kế kiểu đồ thị có quá nhiều lựa chọn
- Ngoài đồ thị có hướng và đồ thị vô hướng, đồ thị còn có nhiều biến thể khác
- Đồ thị đơn, trong đó giữa hai nút có tối đa một cạnh, và đa đồ thị, cho phép nhiều cạnh
- Siêu đồ thị, trong đó một cạnh nối 3 nút trở lên
- Ubergraph, trong đó cạnh có thể trỏ tới cạnh khác
- Mỗi biến thể lại kéo theo các quyết định thiết kế bổ sung
- Phải quyết định có gán ID cho cạnh hay chỉ gán cho nút
- Cũng phải quyết định lưu dữ liệu gì trong nút và cạnh
- Có thể cung cấp mọi đồ thị dưới dạng một kiểu đa dụng như “đa siêu ubergraph có hướng” rồi để người dùng tự giới hạn, nhưng ngay lập tức nảy sinh hai vấn đề
- Giao diện thay đổi, chẳng hạn kết quả của một phép toán là một giá trị đơn hay một danh sách
- Nếu không tận dụng được tính chất đặc biệt của đồ thị, hiệu năng thuật toán sẽ kém đi
- Ví dụ, với maximum weight matching, nếu biết đồ thị là đồ thị hai phía thì có thể dùng thuật toán nhanh, nhưng với đồ thị tổng quát thì cần thuật toán đa dụng chậm hơn
- Với một bài toán P, đồ thị G và các thuật toán A·B·C, cũng phát sinh vấn đề dispatch thuật toán: phải chọn thuật toán nào để chạy
- Một thư viện đồ thị hoàn hảo cần hỗ trợ nhiều loại đồ thị, nhưng như vậy thời gian dành để triển khai các thuật toán mà người dùng thực sự muốn sẽ ít đi
- Thuật toán đồ thị có độ khó triển khai cao
- Thuật toán
find_shortest_pathdo người sáng lập Python viết sau đó đã được sửa năm lần - Nicole nói rằng mọi triển khai PageRank mà cô so sánh đều sai
- NetworkX cung cấp khoảng 500 thuật toán đồ thị, riêng mã thuật toán đã gần 60.000 dòng
- Toàn bộ thư viện chuẩn Python có khoảng 300 package, dưới 600.000 dòng
- Thuật toán
- Người quản lý thư viện chuẩn phải quyết định đưa vào kiểu đồ thị nào, xử lý đặc biệt cho topo nào, thuật toán nào, nên gánh nặng bảo trì rất lớn
- Python cũng nổi tiếng với “batteries included”, nhưng với PEP 594 đang đi theo hướng loại bỏ 20 module thư viện chuẩn
Cũng khó chọn một cách biểu diễn đồ thị duy nhất
- Ngay cả khi chỉ xét đồ thị có hướng đơn giản nhất, vẫn có nhiều cách biểu diễn nội bộ
- Danh sách cạnh:
[[a, b], [b, c], [c, a], [c, b]] - Danh sách kề:
[[b], [c], [a, b]] - Ma trận kề:
[0 1 0; 0 0 1; 1 1 0] - Tập hợp các struct tham chiếu lẫn nhau
- Danh sách cạnh:
- Hiệu năng thao tác thay đổi tùy cách biểu diễn
- Nếu biểu diễn đồ thị có 100 nút, 200 cạnh bằng ma trận kề, ma trận 100×100 sẽ chứa 200 số 1 và 9.800 số 0
- Nếu biểu diễn cùng đồ thị bằng danh sách cạnh, chỉ cần 200 cặp nút
- Tùy ngôn ngữ và mức tối ưu hóa, chênh lệch bộ nhớ có thể hơn 20 lần
- Ngược lại, với đồ thị có 100 nút, 8.000 cạnh, khi tìm cạnh giữa nút 0 và 93 thì kết quả khác đi
- Ma trận kề có thể truy vấn O(1) bằng
graph[0][93] - Danh sách cạnh phải duyệt 8.000 cạnh, nên mất thời gian O(|edge|)
- Ma trận kề có thể truy vấn O(1) bằng
- Đồ thị có ít cạnh là đồ thị thưa, còn đồ thị có gần như mọi cạnh là đồ thị dày đặc
- Một chương trình dựng đồ thị từ dữ liệu bên ngoài ban đầu có thể là đồ thị thưa rồi về sau trở thành đồ thị dày đặc, nên không có “lựa chọn luôn tốt” cho biểu diễn nội bộ
- Nếu hỗ trợ dữ liệu nút, dữ liệu cạnh, nhiều loại nút và cạnh, độ phức tạp triển khai còn tăng thêm
- Thư viện bên thứ ba thường chọn một trong hai hướng
- Cung cấp một kiểu đơn giàu tính năng bao quát mọi trường hợp sử dụng, nhưng hy sinh hiệu quả
- Cung cấp các kiểu đồ thị riêng theo từng cách biểu diễn và để người dùng tự quản lý dữ liệu nút/cạnh
Các đánh đổi được NetworkX và Petgraph thể hiện
- NetworkX lưu đồ thị dưới dạng cấu trúc dict của dict của dict để có thể gắn dữ liệu tùy ý vào nút và cạnh
- Nó cung cấp hàm chuyển đổi sang biểu diễn khác, nhưng không cung cấp cách làm việc trực tiếp trên chính biểu diễn đó
- Thư viện đồ thị tiêu biểu của Rust, Petgraph, cung cấp các kiểu theo từng trường hợp sử dụng như
graph,graphmap,matrix_graph - Bradford dùng Petgraph trong công cụ bảo mật Nosey Parker để tìm secret trong toàn bộ lịch sử repository git
- Đồ thị benchmark là CPython, gồm 250.000 commit và 1.300.000 object
- Mỗi nút commit chỉ có vài cạnh, nên đã chọn danh sách kề
- Hỗ trợ nhiều biểu diễn làm tăng chi phí thêm thuật toán
- Nếu viết thuật toán riêng cho từng biểu diễn, gánh nặng bảo trì tăng 3–4 lần
- Nếu viết bằng abstraction đa dụng trên kiểu đa hình, hiệu năng giảm
- Một người được phỏng vấn ước tính thuật toán đồ thị tự viết có thể nhanh hơn thuật toán đa dụng hơn 20 lần
Ràng buộc hiệu năng là vấn đề cốt lõi của thư viện đồ thị
- Trong thuật toán đồ thị có nhiều bài toán NP-complete hoặc khó hơn
- 14 trong 21 bài toán NP-complete canonical của Karp là bài toán đồ thị
- Bài toán đồ thị có thể chạy trên đầu vào rất lớn, nên cách biểu diễn và chi tiết triển khai quyết định liệu có thể chạy được hay không
- Trong Nosey Parker, Bradford phải duyệt object graph để tái dựng snapshot filesystem cho từng commit
- Bốn graph walker của Petgraph không mở rộng phù hợp với trường hợp sử dụng đó
- Anh đã thiết kế ngay một thuật toán duyệt đồ thị “semi-novel”, giảm mức dùng bộ nhớ xuống còn 1/1.000
- Zayenz nêu 15 puzzle như một trường hợp đồ thị quá lớn để xử lý toàn bộ
- Tìm lời giải là chạy A* search trên không gian trạng thái
- Không gian trạng thái có hơn 20 nghìn tỷ trạng thái
- Nếu sinh tất cả nút thì đã là trạng thái thất bại
- Trong dự án nghiên cứu thêm đồ thị vào constraint solver Gecode mà Zayenz tham gia, kiểu đồ thị đa dụng cũng không thể cạnh tranh với lựa chọn biểu diễn phù hợp bài toán
- Cơ sở dữ liệu đồ thị cũng được thiết kế để chạy các thuật toán đồ thị phức tạp, nhưng vấn đề hiệu năng vẫn còn
- Theo Nicole, nếu không giới hạn độ sâu khi duyệt, cuối cùng sẽ thăm toàn bộ đồ thị
- Ngay cả tìm kiếm theo độ sâu kiểu “đi ra ngoài 3 bước và tìm nếu có đường đi” cũng sẽ thăm rất nhiều dữ liệu
- Trong tư vấn hiệu năng truy vấn đồ thị, Nicole chủ yếu thực hiện các migration rời khỏi cơ sở dữ liệu đồ thị
- Ở một dự án, chỉ giữ nguyên một phép tính, còn lại viết lại thành quy trình MapReduce
- Khó hiểu hơn, nhưng thực sự có thể hoàn tất qua một đêm
Vì sao đồ thị hiếm có trong thư viện chuẩn
- Hỗ trợ đồ thị trên diện rộng hiếm gặp vì nhiều yếu tố chồng lên nhau
- Có nhiều loại đồ thị
- Mỗi loại đồ thị có nhiều cách biểu diễn
- Có nhiều loại thuật toán đồ thị
- Hiệu năng thuật toán nhạy với biểu diễn và chi tiết triển khai
- Người ta chạy các thuật toán rất đắt trên các đồ thị rất lớn
- Thư viện chuẩn của ngôn ngữ phải gánh quá nhiều quyết định thiết kế, đánh đổi và gánh nặng bảo trì
- Lập trình viên cũng có lý do để tránh thư viện đồ thị bên thứ ba
- Thư viện có thể quá hạn chế
- Thư viện đa dụng có thể không đáp ứng yêu cầu hiệu năng
- Đồ thị hữu ích cho phân tích hệ thống, nhưng ở giai đoạn triển khai, thường cần tự kiểm soát biểu diễn dữ liệu và lựa chọn thuật toán
Phụ lục: Các ngôn ngữ và công cụ liên quan cung cấp kiểu đồ thị
- Ngôn ngữ truy vấn đồ thị (GQL) đóng vai trò tương tự SQL trong cơ sở dữ liệu đồ thị
- GraphQL không phải ngôn ngữ truy vấn đồ thị; tên gọi bắt nguồn từ mối liên hệ với Facebook Graph Search
- Khác biệt chính giữa GQL và SQL là quan hệ, tức “join”, là thực thể hạng nhất
- Trong dataset phim và con người, SQL triển khai các quan hệ “diễn xuất”, “đạo diễn”, “sản xuất” dưới dạng các bảng many-to-many riêng
- Trong SPARQL, quan hệ là cạnh, nên có thể dễ dàng truy vấn “những người đảm nhận bất kỳ vai trò nào trong phim Y và vai trò của họ”
- GQL cũng có thể hỗ trợ thao tác cạnh như đảo cạnh, hợp thành và bao đóng bắc cầu
- SPARQL không cung cấp độ dài đường đi hay tính toán trong đường đi, chẳng hạn thu thập chuỗi phim nối hai diễn viên
- GQL hỗ trợ những điều này sẽ trở nên phức tạp hơn nhiều
- Ngôn ngữ đặc tả hình thức Alloy có các primitive duyệt đồ thị hữu ích cho kiểu dữ liệu relation, nên xử lý biểu diễn đồ thị dễ hơn so với ngôn ngữ lập trình thông thường
- Tuy nhiên các primitive này dựa trên cạnh có nhãn và có thể không phù hợp với các biểu diễn đồ thị khác
- Python đã thêm graphlib vào năm 2020
- Không có phương thức nào ngoài
TopologicalSorter - Đồ thị chỉ được nhận dưới dạng dict các nút
- Biểu diễn đồ thị
a -> bbằng dict hướng ngược như{b: [a]}
- Không có phương thức nào ngoài
- Tính đến năm 2023, nội bộ CPython không dùng
graphlib- Trên GitHub, các file tham chiếu
graphlibchưa tới 900 zoneinfođược thêm cùng năm xuất hiện trong hơn 6.000 file- Biểu thức
def topological_sort(xuất hiện trong 4.000 file - Các topological sort tự triển khai thường dùng biểu diễn đồ thị khác với graphlib, nên khó chuyển đổi
- Trên GitHub, các file tham chiếu
- Các trường hợp khác có kiểu đồ thị trong thư viện chuẩn gồm Erlang và SWI-Prolog
- Cũng có những ngôn ngữ lập trình trong đó “mọi thứ đều là đồ thị”
- Các ngôn ngữ phần mềm toán học như Mathematica, MATLAB, Maple cũng có thư viện đồ thị dưới một hình thức nào đó
- Bản cập nhật ngày 18/3/2024 đã tập hợp một số bình luận về bài viết ở trang riêng
1 bình luận
Ý kiến trên Hacker News
Graphviz có thư viện đồ thị nền tảng riêng mà các dự án khác không dùng, và nó có cả ưu lẫn nhược điểm
Dựa trên trải nghiệm đó, chúng tôi cũng đã trải qua một dạng hội chứng hệ thống thứ hai điển hình. Chúng tôi muốn tạo ra một thư viện đồ thị dạng mô-đun, an toàn kiểu và hiệu quả, nhưng rốt cuộc có lẽ đó chỉ là một biến thể của “tốt, nhanh, rẻ — chỉ được chọn hai”
Mô-đun nghĩa là chúng tôi muốn có thể phát triển và biên dịch độc lập một tập hợp các thư viện thuật toán đồ thị; còn an toàn kiểu nghĩa là muốn bắt lỗi lập trình ở thời điểm biên dịch, hoặc muộn nhất là lúc liên kết, thay vì các lỗi runtime kiểu “nút không có thuộc tính color”
Hiệu quả nghĩa là chi phí truy cập thuộc tính đồ thị phải rẻ như truy cập trường trong struct C; chúng tôi không muốn phải mang theo các bảng băm bên ngoài hay làm nhiều phép chuyển đổi chuỗi
Các mục tiêu này có đáng trả giá hay có hợp lý không thì còn có thể tranh luận, nhưng khi đó đó là điều chúng tôi muốn. Trong phòng thí nghiệm có những người sáng tạo C++ nổi tiếng, và chúng tôi cũng từng định cho C++ thêm một cơ hội
Gordon Woodhull, ban đầu là thực tập sinh rồi tiếp tục làm việc cùng chúng tôi, là một lập trình viên xuất sắc và đã viết một bản hiện thực thư viện đồ thị như vậy bằng C++ template. Mã nguồn cũng được đăng tại https://www.dynagraph.org/
Những người còn lại không chắc liệu cuối cùng mình có thể hiểu được mã đó hoạt động ra sao hay không, nên đã review code cùng các nhà phát minh C++ nổi tiếng; sau rất nhiều màn hình mã và sự im lặng, kết luận là “có lẽ nó sẽ chạy”. Khi đó chúng tôi đã biết có thể mình đã vượt qua vách đá của độ phức tạp
Lỗi template ở thời điểm biên dịch khiến một lỗi lấp đầy cả màn hình và tuôn ra những chi tiết mà có lẽ chỉ nhà phát minh C++ mới yêu nổi. Lỗi là ở chúng tôi, còn Gordon vẫn tiếp tục đẩy tới và làm cho cả layout đồ thị động chạy được trong Microsoft OLE
Nhìn lại thì đó giống như Project Xanadu của riêng chúng tôi, và trong lúc chúng tôi lạc lối ở đó, những thứ như Gephi (Java), NetworkX, NetworKit (Python) đã xuất hiện. John Ellson, một kỹ sư phần mềm xuất sắc từng viết một phần Graphviz, đã hồi sinh công việc dòng chính
Từ góc nhìn của người đã làm nhiều việc với đồ thị, tôi đã được hỏi vô số lần: “Tại sao ngôn ngữ lập trình không có kiểu dữ liệu đồ thị tích hợp?”
Giờ tôi mừng vì không chỉ phải nói “làm cho tốt thật sự rất khó” rồi bảo họ tin mình, mà còn có thể chỉ tới một phân tích sâu hơn như bài này
Những gì đa số ngôn ngữ cung cấp dưới dạng kiểu cấu trúc chỉ là mảng tĩnh, mảng động và danh sách liên kết. Những thứ như cây tìm kiếm nhị phân hay bảng băm là các trừu tượng ngữ nghĩa che giấu một phần năng lực của cấu trúc nền bên dưới, chứ không phải biểu diễn cấu trúc thuần túy
Phần trong bài nói “có quá nhiều lựa chọn triển khai” cũng đang nói điều tương tự. Rồi tôi thấy Petgraph [0] và lần đầu xem xét nghiêm túc một thư viện đồ thị đa dụng, thấy khá thú vị, nhưng đến nay tôi vẫn triển khai đồ thị ở cấp domain
[0] https://github.com/petgraph/petgraph
https://core.tcl-lang.org/tcllib/doc/trunk/embedded/md/tclli...
Gần như mọi ngôn ngữ đều cung cấp hash map, và dù trong một số tình huống tự triển khai có thể nhanh hơn, bản triển khai mặc định nhìn chung hoạt động tốt. Với đồ thị thì khó làm như vậy, và nếu có thể thì có lẽ cần cung cấp nhiều kiểu đồ thị
Nói thêm, HashMap của Java hơi đặc biệt ở chỗ có thể điều chỉnh load factor, khác với đa số ngôn ngữ khác
Thứ mọi người muốn không hẳn là bản thân kiểu đồ thị, mà gần với các công cụ để duyệt đồ thị hơn
Tôi nghĩ đồ thị là một trừu tượng hơn là một cấu trúc dữ liệu hay kiểu dữ liệu
Về căn bản, thứ cần để định nghĩa đồ thị chỉ là tập đỉnh v \in V và hàm Neighbors(v), và với phần lớn thuật toán đồ thị cơ bản thì thật sự chỉ cần chừng đó là đủ
Phần còn lại là các ràng buộc tùy trường hợp. A->B có hàm ý B->A hay không, tập nút có thể được phân hoạch dưới các ràng buộc nhất định hay không, có màu hay nhãn hay không, v.v.
Khái quát hơn nữa thì có thể đi tới hypergraph; trong trường hợp này chỉ cần tập đỉnh và tập hợp các tập đỉnh. Tùy mối quan tâm mà có thể biểu diễn theo vô số cách, còn đồ thị thông thường chỉ là một trường hợp đặc biệt
Từ góc nhìn cơ sở dữ liệu, cũng có thể xem đây là vấn đề tối ưu hóa truy vấn và lập chỉ mục. Tùy bạn muốn đặt câu hỏi gì cho đồ thị mà cách biểu diễn giúp trả lời tốt hơn sẽ khác nhau. Cũng như không chỉ có một cách biểu diễn trừu tượng “bảng”, “đồ thị” cũng không thể kết thúc bằng một cách duy nhất
Nó ở cùng mức trừu tượng với những con số thuần túy. Có thể nói rằng có các thư viện “số học” hữu ích cũng như các thư viện “mang tính đồ thị” hữu ích, nhưng không thật sự có nhiều thư viện “số” hay thư viện “đồ thị”. Những khái niệm như vậy quá trừu tượng để tạo thành API
Tệp là đỉnh, còn thư mục là các tập đỉnh có thể lồng nhau
Có hai trở ngại cốt lõi
Với các bài toán đồ thị đơn giản và nhỏ, chỉ cần tự viết danh sách kề bằng vector của vector là đã đủ dễ; còn với các bài toán đồ thị phức tạp và khổng lồ, muốn có hiệu năng thì buộc phải tùy biến phần triển khai đồ thị theo chi tiết của bài toán cần giải
Vì vậy khó thấy kiểu hỗ trợ nào từ ngôn ngữ sẽ hữu ích. Trừ khi có một trình biên dịch siêu thông minh có thể phân tích mã và quyết định cái gì là tối ưu — danh sách kề, ma trận, mảng 3 chiều, v.v. — thì rất khó. Có lẽ còn lâu mới thấy loại tối ưu hóa như vậy trong trình biên dịch
Đây là một ví dụ khác về hiện tượng mà Stroustrup đã thấy. Chúng ta chia sẻ mã tốt với những thứ nhỏ như vector và những thứ lớn như hệ điều hành, nhưng lại không chia sẻ tốt các bài toán cỡ trung
Trong một hệ sinh thái ngôn ngữ, API của vector nhỏ, nên có vẻ dễ chia sẻ. Hệ điều hành có API tương đối nhỏ so với độ phức tạp bên trong, và các thư viện tính toán số cũng vậy, nên chúng được chia sẻ tốt
Ngược lại, với những thứ càng muốn tùy biến nhiều hơn như cấu trúc dữ liệu phức tạp, API càng phức tạp và càng khó chia sẻ. Rốt cuộc khả năng chia sẻ dường như phụ thuộc vào diện tích bề mặt của thứ được chia sẻ, tức kích thước tương đối của API
Electric Clojure dùng chính s-expression của Clojure làm cú pháp viết đồ thị, và dùng macro để cụ thể hóa luồng dữ liệu của hệ thống client/server phản ứng
Ở đây use case là giao diện người dùng full-stack, nhưng ý tưởng có thể khái quát hóa. https://github.com/hyperfiddle/electric Tôi là nhà sáng lập
Tôi cho rằng câu trả lời cho “các kiểu đồ thị đã đi đâu hết?” là: DSL để viết đồ thị phải biểu đạt được scope, luồng điều khiển và trừu tượng hóa; khi đó về thực chất nó trở nên đẳng cấu với một ngôn ngữ lập trình đã được giải phóng khỏi mô hình đánh giá. Trong Python và TypeScript, việc nhúng một ngôn ngữ lập trình hoàn chỉnh vào là khá khó
Bài blog “Four problems preventing visual flowchart programming from expressing web applications” cũng đáng tham khảo
https://www.dustingetz.com/#/page/four%20problems%20preventi...
Bài viết này chủ yếu trả lời câu hỏi “vì sao ngôn ngữ lập trình không hỗ trợ thuật toán đồ thị tốt hơn”, và dường như tập trung vào xử lý đồ thị “big data” hơn là hỗ trợ đồ thị nói chung
Nếu nhìn rộng hơn về hỗ trợ đồ thị, còn có những câu hỏi rộng hơn như “vì sao OGM (Object Graph Mapper) không phổ biến như ORM”, “vì sao JSON được dùng rộng rãi còn RDF hay các dạng tuần tự hóa đồ thị cấp thấp khác thì không”
Cuối cùng, tôi cho rằng lý do lịch sử đóng vai trò lớn. RDF ra đời hơi quá sớm, không tiến hóa đúng cách, và đã tích tụ một hệ sinh thái tiêu chuẩn học thuật cùng triển khai rất tệ. Thêm vào đó, đồ thị vốn phức tạp hơn một chút về triển khai và đường cong học tập, nên không mở rộng tốt tới nhiều lập trình viên
Tôi sẽ không đặt quá nhiều trọng lượng vào phần “Graph Querying Language” của bài. Có những đoạn đọc giống khẩu hiệu marketing do một người hâm mộ Neo4J hoặc SPARQL viết mà chưa thực sự xây sản phẩm
Bài nói rằng “khác biệt chính giữa mọi GQL và SQL là join, tức quan hệ là thực thể hạng nhất”, nhưng trong SQL join cũng là thực thể hạng nhất. Thậm chí còn có từ khóa JOIN
Khi đi xuống tầng thấp hơn của ngôn ngữ truy vấn đồ thị và nhìn vào kế hoạch truy vấn, không có nhiều khác biệt đáng kể so với truy vấn dựa trên SQL. Việc chuẩn hóa GQL[0] đang được tiến hành như một phần mở rộng của SQL là bằng chứng cho điều đó
SPARQL dễ dùng khi cần duyệt đường đi chính xác, nhưng nếu cố làm những việc phức tạp hơn một chút kiểu backend webapp, bạn sẽ nhanh chóng gặp các bẫy như join với giá trị chưa được binding, có thể vô tình làm bay toàn bộ tập kết quả
[0]: https://en.wikipedia.org/wiki/Graph_Query_Language
Ví dụ, type class của Haskell không phải hạng nhất, và trong hầu hết ngôn ngữ lập trình, luồng điều khiển cũng không phải hạng nhất
Chỉ là không có lối tắt cú pháp; còn ngôn ngữ truy vấn đồ thị về bản chất tập trung vào việc bổ sung các lối tắt đó
Công cụ vẽ đồ thị cũng khá đáng thất vọng. Với đồ thị nhỏ thì hoạt động tốt, nhưng khi số nút vượt khoảng 500, kết quả trở nên hoàn toàn không thể hiểu nổi hoặc rất khó xem
Nó thiếu khả năng tự động sắp xếp đồ thị thành cấu trúc phân cấp và cung cấp một giao diện thuận tiện để khám phá. Nếu nghĩ đến việc chúng ta quen nhìn mọi thứ xung quanh, ở mức độ nào đó, như một cấu trúc phân cấp, thì có lẽ khi xây dựng một kiểu dữ liệu đồ thị dùng chung cũng phải giải quyết cùng loại vấn đề
Việc này có thể cần được triển khai ở cấp trình biên dịch, để các thuật toán đồ thị dùng chung thích nghi với hệ thống phân cấp cấu trúc được tạo ra. Nếu thêm vào đó một trình chứng minh định lý để xác nhận rằng một đồ thị con nhất định luôn có một cấu trúc nhất định, thì thủ tục tương ứng có thể được tạo tĩnh, còn phần đồ thị cấp cao còn lại có thể được tạo động lúc runtime
Vì vậy, ai giải được bài toán vẽ đồ thị tổng quát có lẽ cũng sẽ có năng lực hoặc trực giác để triển khai điều này
Một thư viện vẽ đồ thị tổng quát kiểu Graphviz, cung cấp nhiều tùy chọn và quyền kiểm soát hơn
https://eclipse.dev/elk/
Thí nghiệm do nhóm phát triển ELK tại Đại học Kiel thực hiện
https://github.com/kieler/KLighD
Wiki của dự án Kieler
https://rtsys.informatik.uni-kiel.de/confluence/display/KIEL...
Thư viện vẽ đồ thị dựa trên ràng buộc
https://www.adaptagrams.org/
Bản triển khai JavaScript
https://ialab.it.monash.edu/webcola/
Một tài liệu thú vị: HOLA: Human-like Orthogonal Network Layout
https://ialab.it.monash.edu/~dwyer/papers/hola2015.pdf
Demo Confluent Graphs giúp các cạnh dễ đọc hơn
https://www.aviz.fr/~bbach/confluentgraphs/
Stress-Minimizing Orthogonal Layout of Data Flow Diagrams with Ports
https://arxiv.org/pdf/1408.4626.pdf
Improved Optimal and Approximate Power Graph Compression for Clearer Visualisation of Dense Graphs
https://arxiv.org/pdf/1311.6996v1.pdf
Ngay cả với hai đồ thị giống nhau về mặt cấu trúc, hai người vẫn có thể render chúng hoàn toàn khác nhau để nhấn mạnh những khía cạnh dữ liệu khác nhau. Điều này cũng tương tự các vấn đề “thuật toán đồ thị tổng quát” và “cấu trúc dữ liệu đồ thị tổng quát”
Đồ thị nằm trên ranh giới giữa mã và dữ liệu. Chẳng hạn, bất kỳ chương trình nào cũng có call graph, nên theo một nghĩa nào đó, “thuật toán đồ thị tổng quát” chính là bản thân phép tính toán
Chỉ cần vượt quá vài chục nút, việc làm cho nó phẳng, hoặc giảm số giao cắt và nhóm tốt các nút liên quan để trông gần như phẳng, thường đã khó
Trên thực tế, việc vẽ đồ thị phải dung hòa những thứ hầu như không hề phân cấp, và rất khó vạch ra một ranh giới toán học chặt chẽ cho việc nên xem đến đâu là phân cấp. Càng đặt ít giả định về cấu trúc đồ thị nền, như tính liên thông, có chu trình hay không, độ thưa, thì vấn đề này càng tệ hơn
Trong thực tế, khi làm UI để tương tác với đồ thị, thường có thể xác định hoặc áp đặt một, hai tầng meta để phân cụm; nhờ vậy có thể giảm tác động của các nút hairball làm hỏng layout, đồng thời giảm số nút để cải thiện hiệu năng render. Có thể dùng fCOSE cho layout, và cũng có bản triển khai trên Cytoscape.js
Tôi cho rằng quan sát cốt lõi rằng “có quá nhiều lựa chọn triển khai” không hoàn toàn đúng
Trên thực tế, một thư viện có thể triển khai mọi cách biểu diễn đồ thị phù hợp, cung cấp thuật toán có hiệu năng tốt nhất cho từng cách biểu diễn, và cung cấp chuyển đổi giữa các cách biểu diễn. Các chuyển đổi này tỷ lệ với số lượng cách biểu diễn, việc triển khai lẫn sử dụng đều đơn giản, nên là gánh nặng khá hợp lý cho cả người bảo trì lẫn người dùng
Thêm nữa, cũng có thể cung cấp chuyển đổi nhập/xuất từ các kiểu dữ liệu và idiom của thư viện chuẩn. Bộ nhớ và chi phí chuyển đổi đều rẻ, và 99% trường hợp sử dụng có khả năng có thể bỏ qua overhead chuyển đổi dữ liệu ở cả RAM lẫn CPU
Tôi cũng nhớ đến câu “sự thật khắc nghiệt khi làm việc ở Google rốt cuộc là bạn chỉ đang chuyển protobuf từ chỗ này sang chỗ khác”
https://news.ycombinator.com/item?id=20132880
Cuối cùng lúc nào cũng phải tự triển khai lại đồ thị. Hiệu năng là quan trọng, và các thư viện đồ thị có sẵn mà tôi từng thấy không tận dụng được tính quy luật của dataset của chúng tôi. Ví dụ, chúng tôi dùng DAG append-only, trong đó gần như mọi nút chỉ có một cạnh trỏ đến mục được thêm gần nhất, nên bên trong có thể dùng mã hóa độ dài chuỗi chạy
Tôi cũng chưa thấy thư viện đồ thị tổng quát nào hỗ trợ các truy vấn cần thiết. Đặc biệt phần lớn là hàm diff đồ thị con
Hơn nữa, triển khai tùy chỉnh không tốn quá nhiều công. Đồ thị dễ triển khai lại hơn B-tree rất nhiều, và một triển khai đơn giản chỉ cần vài chục dòng. Ngay cả thư viện đã tối ưu cao của chúng tôi, gồm cả các thuật toán hỗ trợ, cũng chỉ khoảng vài trăm dòng
Nếu có cách xuất dữ liệu sang định dạng chuẩn thì tiện, nhưng trong trường hợp sử dụng của chúng tôi, kéo thêm một thư viện vào có vẻ sẽ tạo ra nhiều vấn đề hơn là giải quyết
Ứng dụng còn thiếu mà tôi thường nghĩ đến là Excel cho đồ thị
Giống như Excel cho dữ liệu dạng bảng, nó xử lý dữ liệu ở quy mô vừa đủ để nằm trong RAM, tức là cần máy tính nhưng chưa đến mức cần datacenter; triển khai “đủ tốt” nhiều thuật toán và trực quan hóa; và có thể dùng mà không cần kiến thức lập trình
Như bài viết nói, rất nhiều vấn đề trong thực tế là vấn đề đồ thị, vậy tại sao chỉ lập trình viên mới có công cụ để giải chúng
Dù vậy, vẫn tồn tại các cấu trúc dữ liệu và thư viện chuẩn đủ phù hợp với phần lớn trường hợp sử dụng; nếu có yêu cầu đặc biệt “cay” thì có thể làm giải pháp tùy chỉnh
Bài viết nói rằng đồ thị thường quá lớn, nhưng nếu hỏi những người thực sự làm việc với thuật toán đồ thị thì họ dễ có trải nghiệm như vậy. Phần lớn lập trình viên và người dùng có lẽ chỉ xử lý các đồ thị thật sự nhỏ
Tôi không nghĩ người dùng phổ thông nhìn thấy đồ thị trong các vấn đề ngẫu nhiên ngoài đời. Điều tôi học được khi làm ở các công ty lớn là, nếu đủ cố gắng, mọi thứ đều có thể trở thành bảng tính Excel
https://strlen.com/treesheets/ thì gần với Excel cho dữ liệu cây hơn
Ví dụ, có thể mô hình hóa Internet thành đồ thị, nhưng dù điều đó đúng thì vẫn không rõ nó dẫn đến gì. Internet có thể được biểu diễn theo nhiều cách, và không rõ việc biểu diễn bằng đồ thị nhìn chung có hàm ý kỹ thuật hữu ích nào
Có thể nói, với mức độ thuyết phục tương tự, rằng biểu diễn lý tưởng để thu được thông tin hữu ích lại là một hàm mã hóa ma trận hộp đen ánh xạ input tùy ý thành output nhất quán, tức là mạng nơ-ron
Ở những nơi như Google, đó có thể là một ý tưởng trị giá hàng chục tỷ đô la, nhưng toàn bộ Internet không phải là vấn đề đồ thị đối với nhiều người, và biểu diễn nó bằng đồ thị không giải quyết được nhiều thứ
Hiếm có người giải các vấn đề thực tế trên giấy bằng đồ thị. Bảng thì lúc nào cũng được dùng. Đồ thị thì phổ biến, nhưng vấn đề đồ thị thì không
Các bình luận khác cũng nói rằng trực quan hóa đồ thị khó, nhưng giao diện 3D cho nhiều không gian hơn rất nhiều. Khi làn sóng VR bắt đầu, tôi đã nghĩ “Excel của VR sẽ là gì?”, và câu trả lời của Microsoft là “bảng tính 2D lơ lửng trong không gian 3D”. Tôi thấy thật vô lý. Tôi nghĩ đó sẽ là đồ thị
Nếu có ai muốn cùng khám phá, có thể gửi email cho tôi theo dạng tên người dùng của tôi at gmail.com
Kiểu đồ thị đã tồn tại từ khá lâu rồi
Erlang có https://www.erlang.org/doc/man/digraph.html và https://www.erlang.org/doc/man/digraph_utils, và nếu muốn làm các thao tác theo kiểu lý thuyết tập hợp thì cũng có https://www.erlang.org/doc/man/sofs.html
Nội dung là: “Tôi tìm thấy hai ngôn ngữ khác có kiểu đồ thị là Erlang và SWI-Prolog. Vì không rành cả hai nên tôi không thể nói chúng được thêm vào khi nào, nhưng Erlang đã có ít nhất từ trước năm 2008. Tôi đã liên hệ với một người trong ủy ban ngôn ngữ lõi Erlang nhưng không nhận được câu trả lời”
Tôi từng dùng nó để giải quyết phụ thuộc nhằm xác định thứ tự công việc