Làm thế nào để chúng tôi giữ cho cấu trúc dữ liệu phụ thuộc được cập nhật?


8

Giả sử bạn có một cây phân tích cú pháp, cây cú pháp trừu tượng và biểu đồ luồng điều khiển, mỗi cây có nguồn gốc logic từ cái trước. Về nguyên tắc, có thể dễ dàng xây dựng từng biểu đồ cho cây phân tích, nhưng làm thế nào chúng ta có thể quản lý sự phức tạp của việc cập nhật biểu đồ khi cây phân tích được sửa đổi? Chúng ta biết chính xác cây đã được sửa đổi như thế nào, nhưng làm thế nào để thay đổi có thể được truyền sang các cây khác theo cách không trở nên khó quản lý?

Đương nhiên, biểu đồ phụ thuộc có thể được cập nhật bằng cách tái cấu trúc lại từ đầu mỗi khi biểu đồ đầu tiên thay đổi, nhưng sau đó sẽ không có cách nào để biết chi tiết về các thay đổi trong biểu đồ phụ thuộc.

Tôi hiện có bốn cách để cố gắng giải quyết vấn đề này, nhưng mỗi cách đều có khó khăn.

  1. Các nút của cây phụ thuộc mỗi nút quan sát các nút có liên quan của cây gốc, tự cập nhật và người quan sát liệt kê các nút cây ban đầu khi cần thiết. Sự phức tạp về khái niệm của điều này có thể trở nên nan giải.
  2. Mỗi nút của cây ban đầu có một danh sách các nút cây phụ thuộc phụ thuộc vào nó và khi nút thay đổi, nó đặt cờ trên các nút phụ thuộc để đánh dấu chúng là bẩn, bao gồm cả cha mẹ của các nút phụ thuộc suốt đến tận gốc. Sau mỗi thay đổi, chúng tôi chạy một thuật toán giống như thuật toán xây dựng biểu đồ phụ thuộc từ đầu, nhưng nó bỏ qua bất kỳ nút sạch nào và tái tạo lại mỗi nút bẩn, theo dõi xem nút được xây dựng lại có thực sự khác với nút bẩn hay không. Điều này cũng có thể nhận được khó khăn.
  3. Chúng ta có thể biểu thị kết nối logic giữa biểu đồ gốc và biểu đồ phụ thuộc dưới dạng cấu trúc dữ liệu, giống như một danh sách các ràng buộc, có lẽ được thiết kế bằng ngôn ngữ khai báo. Khi biểu đồ ban đầu thay đổi, chúng ta chỉ cần quét danh sách để khám phá những ràng buộc nào bị vi phạm và cách cây phụ thuộc cần thay đổi để sửa lỗi vi phạm, tất cả được mã hóa dưới dạng dữ liệu.
  4. Chúng ta có thể xây dựng lại biểu đồ phụ thuộc từ đầu như thể không có biểu đồ phụ thuộc hiện có, sau đó so sánh biểu đồ hiện có và biểu đồ mới để khám phá xem nó đã thay đổi như thế nào. Tôi chắc chắn đây là cách dễ nhất vì tôi biết có sẵn các thuật toán để phát hiện sự khác biệt, nhưng chúng đều khá tốn kém về mặt tính toán và về nguyên tắc có vẻ không cần thiết nên tôi cố tình tránh tùy chọn này.

Cách đúng đắn để đối phó với các loại vấn đề là gì? Chắc chắn phải có một mẫu thiết kế làm cho toàn bộ điều này gần như dễ dàng. Sẽ thật tốt khi có một giải pháp tốt cho mọi vấn đề của mô tả chung này. Liệu lớp vấn đề này có một tên?


Hãy để tôi giải thích về những rắc rối mà vấn đề này gây ra. Vấn đề này xuất hiện ở nhiều nơi, bất cứ khi nào hai phần của dự án hoạt động trên biểu đồ, với mỗi biểu đồ là một đại diện khác nhau của cùng một thứ thay đổi trong khi phần mềm đang chạy. Nó giống như tạo một bộ chuyển đổi cho một giao diện, nhưng thay vì bọc một đối tượng hoặc một số đối tượng cố định, chúng ta cần bọc toàn bộ biểu đồ có kích thước tùy ý.

