#ifndef SOURCE_DIFF_LCS_H_
#define SOURCE_DIFF_LCS_H_
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <stack>
#include <vector>
namespace spvtools {
namespace diff {
using DiffMatch = std::vector<bool>;
template <typename Sequence>
class LongestCommonSubsequence {
public:
LongestCommonSubsequence(const Sequence& src, const Sequence& dst)
: src_(src),
dst_(dst),
table_(src.size(), std::vector<DiffMatchEntry>(dst.size())) {}
template <typename T>
uint32_t Get(std::function<bool(T src_elem, T dst_elem)> match,
DiffMatch* src_match_result, DiffMatch* dst_match_result);
private:
struct DiffMatchIndex {
uint32_t src_offset;
uint32_t dst_offset;
};
template <typename T>
void CalculateLCS(std::function<bool(T src_elem, T dst_elem)> match);
void RetrieveMatch(DiffMatch* src_match_result, DiffMatch* dst_match_result);
bool IsInBound(DiffMatchIndex index) {
return index.src_offset < src_.size() && index.dst_offset < dst_.size();
}
bool IsCalculated(DiffMatchIndex index) {
assert(IsInBound(index));
return table_[index.src_offset][index.dst_offset].valid;
}
bool IsCalculatedOrOutOfBound(DiffMatchIndex index) {
return !IsInBound(index) || IsCalculated(index);
}
uint32_t GetMemoizedLength(DiffMatchIndex index) {
if (!IsInBound(index)) {
return 0;
}
assert(IsCalculated(index));
return table_[index.src_offset][index.dst_offset].best_match_length;
}
bool IsMatched(DiffMatchIndex index) {
assert(IsCalculated(index));
return table_[index.src_offset][index.dst_offset].matched;
}
void MarkMatched(DiffMatchIndex index, uint32_t best_match_length,
bool matched) {
assert(IsInBound(index));
DiffMatchEntry& entry = table_[index.src_offset][index.dst_offset];
assert(!entry.valid);
entry.best_match_length = best_match_length & 0x3FFFFFFF;
assert(entry.best_match_length == best_match_length);
entry.matched = matched;
entry.valid = true;
}
const Sequence& src_;
const Sequence& dst_;
struct DiffMatchEntry {
DiffMatchEntry() : best_match_length(0), matched(false), valid(false) {}
uint32_t best_match_length : 30;
uint32_t matched : 1;
uint32_t valid : 1;
};
std::vector<std::vector<DiffMatchEntry>> table_;
};
template <typename Sequence>
template <typename T>
uint32_t LongestCommonSubsequence<Sequence>::Get(
std::function<bool(T src_elem, T dst_elem)> match,
DiffMatch* src_match_result, DiffMatch* dst_match_result) {
CalculateLCS(match);
RetrieveMatch(src_match_result, dst_match_result);
return GetMemoizedLength({0, 0});
}
template <typename Sequence>
template <typename T>
void LongestCommonSubsequence<Sequence>::CalculateLCS(
std::function<bool(T src_elem, T dst_elem)> match) {
if (src_.empty() || dst_.empty()) {
return;
}
std::stack<DiffMatchIndex> to_calculate;
to_calculate.push({0, 0});
while (!to_calculate.empty()) {
DiffMatchIndex current = to_calculate.top();
to_calculate.pop();
assert(IsInBound(current));
if (IsCalculated(current)) {
continue;
}
if (match(src_[current.src_offset], dst_[current.dst_offset])) {
DiffMatchIndex next = {current.src_offset + 1, current.dst_offset + 1};
if (IsCalculatedOrOutOfBound(next)) {
MarkMatched(current, GetMemoizedLength(next) + 1, true);
} else {
to_calculate.push(current);
to_calculate.push(next);
}
continue;
}
DiffMatchIndex next_src = {current.src_offset + 1, current.dst_offset};
DiffMatchIndex next_dst = {current.src_offset, current.dst_offset + 1};
if (IsCalculatedOrOutOfBound(next_src) &&
IsCalculatedOrOutOfBound(next_dst)) {
uint32_t best_match_length =
std::max(GetMemoizedLength(next_src), GetMemoizedLength(next_dst));
MarkMatched(current, best_match_length, false);
continue;
}
to_calculate.push(current);
if (!IsCalculatedOrOutOfBound(next_src)) {
to_calculate.push(next_src);
}
if (!IsCalculatedOrOutOfBound(next_dst)) {
to_calculate.push(next_dst);
}
}
}
template <typename Sequence>
void LongestCommonSubsequence<Sequence>::RetrieveMatch(
DiffMatch* src_match_result, DiffMatch* dst_match_result) {
src_match_result->clear();
dst_match_result->clear();
src_match_result->resize(src_.size(), false);
dst_match_result->resize(dst_.size(), false);
DiffMatchIndex current = {0, 0};
while (IsInBound(current)) {
if (IsMatched(current)) {
(*src_match_result)[current.src_offset++] = true;
(*dst_match_result)[current.dst_offset++] = true;
continue;
}
if (GetMemoizedLength({current.src_offset + 1, current.dst_offset}) >=
GetMemoizedLength({current.src_offset, current.dst_offset + 1})) {
++current.src_offset;
} else {
++current.dst_offset;
}
}
}
}
}
#endif