Thuật toán Diff? [đóng cửa]


164

Tôi đã trông như điên vì một lời giải thích về một thuật toán khác biệt hoạt động và hiệu quả.

Gần nhất tôi nhận được là liên kết này đến RFC 3284 (từ một số bài đăng trên blog của Eric Sink), mô tả bằng thuật ngữ hoàn toàn dễ hiểu định dạng dữ liệu trong đó các kết quả khác nhau được lưu trữ. Tuy nhiên, nó không đề cập đến bất cứ điều gì về cách một chương trình sẽ đạt được những kết quả này trong khi thực hiện một khác biệt.

Tôi đang cố gắng nghiên cứu điều này vì tò mò cá nhân, bởi vì tôi chắc chắn phải có sự đánh đổi khi thực hiện thuật toán diff, đôi khi khá rõ ràng khi bạn nhìn vào diffs và tự hỏi "tại sao chương trình diff lại chọn điều này như một sự thay đổi thay vì đó?"...

Tôi có thể tìm thấy mô tả về một thuật toán hiệu quả mà cuối cùng xuất ra VCDIFF ở đâu?
Nhân tiện, nếu bạn tình cờ tìm thấy một mô tả về thuật toán thực tế được sử dụng bởi DiffMerge của SourceGear, điều đó còn tuyệt vời hơn nữa.

LƯU Ý: chuỗi con phổ biến dài nhất dường như không phải là thuật toán được sử dụng bởi VCDIFF, có vẻ như họ đang làm gì đó thông minh hơn, với định dạng dữ liệu họ sử dụng.


Không có gì trên wikipedia? Bạn có thể cố gắng tìm một triển khai khác trong một lớp lang cao như python, điều đó có thể dễ hiểu hơn so với triển khai C. Python nổi tiếng vì dễ đọc? Có một difflib trong python. Đây là url đến nguồn. Nguồn này có rất nhiều ý kiến ​​về các thuật toán khác nhau. svn.python.org/view/python/trunk/Lib/ trên
bsergean

4
RFC không có nghĩa là để mô tả các thuật toán. Chúng có nghĩa là để mô tả các giao diện (/ giao thức).

2
Trên thực tế, cốt lõi của thuật toán diff, vấn đề chuỗi con phổ biến dài nhất, có thể được tìm thấy trên Wikipedia. Trang này cung cấp tổng quan về thuật toán và mã mẫu mà tôi thấy hữu ích khi tôi cần viết một khác biệt tùy chỉnh: en.wikipedia.org/wiki/Longest_common_sub resultence_probols
Corwin Joy

3
Có lẽ điều này sẽ giúp: paulbutler.org/archives/a-simple-diff-al Thuậtm-in-php Nó chắc chắn là tuyệt vời, và nó rất nhỏ (chỉ có 29 dòng hoàn toàn ; nó có 2 chức năng). Nó tương tự như điều so sánh sửa đổi của Stack Overflow.
Nathan

VCDIFF không dành cho người khác có thể đọc được. Nó sử dụng các hướng dẫn thêm, sao chép và chạy trái ngược với các hướng dẫn xóa và chèn dễ đọc hơn của con người được phát ra bởi hầu hết các thuật toán khác biệt văn bản đơn giản. Đối với VCDIFF, bạn cần một cái gì đó như xdelta algortihm
asgerhallas 20/03/13

Câu trả lời:


175

Thuật toán khác biệt O (ND) và biến thể của nó là một bài báo tuyệt vời và bạn có thể muốn bắt đầu từ đó. Nó bao gồm mã giả và một hình ảnh đẹp của các giao diện đồ thị liên quan đến việc thực hiện tìm khác biệt.

Phần 4 của bài viết giới thiệu một số tinh chỉnh cho thuật toán làm cho nó rất hiệu quả.

Thực hiện thành công điều này sẽ để lại cho bạn một công cụ rất hữu ích trong hộp công cụ của bạn (và có lẽ cũng có một số kinh nghiệm tuyệt vời).

Việc tạo định dạng đầu ra mà bạn cần đôi khi có thể khó khăn, nhưng nếu bạn có hiểu biết về các thuật toán bên trong, thì bạn sẽ có thể xuất bất cứ thứ gì bạn cần. Bạn cũng có thể giới thiệu heuristic để ảnh hưởng đến đầu ra và thực hiện một số sự đánh đổi nhất định.

Đây là một trang bao gồm một chút tài liệu, mã nguồn đầy đủ và các ví dụ về thuật toán diff sử dụng các kỹ thuật trong thuật toán đã nói ở trên.

Các mã nguồn dường như theo các thuật toán cơ bản chặt chẽ và rất dễ dàng để đọc.

Cũng có một chút về việc chuẩn bị đầu vào, mà bạn có thể thấy hữu ích. Có một sự khác biệt lớn về đầu ra khi bạn khác biệt theo ký tự hoặc mã thông báo (từ).

Chúc may mắn!


1
Trong trường hợp liên kết xấu đi, đây là Myers 1986; xem ví dụ: citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.46927 - nó còn bao gồm một liên kết đến diffbài viết Unix của Hunt và McIlroy.
tripleee

34

Tôi sẽ bắt đầu bằng cách xem mã nguồn thực tế cho diff, mà GNU cung cấp .