Mỗi lần tôi thử điều này, tôi kết thúc với một mớ hỗn độn khó hiểu. Luồng điều khiển của người quan sát có thể khó theo dõi khi nó trở nên phức tạp và thuật toán chuyển đổi một biểu đồ này sang biểu đồ khác thường đủ khó để theo dõi khi nó được trình bày rõ ràng và không trải rộng trên nhiều lớp. Vấn đề là dường như không có cách nào để sử dụng chỉ một thuật toán chuyển đổi biểu đồ đơn giản, đơn giản khi biểu đồ gốc đang thay đổi.

Đương nhiên, chúng ta không thể trực tiếp sử dụng thuật toán chuyển đổi biểu đồ thông thường vì điều đó không thể đáp ứng với các thay đổi theo bất kỳ cách nào ngoài việc bắt đầu từ đầu, vậy các lựa chọn thay thế là gì? Có lẽ thuật toán có thể được viết theo kiểu chuyển tiếp, trong đó mỗi bước của thuật toán được biểu diễn dưới dạng một đối tượng với một phương thức cho từng loại nút trong biểu đồ gốc, giống như một khách truy cập. Sau đó, thuật toán có thể được lắp ráp bằng cách kết hợp nhiều khách truy cập đơn giản khác nhau lại với nhau.


Một ví dụ khác: Giả sử bạn có một GUI được trình bày giống như bạn có thể có trong Java Swing, sử dụng JPanels và trình quản lý bố cục. Bạn có thể đơn giản hóa quy trình đó bằng cách sử dụng JPanels lồng nhau thay cho các trình quản lý bố cục phức tạp, do đó, bạn kết thúc với một cây chứa các thùng chứa khác nhau bao gồm các nút chỉ tồn tại cho mục đích bố trí và nếu không thì vô nghĩa. Bây giờ giả sử rằng cùng một cây được sử dụng để tạo GUI của bạn cũng được sử dụng trong một phần khác của ứng dụng của bạn, nhưng thay vì đặt cây ra đồ họa, nó đang làm việc với một thư viện sẽ tạo ra một cây đại diện trừu tượng như một hệ thống các thư mục. Để sử dụng thư viện này, chúng ta cần có một phiên bản của cây không có các nút bố trí; các nút bố trí cần được làm phẳng thành các nút cha của chúng,


Một cách khác để xem xét nó: Chính khái niệm làm việc với những cây đột biến đã vi phạm Luật Demeter . Sẽ không thực sự vi phạm pháp luật nếu cây là một giá trị như cây phân tích cú pháp và cây cú pháp thông thường, nhưng trong trường hợp đó sẽ không có vấn đề gì vì không cần phải cập nhật. Vì vậy, vấn đề này tồn tại như là kết quả trực tiếp của việc vi phạm Luật Demeter, nhưng làm thế nào để bạn tránh điều đó nói chung khi tên miền của bạn dường như là về thao túng cây hoặc đồ thị?

Mẫu tổng hợp là một công cụ tuyệt vời để biến một biểu đồ thành một đối tượng duy nhất và tuân theo Định luật Demeter. Có thể sử dụng mô hình Hợp nhất để biến một loại cây này thành một loại cây khác một cách hiệu quả không? Bạn có thể tạo một cây phân tích cú pháp hỗn hợp để nó hoạt động như một cây cú pháp trừu tượng và thậm chí là một biểu đồ luồng điều khiển không? Có cách nào để làm điều đó mà không vi phạm nguyên tắc trách nhiệm duy nhất ? Mẫu tổng hợp có xu hướng khiến các lớp tiếp thu mọi trách nhiệm mà chúng chạm vào, nhưng có lẽ nó có thể được kết hợp với mẫu Chiến lược bằng cách nào đó.


1
Có lẽ nhìn vào các thuật toán phân tích cú pháp gia tăng, ví dụ cstheory.stackexchange.com/questions/6852/ gợi
psr

Câu trả lời:


5

Tôi nghĩ rằng các kịch bản của bạn đang thảo luận về các biến thể trên Mẫu Người quan sát . Mỗi nút ban đầu ( chủ đề trên mạng ) có (ít nhất) hai phương thức sau:

  • registerObserver(observer) - thêm một nút phụ thuộc vào danh sách các quan sát viên.
  • notifyObservers()- cuộc gọi x.notify(this)trên mỗi người quan sát

