be/src/exprs/function/function_hamming_distance.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include <algorithm> |
19 | | #include <vector> |
20 | | |
21 | | #include "common/status.h" |
22 | | #include "core/column/column_nullable.h" |
23 | | #include "core/column/column_string.h" |
24 | | #include "core/data_type/data_type_number.h" |
25 | | #include "core/string_ref.h" |
26 | | #include "exprs/function/simple_function_factory.h" |
27 | | #include "util/simd/vstring_function.h" |
28 | | |
29 | | namespace doris { |
30 | | |
31 | | class FunctionHammingDistance : public IFunction { |
32 | | public: |
33 | | using ResultDataType = DataTypeInt64; |
34 | | using ResultPaddedPODArray = PaddedPODArray<Int64>; |
35 | | using ResultColumnType = ColumnVector<ResultDataType::PType>; |
36 | | |
37 | | static constexpr auto name = "hamming_distance"; |
38 | | |
39 | 45 | static FunctionPtr create() { return std::make_shared<FunctionHammingDistance>(); } |
40 | | |
41 | 1 | String get_name() const override { return name; } |
42 | 37 | size_t get_number_of_arguments() const override { return 2; } |
43 | | |
44 | 37 | DataTypePtr get_return_type_impl(const DataTypes& arguments) const override { |
45 | 37 | const bool has_nullable = std::ranges::any_of( |
46 | 65 | arguments, [](const DataTypePtr& type) { return type->is_nullable(); }); |
47 | 37 | if (has_nullable) { |
48 | 14 | return make_nullable(std::make_shared<ResultDataType>()); |
49 | 14 | } |
50 | 23 | return std::make_shared<ResultDataType>(); |
51 | 37 | } |
52 | | |
53 | 74 | bool use_default_implementation_for_nulls() const override { return false; } |
54 | | |
55 | | Status execute_impl(FunctionContext* /*context*/, Block& block, const ColumnNumbers& arguments, |
56 | 37 | uint32_t result, size_t input_rows_count) const override { |
57 | 37 | const auto& [left_col, left_const] = |
58 | 37 | unpack_if_const(block.get_by_position(arguments[0]).column); |
59 | 37 | const auto& [right_col, right_const] = |
60 | 37 | unpack_if_const(block.get_by_position(arguments[1]).column); |
61 | | |
62 | 37 | const auto* left_nullable = check_and_get_column<ColumnNullable>(left_col.get()); |
63 | 37 | const auto* right_nullable = check_and_get_column<ColumnNullable>(right_col.get()); |
64 | | |
65 | 37 | const IColumn* left_nested = |
66 | 37 | left_nullable ? &left_nullable->get_nested_column() : left_col.get(); |
67 | 37 | const IColumn* right_nested = |
68 | 37 | right_nullable ? &right_nullable->get_nested_column() : right_col.get(); |
69 | | |
70 | 37 | const auto* left_str_col = assert_cast<const ColumnString*>(left_nested); |
71 | 37 | const auto* right_str_col = assert_cast<const ColumnString*>(right_nested); |
72 | | |
73 | 37 | auto res_col = ResultColumnType::create(input_rows_count); |
74 | 37 | auto& res_data = res_col->get_data(); |
75 | | |
76 | 37 | const NullMap* left_null_map = |
77 | 37 | left_nullable ? &left_nullable->get_null_map_data() : nullptr; |
78 | 37 | const NullMap* right_null_map = |
79 | 37 | right_nullable ? &right_nullable->get_null_map_data() : nullptr; |
80 | 37 | const bool has_nullable = left_null_map != nullptr || right_null_map != nullptr; |
81 | | |
82 | 37 | if (!has_nullable) { |
83 | 23 | if (left_const) { |
84 | 3 | RETURN_IF_ERROR( |
85 | 3 | scalar_vector(left_str_col->get_data_at(0), *right_str_col, res_data)); |
86 | 20 | } else if (right_const) { |
87 | 4 | RETURN_IF_ERROR( |
88 | 4 | vector_scalar(*left_str_col, right_str_col->get_data_at(0), res_data)); |
89 | 16 | } else { |
90 | 16 | RETURN_IF_ERROR(vector_vector(*left_str_col, *right_str_col, res_data)); |
91 | 16 | } |
92 | 15 | block.replace_by_position(result, std::move(res_col)); |
93 | 15 | return Status::OK(); |
94 | 23 | } |
95 | | |
96 | 14 | auto null_col = ColumnUInt8::create(input_rows_count, 0); |
97 | 14 | auto& null_map = null_col->get_data(); |
98 | 14 | if (left_const) { |
99 | 3 | if (left_null_map && (*left_null_map)[0]) { |
100 | 0 | std::fill(null_map.begin(), null_map.end(), 1); |
101 | 0 | block.replace_by_position( |
102 | 0 | result, ColumnNullable::create(std::move(res_col), std::move(null_col))); |
103 | 0 | return Status::OK(); |
104 | 0 | } |
105 | | |
106 | 3 | const auto left = left_str_col->get_data_at(0); |
107 | 3 | RETURN_IF_ERROR(scalar_vector_nullable(left, *right_str_col, right_null_map, res_data, |
108 | 3 | null_map)); |
109 | 11 | } else if (right_const) { |
110 | 4 | if (right_null_map && (*right_null_map)[0]) { |
111 | 0 | std::fill(null_map.begin(), null_map.end(), 1); |
112 | 0 | block.replace_by_position( |
113 | 0 | result, ColumnNullable::create(std::move(res_col), std::move(null_col))); |
114 | 0 | return Status::OK(); |
115 | 0 | } |
116 | | |
117 | 4 | RETURN_IF_ERROR(vector_scalar_nullable(*left_str_col, right_str_col->get_data_at(0), |
118 | 4 | left_null_map, res_data, null_map)); |
119 | 7 | } else { |
120 | 20 | for (size_t i = 0; i < input_rows_count; ++i) { |
121 | 13 | const bool left_is_null = left_null_map && (*left_null_map)[i]; |
122 | 13 | const bool right_is_null = right_null_map && (*right_null_map)[i]; |
123 | 13 | if (left_is_null || right_is_null) { |
124 | 7 | null_map[i] = 1; |
125 | 7 | res_data[i] = 0; |
126 | 7 | continue; |
127 | 7 | } |
128 | | |
129 | 6 | RETURN_IF_ERROR(hamming_distance(left_str_col->get_data_at(i), |
130 | 6 | right_str_col->get_data_at(i), res_data[i], i)); |
131 | 6 | } |
132 | 7 | } |
133 | | |
134 | 11 | block.replace_by_position(result, |
135 | 11 | ColumnNullable::create(std::move(res_col), std::move(null_col))); |
136 | 11 | return Status::OK(); |
137 | 14 | } |
138 | | |
139 | | private: |
140 | | static Status vector_vector(const ColumnString& lcol, const ColumnString& rcol, |
141 | 16 | ResultPaddedPODArray& res) { |
142 | 16 | DCHECK_EQ(lcol.size(), rcol.size()); |
143 | | |
144 | 16 | const size_t size = lcol.size(); |
145 | 16 | res.resize(size); |
146 | 16 | std::vector<size_t> left_offsets; |
147 | 16 | std::vector<size_t> right_offsets; |
148 | 30 | for (size_t i = 0; i < size; ++i) { |
149 | 19 | const auto left = lcol.get_data_at(i); |
150 | 19 | const auto right = rcol.get_data_at(i); |
151 | 19 | RETURN_IF_ERROR(hamming_distance_with_offsets( |
152 | 19 | left, left_offsets, false, simd::VStringFunctions::is_ascii(left), right, |
153 | 19 | right_offsets, false, simd::VStringFunctions::is_ascii(right), res[i], i)); |
154 | 19 | } |
155 | 11 | return Status::OK(); |
156 | 16 | } |
157 | | |
158 | | static Status vector_scalar(const ColumnString& lcol, const StringRef& rdata, |
159 | 4 | ResultPaddedPODArray& res) { |
160 | 4 | const size_t size = lcol.size(); |
161 | 4 | res.resize(size); |
162 | 4 | const bool right_ascii = simd::VStringFunctions::is_ascii(rdata); |
163 | 4 | std::vector<size_t> right_offsets; |
164 | 4 | simd::VStringFunctions::get_utf8_char_offsets(rdata, right_offsets); |
165 | 4 | std::vector<size_t> left_offsets; |
166 | 6 | for (size_t i = 0; i < size; ++i) { |
167 | 4 | const auto left = lcol.get_data_at(i); |
168 | 4 | RETURN_IF_ERROR(hamming_distance_with_offsets( |
169 | 4 | left, left_offsets, false, simd::VStringFunctions::is_ascii(left), rdata, |
170 | 4 | right_offsets, true, right_ascii, res[i], i)); |
171 | 4 | } |
172 | 2 | return Status::OK(); |
173 | 4 | } |
174 | | |
175 | | static Status scalar_vector(const StringRef& ldata, const ColumnString& rcol, |
176 | 3 | ResultPaddedPODArray& res) { |
177 | 3 | const size_t size = rcol.size(); |
178 | 3 | res.resize(size); |
179 | 3 | const bool left_ascii = simd::VStringFunctions::is_ascii(ldata); |
180 | 3 | std::vector<size_t> left_offsets; |
181 | 3 | simd::VStringFunctions::get_utf8_char_offsets(ldata, left_offsets); |
182 | 3 | std::vector<size_t> right_offsets; |
183 | 5 | for (size_t i = 0; i < size; ++i) { |
184 | 3 | const auto right = rcol.get_data_at(i); |
185 | 3 | RETURN_IF_ERROR(hamming_distance_with_offsets( |
186 | 3 | ldata, left_offsets, true, left_ascii, right, right_offsets, false, |
187 | 3 | simd::VStringFunctions::is_ascii(right), res[i], i)); |
188 | 3 | } |
189 | 2 | return Status::OK(); |
190 | 3 | } |
191 | | |
192 | | static Status vector_scalar_nullable(const ColumnString& lcol, const StringRef& rdata, |
193 | | const NullMap* left_null_map, ResultPaddedPODArray& res, |
194 | 4 | NullMap& null_map) { |
195 | 4 | const size_t size = lcol.size(); |
196 | 4 | res.resize(size); |
197 | 4 | const bool right_ascii = simd::VStringFunctions::is_ascii(rdata); |
198 | 4 | std::vector<size_t> right_offsets; |
199 | 4 | simd::VStringFunctions::get_utf8_char_offsets(rdata, right_offsets); |
200 | 4 | std::vector<size_t> left_offsets; |
201 | 8 | for (size_t i = 0; i < size; ++i) { |
202 | 6 | if (left_null_map && (*left_null_map)[i]) { |
203 | 1 | null_map[i] = 1; |
204 | 1 | res[i] = 0; |
205 | 1 | continue; |
206 | 1 | } |
207 | | |
208 | 5 | const auto left = lcol.get_data_at(i); |
209 | 5 | RETURN_IF_ERROR(hamming_distance_with_offsets( |
210 | 5 | left, left_offsets, false, simd::VStringFunctions::is_ascii(left), rdata, |
211 | 5 | right_offsets, true, right_ascii, res[i], i)); |
212 | 5 | } |
213 | 2 | return Status::OK(); |
214 | 4 | } |
215 | | |
216 | | static Status scalar_vector_nullable(const StringRef& ldata, const ColumnString& rcol, |
217 | | const NullMap* right_null_map, ResultPaddedPODArray& res, |
218 | 3 | NullMap& null_map) { |
219 | 3 | const size_t size = rcol.size(); |
220 | 3 | res.resize(size); |
221 | 3 | const bool left_ascii = simd::VStringFunctions::is_ascii(ldata); |
222 | 3 | std::vector<size_t> left_offsets; |
223 | 3 | simd::VStringFunctions::get_utf8_char_offsets(ldata, left_offsets); |
224 | 3 | std::vector<size_t> right_offsets; |
225 | 7 | for (size_t i = 0; i < size; ++i) { |
226 | 5 | if (right_null_map && (*right_null_map)[i]) { |
227 | 1 | null_map[i] = 1; |
228 | 1 | res[i] = 0; |
229 | 1 | continue; |
230 | 1 | } |
231 | | |
232 | 4 | const auto right = rcol.get_data_at(i); |
233 | 4 | RETURN_IF_ERROR(hamming_distance_with_offsets( |
234 | 4 | ldata, left_offsets, true, left_ascii, right, right_offsets, false, |
235 | 4 | simd::VStringFunctions::is_ascii(right), res[i], i)); |
236 | 4 | } |
237 | 2 | return Status::OK(); |
238 | 3 | } |
239 | | |
240 | | static Status hamming_distance_ascii(const StringRef& left, const StringRef& right, |
241 | 24 | Int64& result, size_t row) { |
242 | 24 | if (left.size != right.size) { |
243 | 7 | return Status::InvalidArgument( |
244 | 7 | "hamming_distance requires strings of the same length at row {}", row); |
245 | 7 | } |
246 | | |
247 | 17 | Int64 distance = 0; |
248 | 69 | for (size_t i = 0; i < left.size; ++i) { |
249 | 52 | distance += static_cast<Int64>(left.data[i] != right.data[i]); |
250 | 52 | } |
251 | 17 | result = distance; |
252 | 17 | return Status::OK(); |
253 | 24 | } |
254 | | |
255 | | static Status hamming_distance_utf8(const StringRef& left, |
256 | | const std::vector<size_t>& left_offsets, |
257 | | const StringRef& right, |
258 | | const std::vector<size_t>& right_offsets, Int64& result, |
259 | 17 | size_t row) { |
260 | 17 | if (left_offsets.size() != right_offsets.size()) { |
261 | 4 | return Status::InvalidArgument( |
262 | 4 | "hamming_distance requires strings of the same length at row {}", row); |
263 | 4 | } |
264 | | |
265 | 13 | Int64 distance = 0; |
266 | 13 | const size_t len = left_offsets.size(); |
267 | 31 | for (size_t i = 0; i + 1 < len; ++i) { |
268 | 18 | const size_t left_off = left_offsets[i]; |
269 | 18 | const size_t left_next = left_offsets[i + 1]; |
270 | 18 | const size_t right_off = right_offsets[i]; |
271 | 18 | const size_t right_next = right_offsets[i + 1]; |
272 | 18 | distance += static_cast<Int64>(!simd::VStringFunctions::utf8_char_equal( |
273 | 18 | left, left_off, left_next, right, right_off, right_next)); |
274 | 18 | } |
275 | 13 | if (len > 0) { |
276 | 13 | const size_t left_off = left_offsets[len - 1]; |
277 | 13 | const size_t right_off = right_offsets[len - 1]; |
278 | 13 | distance += static_cast<Int64>(!simd::VStringFunctions::utf8_char_equal( |
279 | 13 | left, left_off, left.size, right, right_off, right.size)); |
280 | 13 | } |
281 | | |
282 | 13 | result = distance; |
283 | 13 | return Status::OK(); |
284 | 17 | } |
285 | | |
286 | | static Status hamming_distance_with_offsets( |
287 | | const StringRef& left, std::vector<size_t>& left_offsets, bool left_offsets_ready, |
288 | | bool left_ascii, const StringRef& right, std::vector<size_t>& right_offsets, |
289 | 41 | bool right_offsets_ready, bool right_ascii, Int64& result, size_t row) { |
290 | 41 | if (left_ascii && right_ascii) { |
291 | 24 | return hamming_distance_ascii(left, right, result, row); |
292 | 24 | } |
293 | | |
294 | 17 | if (!left_offsets_ready) { |
295 | 15 | simd::VStringFunctions::get_utf8_char_offsets(left, left_offsets); |
296 | 15 | } |
297 | 17 | if (!right_offsets_ready) { |
298 | 13 | simd::VStringFunctions::get_utf8_char_offsets(right, right_offsets); |
299 | 13 | } |
300 | 17 | return hamming_distance_utf8(left, left_offsets, right, right_offsets, result, row); |
301 | 41 | } |
302 | | |
303 | | static Status hamming_distance(const StringRef& left, const StringRef& right, Int64& result, |
304 | 6 | size_t row) { |
305 | 6 | std::vector<size_t> left_offsets; |
306 | 6 | std::vector<size_t> right_offsets; |
307 | 6 | return hamming_distance_with_offsets( |
308 | 6 | left, left_offsets, false, simd::VStringFunctions::is_ascii(left), right, |
309 | 6 | right_offsets, false, simd::VStringFunctions::is_ascii(right), result, row); |
310 | 6 | } |
311 | | }; |
312 | | |
313 | 7 | void register_function_hamming_distance(SimpleFunctionFactory& factory) { |
314 | 7 | factory.register_function<FunctionHammingDistance>(); |
315 | 7 | } |
316 | | |
317 | | } // namespace doris |