diff --git a/polars/polars-ops/src/series/ops/various.rs b/polars/polars-ops/src/series/ops/various.rs index 872fe4d69e0b..695dd7f25fd6 100644 --- a/polars/polars-ops/src/series/ops/various.rs +++ b/polars/polars-ops/src/series/ops/various.rs @@ -39,7 +39,7 @@ pub trait SeriesMethods: SeriesSealed { } } - fn is_sorted(&self, options: SortOptions) -> bool { + fn is_sorted(&self, options: SortOptions) -> PolarsResult { let s = self.as_series(); // fast paths @@ -50,12 +50,30 @@ pub trait SeriesMethods: SeriesSealed { && !options.nulls_last && matches!(s.is_sorted_flag(), IsSorted::Ascending)) { - return true; + return Ok(true); + } + let nc = s.null_count(); + let slen = s.len() - nc - 1; // Number of comparisons we might have to do + if nc == s.len() { + // All nulls is all equal + return Ok(true); + } + if nc > 0 { + let nulls = s.chunks().iter().flat_map(|c| c.validity().unwrap()); + let mut npairs = nulls.clone().zip(nulls.skip(1)); + // A null never precedes (follows) a non-null iff all nulls are at the end (beginning) + if (options.nulls_last && npairs.any(|(a, b)| !a && b)) || npairs.any(|(a, b)| a && !b) + { + return Ok(false); + } + } + // Compare adjacent elements with no-copy slices that don't include any nulls + let offset = !options.nulls_last as i64 * nc as i64; + let (s1, s2) = (s.slice(offset, slen), s.slice(offset + 1, slen)); + match options.descending { + true => Ok(Series::gt_eq(&s1, &s2)?.all()), + false => Ok(Series::lt_eq(&s1, &s2)?.all()), } - - // TODO! optimize - let out = s.sort_with(options); - out.eq(s) } } diff --git a/py-polars/src/series/mod.rs b/py-polars/src/series/mod.rs index ca2d0e50d83a..da315ab01a6a 100644 --- a/py-polars/src/series/mod.rs +++ b/py-polars/src/series/mod.rs @@ -651,13 +651,13 @@ impl PySeries { }) } - fn is_sorted(&self, descending: bool) -> bool { + fn is_sorted(&self, descending: bool) -> PyResult { let options = SortOptions { descending, nulls_last: descending, multithreaded: true, }; - self.series.is_sorted(options) + Ok(self.series.is_sorted(options).map_err(PyPolarsErr::from)?) } fn clear(&self) -> Self {