Và mỗi nút phụ thuộc ( người quan sát trực tuyến ) có một notify(original)phương thức. So sánh các kịch bản của bạn:

  1. Các notifyphương pháp ngay lập tức tái xây dựng một cây con phụ thuộc.
  2. Các notifyphương pháp thiết lập một lá cờ, các tính toán lại xảy ra sau mỗi bộ cập nhật.
  3. Các notifyObserversphương pháp là thông minh và chỉ thông báo cho những người quan sát có hạn chế là vô hiệu. Điều này có thể sẽ sử dụng Mẫu khách truy cập , để các nút phụ thuộc có thể đưa ra một phương thức quyết định điều này.
  4. (mô hình này không liên quan đến việc xây dựng lại vũ phu)

Vì ba ý tưởng đầu tiên chỉ là các biến thể của mẫu người quan sát, thiết kế của chúng sẽ có độ phức tạp tương tự (vì thực tế, chúng thực sự được sắp xếp theo mức độ phức tạp tăng dần - tôi nghĩ 1 là cách đơn giản nhất để thực hiện).

Tôi có thể nghĩ về một cải tiến: xây dựng các cây phụ thuộc một cách lười biếng . Mỗi nút phụ thuộc sau đó sẽ có một cờ boolean được đặt thành validhoặc invalid. Mỗi phương thức truy cập sẽ kiểm tra cờ này và, nếu cần, tính toán lại cây con. Sự khác biệt so với №2 là tính toán lại xảy ra khi truy cập, không phải khi thay đổi. Điều này có thể sẽ dẫn đến việc tính toán ít nhất, nhưng có thể dẫn đến những khó khăn đáng kể nếu loại nút sẽ phải thay đổi khi truy cập.


Tôi cũng muốn thử thách sự cần thiết của nhiều cây phụ thuộc. Ví dụ, tôi luôn cấu trúc các trình phân tích cú pháp của mình theo cách chúng ngay lập tức phát ra AST. Thông tin chỉ liên quan trong quá trình xây dựng cây này không phải được lưu trữ trong bất kỳ cấu trúc dữ liệu cố định nào. Tương tự như vậy, bạn cũng có thể chọn các đối tượng của mình theo cách mà AST có một diễn giải dưới dạng biểu đồ luồng điều khiển.

Đối với một ví dụ thực tế, phần trình biên dịch bên trong trình perlthông dịch thực hiện điều này: AST được xây dựng từ dưới lên, trong đó một số nút được gập liên tục. Trong lần chạy thứ hai, các nút được kết nối theo thứ tự thực hiện, trong đó một số nút được bỏ qua bởi tối ưu hóa. Kết quả là phân tích cú pháp rất nhanh (và ít phân bổ), nhưng tối ưu hóa rất hạn chế. Cần lưu ý rằng trong khi thiết kế như vậy là có thể , nó có thể không phải là thứ bạn nên cố gắng: Đó là mộtđánh đổi tính toán vi phạm hoàn thành của đơn Trách nhiệm Nguyên tắc .

Nếu bạn thực sự cần nhiều cây, thì bạn cũng nên xem xét liệu chúng có thực sự phải được xây dựng đồng thời hay không. Trong phần lớn các trường hợp, một cây phân tích không đổi sau khi phân tích cú pháp. Tương tự, AST có thể sẽ không đổi sau khi macro được giải quyết và tối ưu hóa mức AST đã được thực hiện.


Trong cùng một hướng, bạn có thể thử lập trình phản ứng chức năng. Điều đó có thể linh hoạt hơn: lampwww.epfl.ch/~imaier/pub/DeprecatingObserversTR2010.pdf
Jim Barrow

2

Bạn dường như đang nghĩ đến một trường hợp chung gồm 2 biểu đồ, trong đó biểu đồ thứ hai hoàn toàn có thể xuất phát từ biểu đồ thứ nhất và bạn muốn tính lại hiệu quả biểu đồ thứ 2 khi một phần của thay đổi đầu tiên.

Điều này dường như không khác biệt về mặt khái niệm so với vấn đề giảm thiểu tính toán lại chỉ trong biểu đồ đầu tiên, mặc dù tôi cho rằng khi được thực hiện trong một hệ thống cụ thể, chúng có thể là các loại khác nhau trong mỗi biểu đồ.

Đó là khá nhiều tất cả về theo dõi phụ thuộc, cả trong và giữa các biểu đồ. Đối với mỗi nút được thay đổi, hãy cập nhật tất cả các phụ thuộc, đệ quy.

