1use std::hash::BuildHasher;
2
3use polars_arrow::bitmap::utils::get_bit_unchecked;
4use polars_utils::aliases::PlSeedableRandomStateQuality;
5use polars_utils::hashing::{_boost_hash_combine, folded_multiply};
6use polars_utils::total_ord::{ToTotalOrd, TotalHash};
7use rayon::prelude::*;
8use xxhash_rust::xxh3::xxh3_64_with_seed;
9
10use super::*;
11use crate::prelude::*;
12use crate::runtime::RAYON;
13use crate::series::implementations::null::NullChunked;
14
15const MULTIPLE: u64 = 6364136223846793005;
17
18pub trait VecHash {
23 fn vec_hash(
25 &self,
26 _random_state: PlSeedableRandomStateQuality,
27 _buf: &mut Vec<u64>,
28 ) -> PolarsResult<()>;
29
30 fn vec_hash_combine(
31 &self,
32 _random_state: PlSeedableRandomStateQuality,
33 _hashes: &mut [u64],
34 ) -> PolarsResult<()>;
35}
36
37pub(crate) fn get_null_hash_value(random_state: &PlSeedableRandomStateQuality) -> u64 {
38 let first = random_state.hash_one(3188347919usize);
41 random_state.hash_one(first)
42}
43
44fn insert_null_hash(
45 chunks: &[ArrayRef],
46 random_state: PlSeedableRandomStateQuality,
47 buf: &mut Vec<u64>,
48) {
49 let null_h = get_null_hash_value(&random_state);
50 let hashes = buf.as_mut_slice();
51
52 let mut offset = 0;
53 chunks.iter().for_each(|arr| {
54 if arr.null_count() > 0 {
55 let validity = arr.validity().unwrap();
56 let (slice, byte_offset, _) = validity.as_slice();
57 (0..validity.len())
58 .map(|i| unsafe { get_bit_unchecked(slice, i + byte_offset) })
59 .zip(&mut hashes[offset..])
60 .for_each(|(valid, h)| {
61 *h = [null_h, *h][valid as usize];
62 })
63 }
64 offset += arr.len();
65 });
66}
67
68fn numeric_vec_hash<T>(
69 ca: &ChunkedArray<T>,
70 random_state: PlSeedableRandomStateQuality,
71 buf: &mut Vec<u64>,
72) where
73 T: PolarsNumericType,
74 T::Native: TotalHash + ToTotalOrd,
75 <T::Native as ToTotalOrd>::TotalOrdItem: Hash,
76{
77 buf.clear();
84 buf.reserve(ca.len());
85
86 #[allow(unused_unsafe)]
87 #[allow(clippy::useless_transmute)]
88 ca.downcast_iter().for_each(|arr| {
89 buf.extend(
90 arr.values()
91 .as_slice()
92 .iter()
93 .copied()
94 .map(|v| random_state.hash_one(v.to_total_ord())),
95 );
96 });
97 insert_null_hash(&ca.chunks, random_state, buf)
98}
99
100fn numeric_vec_hash_combine<T>(
101 ca: &ChunkedArray<T>,
102 random_state: PlSeedableRandomStateQuality,
103 hashes: &mut [u64],
104) where
105 T: PolarsNumericType,
106 T::Native: TotalHash + ToTotalOrd,
107 <T::Native as ToTotalOrd>::TotalOrdItem: Hash,
108{
109 let null_h = get_null_hash_value(&random_state);
110
111 let mut offset = 0;
112 ca.downcast_iter().for_each(|arr| {
113 match arr.null_count() {
114 0 => arr
115 .values()
116 .as_slice()
117 .iter()
118 .zip(&mut hashes[offset..])
119 .for_each(|(v, h)| {
120 *h = folded_multiply(
122 random_state.hash_one(v.to_total_ord()) ^ folded_multiply(*h, MULTIPLE),
125 MULTIPLE,
126 );
127 }),
128 _ => {
129 let validity = arr.validity().unwrap();
130 let (slice, byte_offset, _) = validity.as_slice();
131 (0..validity.len())
132 .map(|i| unsafe { get_bit_unchecked(slice, i + byte_offset) })
133 .zip(&mut hashes[offset..])
134 .zip(arr.values().as_slice())
135 .for_each(|((valid, h), l)| {
136 let lh = random_state.hash_one(l.to_total_ord());
137 let to_hash = [null_h, lh][valid as usize];
138 *h = folded_multiply(to_hash ^ folded_multiply(*h, MULTIPLE), MULTIPLE);
139 });
140 },
141 }
142 offset += arr.len();
143 });
144}
145
146macro_rules! vec_hash_numeric {
147 ($ca:ident) => {
148 impl VecHash for $ca {
149 fn vec_hash(
150 &self,
151 random_state: PlSeedableRandomStateQuality,
152 buf: &mut Vec<u64>,
153 ) -> PolarsResult<()> {
154 numeric_vec_hash(self, random_state, buf);
155 Ok(())
156 }
157
158 fn vec_hash_combine(
159 &self,
160 random_state: PlSeedableRandomStateQuality,
161 hashes: &mut [u64],
162 ) -> PolarsResult<()> {
163 numeric_vec_hash_combine(self, random_state, hashes);
164 Ok(())
165 }
166 }
167 };
168}
169
170vec_hash_numeric!(Int64Chunked);
171vec_hash_numeric!(Int32Chunked);
172vec_hash_numeric!(Int16Chunked);
173vec_hash_numeric!(Int8Chunked);
174vec_hash_numeric!(UInt64Chunked);
175vec_hash_numeric!(UInt32Chunked);
176vec_hash_numeric!(UInt16Chunked);
177vec_hash_numeric!(UInt8Chunked);
178vec_hash_numeric!(Float64Chunked);
179vec_hash_numeric!(Float32Chunked);
180#[cfg(feature = "dtype-f16")]
181vec_hash_numeric!(Float16Chunked);
182#[cfg(feature = "dtype-u128")]
183vec_hash_numeric!(UInt128Chunked);
184#[cfg(any(feature = "dtype-decimal", feature = "dtype-i128"))]
185vec_hash_numeric!(Int128Chunked);
186
187impl VecHash for StringChunked {
188 fn vec_hash(
189 &self,
190 random_state: PlSeedableRandomStateQuality,
191 buf: &mut Vec<u64>,
192 ) -> PolarsResult<()> {
193 self.as_binary().vec_hash(random_state, buf)?;
194 Ok(())
195 }
196
197 fn vec_hash_combine(
198 &self,
199 random_state: PlSeedableRandomStateQuality,
200 hashes: &mut [u64],
201 ) -> PolarsResult<()> {
202 self.as_binary().vec_hash_combine(random_state, hashes)?;
203 Ok(())
204 }
205}
206
207pub fn _hash_binary_array(
209 arr: &BinaryArray<i64>,
210 random_state: PlSeedableRandomStateQuality,
211 buf: &mut Vec<u64>,
212) {
213 let null_h = get_null_hash_value(&random_state);
214 if arr.null_count() == 0 {
215 buf.extend(arr.values_iter().map(|v| xxh3_64_with_seed(v, null_h)))
217 } else {
218 buf.extend(arr.into_iter().map(|opt_v| match opt_v {
219 Some(v) => xxh3_64_with_seed(v, null_h),
220 None => null_h,
221 }))
222 }
223}
224
225fn hash_binview_array(
226 arr: &BinaryViewArray,
227 random_state: PlSeedableRandomStateQuality,
228 buf: &mut Vec<u64>,
229) {
230 let null_h = get_null_hash_value(&random_state);
231 let views = arr.views().iter();
232 let buffers = arr.data_buffers();
233 unsafe {
234 if arr.null_count() == 0 {
235 buf.extend(views.map(|v| v.hash_with_buffers_unchecked(buffers, &random_state)))
236 } else {
237 buf.extend(views.zip(arr.validity().unwrap()).map(|(v, is_valid)| {
238 if is_valid {
239 v.hash_with_buffers_unchecked(buffers, &random_state)
240 } else {
241 null_h
242 }
243 }))
244 }
245 }
246}
247
248impl VecHash for BinaryChunked {
249 fn vec_hash(
250 &self,
251 random_state: PlSeedableRandomStateQuality,
252 buf: &mut Vec<u64>,
253 ) -> PolarsResult<()> {
254 buf.clear();
255 buf.reserve(self.len());
256 self.downcast_iter()
257 .for_each(|arr| hash_binview_array(arr, random_state.clone(), buf));
258 Ok(())
259 }
260
261 fn vec_hash_combine(
262 &self,
263 random_state: PlSeedableRandomStateQuality,
264 hashes: &mut [u64],
265 ) -> PolarsResult<()> {
266 let null_h = get_null_hash_value(&random_state);
267
268 let mut offset = 0;
269 self.downcast_iter().for_each(|arr| {
270 let views = arr.views().iter();
271 let buffers = arr.data_buffers();
272 let hashes = &mut hashes[offset..];
273 unsafe {
274 if arr.null_count() == 0 {
275 views.zip(hashes).for_each(|(v, h)| {
276 let l = v.hash_with_buffers_unchecked(buffers, &random_state);
277 *h = _boost_hash_combine(l, *h)
278 })
279 } else {
280 views
281 .zip(arr.validity().unwrap())
282 .zip(hashes)
283 .for_each(|((v, is_valid), h)| {
284 let l = if is_valid {
285 v.hash_with_buffers_unchecked(buffers, &random_state)
286 } else {
287 null_h
288 };
289 *h = _boost_hash_combine(l, *h)
290 })
291 }
292 }
293 offset += arr.len();
294 });
295 Ok(())
296 }
297}
298
299impl VecHash for BinaryOffsetChunked {
300 fn vec_hash(
301 &self,
302 random_state: PlSeedableRandomStateQuality,
303 buf: &mut Vec<u64>,
304 ) -> PolarsResult<()> {
305 buf.clear();
306 buf.reserve(self.len());
307 self.downcast_iter()
308 .for_each(|arr| _hash_binary_array(arr, random_state.clone(), buf));
309 Ok(())
310 }
311
312 fn vec_hash_combine(
313 &self,
314 random_state: PlSeedableRandomStateQuality,
315 hashes: &mut [u64],
316 ) -> PolarsResult<()> {
317 let null_h = get_null_hash_value(&random_state);
318
319 let mut offset = 0;
320 self.downcast_iter().for_each(|arr| {
321 match arr.null_count() {
322 0 => arr
323 .values_iter()
324 .zip(&mut hashes[offset..])
325 .for_each(|(v, h)| {
326 let l = xxh3_64_with_seed(v, null_h);
327 *h = _boost_hash_combine(l, *h)
328 }),
329 _ => {
330 let validity = arr.validity().unwrap();
331 let (slice, byte_offset, _) = validity.as_slice();
332 (0..validity.len())
333 .map(|i| unsafe { get_bit_unchecked(slice, i + byte_offset) })
334 .zip(&mut hashes[offset..])
335 .zip(arr.values_iter())
336 .for_each(|((valid, h), l)| {
337 let l = if valid {
338 xxh3_64_with_seed(l, null_h)
339 } else {
340 null_h
341 };
342 *h = _boost_hash_combine(l, *h)
343 });
344 },
345 }
346 offset += arr.len();
347 });
348 Ok(())
349 }
350}
351
352impl VecHash for NullChunked {
353 fn vec_hash(
354 &self,
355 random_state: PlSeedableRandomStateQuality,
356 buf: &mut Vec<u64>,
357 ) -> PolarsResult<()> {
358 let null_h = get_null_hash_value(&random_state);
359 buf.clear();
360 buf.resize(self.len(), null_h);
361 Ok(())
362 }
363
364 fn vec_hash_combine(
365 &self,
366 random_state: PlSeedableRandomStateQuality,
367 hashes: &mut [u64],
368 ) -> PolarsResult<()> {
369 let null_h = get_null_hash_value(&random_state);
370 hashes
371 .iter_mut()
372 .for_each(|h| *h = _boost_hash_combine(null_h, *h));
373 Ok(())
374 }
375}
376impl VecHash for BooleanChunked {
377 fn vec_hash(
378 &self,
379 random_state: PlSeedableRandomStateQuality,
380 buf: &mut Vec<u64>,
381 ) -> PolarsResult<()> {
382 buf.clear();
383 buf.reserve(self.len());
384 let true_h = random_state.hash_one(true);
385 let false_h = random_state.hash_one(false);
386 let null_h = get_null_hash_value(&random_state);
387 self.downcast_iter().for_each(|arr| {
388 if arr.null_count() == 0 {
389 buf.extend(arr.values_iter().map(|v| if v { true_h } else { false_h }))
390 } else {
391 buf.extend(arr.into_iter().map(|opt_v| match opt_v {
392 Some(true) => true_h,
393 Some(false) => false_h,
394 None => null_h,
395 }))
396 }
397 });
398 Ok(())
399 }
400
401 fn vec_hash_combine(
402 &self,
403 random_state: PlSeedableRandomStateQuality,
404 hashes: &mut [u64],
405 ) -> PolarsResult<()> {
406 let true_h = random_state.hash_one(true);
407 let false_h = random_state.hash_one(false);
408 let null_h = get_null_hash_value(&random_state);
409
410 let mut offset = 0;
411 self.downcast_iter().for_each(|arr| {
412 match arr.null_count() {
413 0 => arr
414 .values_iter()
415 .zip(&mut hashes[offset..])
416 .for_each(|(v, h)| {
417 let l = if v { true_h } else { false_h };
418 *h = _boost_hash_combine(l, *h)
419 }),
420 _ => {
421 let validity = arr.validity().unwrap();
422 let (slice, byte_offset, _) = validity.as_slice();
423 (0..validity.len())
424 .map(|i| unsafe { get_bit_unchecked(slice, i + byte_offset) })
425 .zip(&mut hashes[offset..])
426 .zip(arr.values())
427 .for_each(|((valid, h), l)| {
428 let l = if valid {
429 if l { true_h } else { false_h }
430 } else {
431 null_h
432 };
433 *h = _boost_hash_combine(l, *h)
434 });
435 },
436 }
437 offset += arr.len();
438 });
439 Ok(())
440 }
441}
442
443#[cfg(feature = "object")]
444impl<T> VecHash for ObjectChunked<T>
445where
446 T: PolarsObject,
447{
448 fn vec_hash(
449 &self,
450 random_state: PlSeedableRandomStateQuality,
451 buf: &mut Vec<u64>,
452 ) -> PolarsResult<()> {
453 buf.clear();
460 buf.reserve(self.len());
461
462 self.downcast_iter()
463 .for_each(|arr| buf.extend(arr.into_iter().map(|opt_v| random_state.hash_one(opt_v))));
464
465 Ok(())
466 }
467
468 fn vec_hash_combine(
469 &self,
470 random_state: PlSeedableRandomStateQuality,
471 hashes: &mut [u64],
472 ) -> PolarsResult<()> {
473 self.apply_to_slice(
474 |opt_v, h| {
475 let hashed = random_state.hash_one(opt_v);
476 _boost_hash_combine(hashed, *h)
477 },
478 hashes,
479 );
480 Ok(())
481 }
482}
483
484pub fn _df_rows_to_hashes_threaded_vertical(
485 keys: &[DataFrame],
486 build_hasher: Option<PlSeedableRandomStateQuality>,
487) -> PolarsResult<(Vec<UInt64Chunked>, PlSeedableRandomStateQuality)> {
488 let build_hasher = build_hasher.unwrap_or_default();
489
490 let hashes = RAYON.install(|| {
491 keys.into_par_iter()
492 .map(|df| {
493 let hb = build_hasher.clone();
494 let mut hashes = vec![];
495 columns_to_hashes(df, Some(hb), &mut hashes)?;
496 Ok(UInt64Chunked::from_vec(PlSmallStr::EMPTY, hashes))
497 })
498 .collect::<PolarsResult<Vec<_>>>()
499 })?;
500 Ok((hashes, build_hasher))
501}
502
503pub fn columns_to_hashes(
504 keys: &DataFrame,
505 build_hasher: Option<PlSeedableRandomStateQuality>,
506 hashes: &mut Vec<u64>,
507) -> PolarsResult<PlSeedableRandomStateQuality> {
508 let build_hasher = build_hasher.unwrap_or_default();
509
510 if keys.width() == 0 {
511 let null_h = get_null_hash_value(&build_hasher);
512
513 for _ in 0..keys.height() {
514 hashes.push(null_h);
515 }
516
517 return Ok(build_hasher);
518 }
519
520 let mut iter = keys.columns().iter();
521 let first = iter.next().expect("at least one key");
522 first.vec_hash(build_hasher.clone(), hashes)?;
523
524 for keys in iter {
525 keys.vec_hash_combine(build_hasher.clone(), hashes)?;
526 }
527
528 Ok(build_hasher)
529}