Để hiểu về cách mã nguồn thực sự hoạt động, các tài liệu trong gói đó tham chiếu các bài báo đã truyền cảm hứng cho nó:

Thuật toán cơ bản được mô tả trong "Thuật toán khác biệt O (ND) và các biến thể của nó", Eugene W. Myers, 'Al Thuậtmica' Vol. 1 số 2, 1986, trang 251-266; và trong "Chương trình so sánh tệp", Webb Miller và Eugene W. Myers, 'Phần mềm - Thực hành và trải nghiệm' Tập. 15 số 11, 1985, trang 1025-1040. Thuật toán được phát hiện độc lập như được mô tả trong "Thuật toán cho khớp chuỗi gần đúng", E. Ukkonen, `Information and Control 'Vol. 64, 1985, trang 100-118.

Đọc các bài báo sau đó xem mã nguồn để thực hiện là quá đủ để hiểu cách thức hoạt động của nó.


80
Nói ngắn gọn, đôi khi, việc tìm ra thuật toán cơ bản từ mã nguồn thực tế (đặc biệt là nếu nó được tối ưu hóa để có hiệu quả) có thể khá phức tạp. Tôi sẽ có thể hiểu những gì chương trình đang làm từng bước, nhưng không chính xác "tại sao", hoặc một tổng quan cấp cao về điều đó ... Ví dụ: Bạn sẽ không bao giờ hiểu cách thức biểu thức chính quy hoạt động (hoặc chúng là gì) bởi nhìn vào việc thực hiện Regexes của Perl. Hoặc nếu bạn có thể làm điều đó, sau đó tôi đội mũ, tôi chắc chắn cần một cái nhìn tổng quan hơn, mức độ cao hơn để tìm hiểu những gì đang xảy ra.
Daniel Magliola

3
Tôi không bao giờ hiểu được phần lớn Perl hoạt động như thế nào :-), nhưng có một liên kết trong các tài liệu gói (xem bản cập nhật) sẽ đưa bạn đến tài liệu mô tả nó (nó là thuật toán diff, không phải Perl).
paxdiablo

32
Đừng đọc mã. Đọc tờ giấy.
Ira Baxter

31

Xem https://github.com/google/diff-match-patch

"Các thư viện Diff Match và Patch cung cấp các thuật toán mạnh mẽ để thực hiện các hoạt động cần thiết để đồng bộ hóa văn bản thuần túy. ... Hiện có sẵn trong Java, JavaScript, C ++, C # và Python"

Đồng thời xem trang Diff của wikipedia.org và - " Bram Cohen: Vấn đề khác đã được giải quyết "


2
Chỉ muốn đề cập rằng thuật toán của Cohen dường như cũng được gọi là Patience Diff. Đó là thuật toán khác (mặc định?) Trong chợ và một thuật toán tùy chọn trong git.
Dale Hagglund

13

Tôi đến đây để tìm kiếm thuật toán diff và sau đó tự thực hiện. Xin lỗi tôi không biết về vcdiff.

Wikipedia : Từ một chuỗi phổ biến dài nhất, chỉ cần một bước nhỏ để có đầu ra giống như khác: nếu một mục không có trong phần tiếp theo nhưng hiện diện trong bản gốc, thì nó phải bị xóa. (Dấu '-', bên dưới.) Nếu nó không có trong phần tiếp theo nhưng xuất hiện trong chuỗi thứ hai, thì nó phải được thêm vào. (Dấu '+'.)

Hoạt hình đẹp của thuật toán LCS ở đây .

Liên kết đến một triển khai ruby ​​LCS nhanh ở đây .

Dưới đây là sự thích ứng ruby ​​chậm và đơn giản của tôi.

def lcs(xs, ys)
  if xs.count > 0 and ys.count > 0
    xe, *xb = xs
    ye, *yb = ys
    if xe == ye
      return [xe] + lcs(xb, yb)
    end
    a = lcs(xs, yb)
    b = lcs(xb, ys)
    return (a.length > b.length) ? a : b
  end
  return []
end

def find_diffs(original, modified, subsequence)
  result = []
  while subsequence.length > 0
    sfirst, *subsequence = subsequence
    while modified.length > 0
      mfirst, *modified = modified
      break if mfirst == sfirst
      result << "+#{mfirst}"
    end
    while original.length > 0
      ofirst, *original = original
      break if ofirst == sfirst
      result << "-#{ofirst}"
    end
    result << "#{sfirst}"
  end
  while modified.length > 0
    mfirst, *modified = modified
    result << "+#{mfirst}"
  end
  while original.length > 0
    ofirst, *original = original
    result << "-#{ofirst}"
  end
  return result
end

def pretty_diff(original, modified)
  subsequence = lcs(modified, original)
  diffs = find_diffs(original, modified, subsequence)

  puts 'ORIG      [' + original.join(', ') + ']'
  puts 'MODIFIED  [' + modified.join(', ') + ']'
  puts 'LCS       [' + subsequence.join(', ') + ']'
  puts 'DIFFS     [' + diffs.join(', ') + ']'
end

pretty_diff("human".scan(/./), "chimpanzee".scan(/./))
# ORIG      [h, u, m, a, n]
# MODIFIED  [c, h, i, m, p, a, n, z, e, e]
# LCS       [h, m, a, n]
# DIFFS     [+c, h, +i, -u, m, +p, a, n, +z, +e, +e]

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.