Tất nhiên, trước khi thực hiện bất kỳ cập nhật nào, bạn muốn sắp xếp theo cấu trúc liên kết đồ thị phụ thuộc của bạn. Điều này cho bạn biết nếu bạn có các phụ thuộc vòng tròn tạo ra một làn sóng cập nhật vô hạn và cũng đảm bảo rằng đối với bất kỳ nút nào, bạn sẽ cập nhật tất cả các phụ thuộc của nó trước khi cập nhật nút đó, do đó tránh được tính toán vô nghĩa sẽ phải làm lại sau.

Bạn đặc biệt không phải thể hiện sự phụ thuộc bằng ngôn ngữ khai báo, nhưng bạn có thể, đó là một vấn đề hoàn toàn độc lập.

Đây là một thuật toán chung và trong các trường hợp cụ thể, bạn có thể làm nhiều hơn để tăng tốc nó. Có thể một số công việc bạn đang làm để cập nhật một phụ thuộc cũng hữu ích trong việc cập nhật các phụ thuộc khác và một thuật toán tốt sẽ tận dụng lợi thế đó.

Theo như thuật toán chuyển đổi đồ thị là một mớ hỗn độn không thể nhầm lẫn, giải pháp này có phần cụ thể về ngôn ngữ nhưng cách tiếp cận hướng đối tượng có thể có một số lớp hoàn toàn xử lý việc cập nhật các phụ thuộc nói chung - đại diện cho phụ thuộc, thực hiện sắp xếp theo cấu trúc liên kết và tính toán . Để thực hiện tính toán, họ ủy quyền cho các lớp thực tế của bạn, có thể sử dụng hàm hạng nhất mà chúng được trao khi chúng được tạo, có thể do các lớp chúng phân phối phải thực hiện giao diện (như thường lệ nếu chúng không thể vì Ví dụ, bạn không tạo chúng, bạn có thể sử dụng bộ chuyển đổi). Tôi cho rằng trong một số trường hợp, bạn có thể sử dụng sự phản chiếu để thu thập thông tin biểu đồ từ biểu đồ của các mối quan hệ đối tượng và gọi các phương thức theo cách đó, nếu việc đó dễ dàng hơn để thiết lập và bạn không '


1

Bạn đã đề cập bạn biết chính xác cây đã được sửa đổi như thế nào, bạn sẽ biết khi nào?

Làm thế nào về thử nghiệm với chuỗi HashTrees hoặc Hash ( Merkle Tree ) hoặc nói chung là khái niệm Phát hiện lỗi . Nếu cây rất lớn mà bạn có thể chia, thì biểu đồ đầu tiên thành N / 2 hoặc gốc N và gán băm / tổng cho các vùng đó. Các cây phụ thuộc sẽ duy trì tập N / 2 hoặc vùng N gốc của nó phụ thuộc vào vùng của cây đầu tiên. Khi thay đổi được phát hiện trong cây đầu tiên, cập nhật các nút tương ứng trong cây phụ thuộc bằng cách sử dụng tra cứu đơn giản (vì bạn biết điều gì đã thay đổi và sau đó là hàm băm / tổng kiểm tra cho vùng đó).


3
Tôi hoàn toàn không thể hiểu làm thế nào nó được cho là hoạt động. Vì tôi có cả cây ban đầu và cây được sửa đổi để so sánh trực tiếp, tôi không hiểu cách băm máy tính là hữu ích.
Geo

Ý tưởng từ phát hiện lỗi là phát hiện những gì đã thay đổi và vì mục đích của bạn do đó biết nơi cần thay đổi và từ đó quản lý thay đổi đó (đó là câu hỏi của bạn). Gợi ý ở trên là một thử nghiệm suy nghĩ, nếu cây của bạn đủ đơn giản và có một thuộc tính tầm thường có thể phơi bày "những gì đã thay đổi" thì có lẽ bạn không cần phải tính băm. Cơ chế / phát hiện lỗi "hay còn gọi là" phát hiện thay đổi "có thể giúp bạn quản lý việc truyền bá.
nắng

1

Một đại diện khác của vấn đề - bạn có một số dữ liệu (biểu đồ) và các cách biểu diễn khác nhau của nó (ví dụ: bảng bố trí / chế độ xem dạng cây). Bạn muốn chắc chắn, rằng mỗi đại diện phù hợp với các đại diện khác.

