blob: 27d24be3ce6e33342772daa4cb4d717d27a65657 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <algorithm>
#include <vector>
#include "common/status.h"
#include "core/data_type/data_type_number.h"
#include "core/string_ref.h"
#include "exprs/function/function_totype.h"
#include "exprs/function/simple_function_factory.h"
#include "util/simd/vstring_function.h"
namespace doris {
struct NameLevenshtein {
static constexpr auto name = "levenshtein";
};
template <typename LeftDataType, typename RightDataType>
struct LevenshteinImpl {
using ResultDataType = DataTypeInt32;
using ResultPaddedPODArray = PaddedPODArray<Int32>;
static Status vector_vector(const ColumnString::Chars& ldata,
const ColumnString::Offsets& loffsets,
const ColumnString::Chars& rdata,
const ColumnString::Offsets& roffsets, ResultPaddedPODArray& res) {
DCHECK_EQ(loffsets.size(), roffsets.size());
const size_t size = loffsets.size();
res.resize(size);
std::vector<size_t> left_offsets;
std::vector<size_t> right_offsets;
for (size_t i = 0; i < size; ++i) {
res[i] = levenshtein_distance(string_ref_at(ldata, loffsets, i),
string_ref_at(rdata, roffsets, i), left_offsets,
right_offsets);
}
return Status::OK();
}
static Status vector_scalar(const ColumnString::Chars& ldata,
const ColumnString::Offsets& loffsets, const StringRef& rdata,
ResultPaddedPODArray& res) {
const size_t size = loffsets.size();
res.resize(size);
const auto right = rdata.trim_tail_padding_zero();
const bool right_ascii = simd::VStringFunctions::is_ascii(right);
std::vector<size_t> right_offsets;
simd::VStringFunctions::get_utf8_char_offsets(right, right_offsets);
std::vector<size_t> left_offsets;
for (size_t i = 0; i < size; ++i) {
res[i] = levenshtein_distance_with_right_offsets(string_ref_at(ldata, loffsets, i),
left_offsets, right, right_offsets,
right_ascii);
}
return Status::OK();
}
static Status scalar_vector(const StringRef& ldata, const ColumnString::Chars& rdata,
const ColumnString::Offsets& roffsets, ResultPaddedPODArray& res) {
const size_t size = roffsets.size();
res.resize(size);
const auto left = ldata.trim_tail_padding_zero();
const bool left_ascii = simd::VStringFunctions::is_ascii(left);
std::vector<size_t> left_offsets;
simd::VStringFunctions::get_utf8_char_offsets(left, left_offsets);
std::vector<size_t> right_offsets;
for (size_t i = 0; i < size; ++i) {
res[i] = levenshtein_distance_with_left_offsets(left, left_offsets, left_ascii,
string_ref_at(rdata, roffsets, i),
right_offsets);
}
return Status::OK();
}
private:
static StringRef string_ref_at(const ColumnString::Chars& data,
const ColumnString::Offsets& offsets, size_t i) {
DCHECK_LT(i, offsets.size());
const auto idx = static_cast<ssize_t>(i);
return StringRef(data.data() + offsets[idx - 1], offsets[idx] - offsets[idx - 1])
.trim_tail_padding_zero();
}
static Int32 levenshtein_distance_utf8(const StringRef& left,
const std::vector<size_t>& left_offsets,
const StringRef& right,
const std::vector<size_t>& right_offsets) {
const StringRef* left_ref = &left;
const StringRef* right_ref = &right;
const std::vector<size_t>* left_offsets_ref = &left_offsets;
const std::vector<size_t>* right_offsets_ref = &right_offsets;
if (right_offsets_ref->size() > left_offsets_ref->size()) {
std::swap(left_offsets_ref, right_offsets_ref);
std::swap(left_ref, right_ref);
}
const size_t m = left_offsets_ref->size();
const size_t n = right_offsets_ref->size();
std::vector<Int32> prev(n + 1);
std::vector<Int32> curr(n + 1);
for (size_t j = 0; j <= n; ++j) {
prev[j] = static_cast<Int32>(j);
}
for (size_t i = 1; i <= m; ++i) {
curr[0] = static_cast<Int32>(i);
const size_t left_off = (*left_offsets_ref)[i - 1];
const size_t left_next = i < m ? (*left_offsets_ref)[i] : left_ref->size;
for (size_t j = 1; j <= n; ++j) {
const size_t right_off = (*right_offsets_ref)[j - 1];
const size_t right_next = j < n ? (*right_offsets_ref)[j] : right_ref->size;
const Int32 cost =
simd::VStringFunctions::utf8_char_equal(*left_ref, left_off, left_next,
*right_ref, right_off, right_next)
? 0
: 1;
const Int32 insert_cost = curr[j - 1] + 1;
const Int32 delete_cost = prev[j] + 1;
const Int32 replace_cost = prev[j - 1] + cost;
curr[j] = std::min(std::min(insert_cost, delete_cost), replace_cost);
}
std::swap(prev, curr);
}
return prev[n];
}
static Int32 levenshtein_distance_ascii(const StringRef& left, const StringRef& right) {
const StringRef* left_ref = &left;
const StringRef* right_ref = &right;
size_t m = left.size;
size_t n = right.size;
if (n > m) {
std::swap(left_ref, right_ref);
std::swap(m, n);
}
std::vector<Int32> prev(n + 1);
std::vector<Int32> curr(n + 1);
for (size_t j = 0; j <= n; ++j) {
prev[j] = static_cast<Int32>(j);
}
for (size_t i = 1; i <= m; ++i) {
curr[0] = static_cast<Int32>(i);
const char left_char = left_ref->data[i - 1];
for (size_t j = 1; j <= n; ++j) {
const Int32 cost = left_char == right_ref->data[j - 1] ? 0 : 1;
const Int32 insert_cost = curr[j - 1] + 1;
const Int32 delete_cost = prev[j] + 1;
const Int32 replace_cost = prev[j - 1] + cost;
curr[j] = std::min(std::min(insert_cost, delete_cost), replace_cost);
}
std::swap(prev, curr);
}
return prev[n];
}
static Int32 levenshtein_distance(const StringRef& left, const StringRef& right,
std::vector<size_t>& left_offsets,
std::vector<size_t>& right_offsets) {
const bool left_ascii = simd::VStringFunctions::is_ascii(left);
const bool right_ascii = simd::VStringFunctions::is_ascii(right);
if (left_ascii && right_ascii) {
return levenshtein_distance_ascii(left, right);
}
if (left.size == 0) {
return static_cast<Int32>(simd::VStringFunctions::get_char_len(right.data, right.size));
}
if (right.size == 0) {
return static_cast<Int32>(simd::VStringFunctions::get_char_len(left.data, left.size));
}
simd::VStringFunctions::get_utf8_char_offsets(left, left_offsets);
simd::VStringFunctions::get_utf8_char_offsets(right, right_offsets);
return levenshtein_distance_utf8(left, left_offsets, right, right_offsets);
}
static Int32 levenshtein_distance_with_right_offsets(const StringRef& left,
std::vector<size_t>& left_offsets,
const StringRef& right,
const std::vector<size_t>& right_offsets,
bool right_ascii) {
const bool left_ascii = simd::VStringFunctions::is_ascii(left);
if (left_ascii && right_ascii) {
return levenshtein_distance_ascii(left, right);
}
if (left.size == 0) {
return static_cast<Int32>(right_offsets.size());
}
if (right.size == 0) {
return left_ascii ? static_cast<Int32>(left.size)
: static_cast<Int32>(
simd::VStringFunctions::get_char_len(left.data, left.size));
}
simd::VStringFunctions::get_utf8_char_offsets(left, left_offsets);
return levenshtein_distance_utf8(left, left_offsets, right, right_offsets);
}
static Int32 levenshtein_distance_with_left_offsets(const StringRef& left,
const std::vector<size_t>& left_offsets,
bool left_ascii, const StringRef& right,
std::vector<size_t>& right_offsets) {
const bool right_ascii = simd::VStringFunctions::is_ascii(right);
if (left_ascii && right_ascii) {
return levenshtein_distance_ascii(left, right);
}
if (left.size == 0) {
return static_cast<Int32>(
right_ascii ? right.size
: simd::VStringFunctions::get_char_len(right.data, right.size));
}
if (right.size == 0) {
return static_cast<Int32>(left_offsets.size());
}
simd::VStringFunctions::get_utf8_char_offsets(right, right_offsets);
return levenshtein_distance_utf8(left, left_offsets, right, right_offsets);
}
static Int32 levenshtein_distance(const StringRef& left, const StringRef& right) {
std::vector<size_t> left_offsets;
std::vector<size_t> right_offsets;
return levenshtein_distance(left, right, left_offsets, right_offsets);
}
};
using FunctionLevenshtein =
FunctionBinaryToType<DataTypeString, DataTypeString, LevenshteinImpl, NameLevenshtein>;
void register_function_levenshtein(SimpleFunctionFactory& factory) {
factory.register_function<FunctionLevenshtein>();
}
} // namespace doris