Vì vậy, tại sao bạn không thử và đưa ra hầu hết các đại diện cơ bản và biến các đại diện khác để xem các đại diện cơ bản? Sau đó, đủ để thay đổi cơ bản và quan điểm sẽ vẫn nhất quán.


Ví dụ về bố cục: Đại diện đầu tiên là, giả sử:

panelA(
    panelB(
        panelC(
            widget1
            widget2
        )
        panelD(
            widget3
        )
    )
    widget4
)

Vì vậy, bạn chuyển nó thành biểu diễn "đơn giản hơn", danh sách các bộ dữ liệu sau:

[
    (panelA, panelB, panelC, widget1),
    (panelA, panelB, panelC, widget2),
    (panelA, panelB, panelD, widget3),
    (panelA, widget4),
]

Sau đó, trong khi sử dụng biểu đồ này với Xoay, bạn tạo chế độ xem, lần lượt biểu diễn ở trên thành cây chuyên dụng và khi sử dụng với chế độ xem dạng cây, bạn có chế độ xem chỉ trả về cho bạn danh sách các yếu tố cuối cùng của bộ dữ liệu.

"Đơn giản" hay "cơ bản" nghĩa là gì? Quan trọng nhất - phải dễ dàng chuyển sang bất kỳ chế độ xem nào (để tính toán mỗi chế độ xem rẻ). Ngoài ra, nó phải dễ dàng sửa đổi từ bất kỳ chế độ xem.

Hãy nói rằng, bây giờ chúng tôi muốn sửa đổi cấu trúc này bằng cách sử dụng chế độ xem bố cục. Nó phải dịch cuộc gọi "panelC.parent = panelD" thành "tìm bất kỳ danh sách nào có panelD trong đó, tìm tất cả các danh sách có chứa panelC, thay thế tất cả các thành phần của danh sách đó, đi trước panelC bằng một phần của danh sách đầu tiên trước panelD với nó" .


Như những người khác đã chỉ ra - Người quan sát có thể hữu ích.

Nếu chúng ta đang nói về các biểu đồ phân tích cây / AST / điều khiển luồng, chúng ta không phải thông báo cho bất kỳ chế độ xem nào mà biểu đồ đã thay đổi, bởi vì khi sử dụng, bạn sẽ kiểm tra nó và kiểm tra sẽ tự động chuyển biểu diễn "cơ bản" để xem biểu diễn.

Nếu chúng ta đang nói về Xoay, thay đổi thành một chế độ xem phải được thông báo trong các chế độ xem khác, để những thứ mà người dùng có thể thấy thay đổi.

Cuối cùng - đây là câu hỏi rất cụ thể. Tôi muốn nói rằng giải pháp đầy đủ sẽ khác biệt mạnh mẽ khi bạn sử dụng nó để bố trí và phân tích ngôn ngữ, và việc giải thích hoàn toàn chung chung sẽ trở nên xấu xí và tốn kém như địa ngục.


Tái bút Đại diện ở trên là xấu xí, tạo ra quảng cáo, vv Nó chỉ nhằm mục đích hiển thị khái niệm, không phải là giải pháp thực tế.


Làm thế nào để một người làm điều đó theo một cách không đặc biệt? Tôi không có nghĩa là một giải pháp hoàn toàn chung chung, chỉ là một mô hình, chiến lược hoặc thực tiễn tốt làm cho các loại vấn đề này hơi khó khăn hơn một chút.
Geo

1. Sử dụng mẫu Xem. Trên thực tế, giống như MVS, trong đó V và C là những thứ giống nhau - các khung nhìn cho swing hoặc phân cấp thư mục và mô hình là giải hấp nội bộ 2. Sử dụng mẫu Observer nếu cần (như tôi đã nói - không phải lúc nào cũng cần thiết) 3. Khi thiết kế mô hình / đại diện nội bộ hãy ghi nhớ những hoạt động sẽ được áp dụng. Bạn cần nó đơn giản nhất có thể, điều này sẽ làm cho các khung nhìn đơn giản và thậm chí có thể là nguyên tử. Hãy nhớ rằng: bạn cần đại diện để giúp dễ dàng thực hiện và thay đổi từ viws dễ dàng để giới thiệu
Filip Malczak
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.