Skip to content

Commit

Permalink
Implement LinearGroupBy with only safe Rust
Browse files Browse the repository at this point in the history
  • Loading branch information
Kerollmops committed Dec 24, 2020
1 parent 7b2fb19 commit 0de2daa
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 191 deletions.
12 changes: 0 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,6 @@ extern crate core as std;

macro_rules! group_by_wrapped {
(struct $name:ident, $elem:ty) => {
impl<'a, T: 'a> $name<'a, T> {
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

#[inline]
pub fn remainder_len(&self) -> usize {
self.0.remainder_len()
}
}

impl<'a, T: 'a> std::iter::Iterator for $name<'a, T>
where T: PartialEq,
{
Expand Down
293 changes: 114 additions & 179 deletions src/linear_group/linear_group_by.rs
Original file line number Diff line number Diff line change
@@ -1,215 +1,150 @@
use std::slice::{from_raw_parts, from_raw_parts_mut};
use std::{fmt, marker};
use std::iter::FusedIterator;
use std::{mem, fmt};

use crate::offset_from;
pub struct LinearGroupBy<'a, T: 'a, P> {
slice: &'a [T],
predicate: P,
}

macro_rules! group_by {
(struct $name:ident, $elem:ty, $mkslice:ident) => {
impl<'a, T: 'a, P> $name<'a, T, P> {
#[inline]
pub fn is_empty(&self) -> bool {
self.ptr == self.end
}
impl<'a, T: 'a, P> LinearGroupBy<'a, T, P> {
pub(crate) fn new(slice: &'a [T], predicate: P) -> Self {
LinearGroupBy { slice, predicate }
}
}

#[inline]
pub fn remainder_len(&self) -> usize {
unsafe { offset_from(self.end, self.ptr) }
impl<'a, T: 'a, P> Iterator for LinearGroupBy<'a, T, P>
where
P: FnMut(&T, &T) -> bool,
{
type Item = &'a [T];

#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.slice.is_empty() {
None
} else {
let mut len = 1;
let mut iter = self.slice.windows(2);
while let Some([l, r]) = iter.next() {
if (self.predicate)(l, r) { len += 1 } else { break }
}
let (head, tail) = self.slice.split_at(len);
self.slice = tail;
Some(head)
}
}

impl<'a, T: 'a, P> std::iter::Iterator for $name<'a, T, P>
where P: FnMut(&T, &T) -> bool,
{
type Item = $elem;

fn next(&mut self) -> Option<Self::Item> {
if self.is_empty() { return None }

let mut i = 0;
let mut ptr = self.ptr;

// we use an unsafe block to avoid bounds checking here.
// this is safe because the only thing we do here is to get
// two elements at `ptr` and `ptr + 1`, bounds checking is done by hand.

// we need to get *two* contiguous elements so we check that:
// - the first element is at the `end - 1` position because
// - the second one will be read from `ptr + 1` that must
// be lower or equal to `end`
unsafe {
while ptr != self.end.sub(1) {
let a = &*ptr;
ptr = ptr.add(1);
let b = &*ptr;

i += 1;

if !(self.predicate)(a, b) {
let slice = $mkslice(self.ptr, i);
self.ptr = ptr;
return Some(slice)
}
}
}

// `i` is either `0` or the `slice length - 1` because either:
// - we have not entered the loop and so `i` is equal to `0`
// the slice length is necessarily `1` because we ensure it is not empty
// - we have entered the loop and we have not early returned
// so `i` is equal to the slice `length - 1`
let slice = unsafe { $mkslice(self.ptr, i + 1) };
self.ptr = self.end;
Some(slice)
}

fn size_hint(&self) -> (usize, Option<usize>) {
if self.is_empty() { return (0, Some(0)) }
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.slice.is_empty() { (0, Some(0)) } else { (1, Some(self.slice.len())) }
}

let len = self.remainder_len();
(1, Some(len))
}
#[inline]
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
}

fn last(mut self) -> Option<Self::Item> {
self.next_back()
impl<'a, T: 'a, P> DoubleEndedIterator for LinearGroupBy<'a, T, P>
where
P: FnMut(&T, &T) -> bool,
{
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.slice.is_empty() {
None
} else {
let mut len = 1;
let mut iter = self.slice.windows(2);
while let Some([l, r]) = iter.next_back() {
if (self.predicate)(l, r) { len += 1 } else { break }
}
let (head, tail) = self.slice.split_at(self.slice.len() - len);
self.slice = head;
Some(tail)
}
}
}

impl<'a, T: 'a, P> std::iter::DoubleEndedIterator for $name<'a, T, P>
where P: FnMut(&T, &T) -> bool,
{
fn next_back(&mut self) -> Option<Self::Item> {
// during the loop we retrieve two elements at `ptr` and `ptr - 1`.
if self.is_empty() { return None }

let mut i = 0;

unsafe {
// we ensure that the first element that will be read
// is not under `end` because `end` is out of bound.
let mut ptr = self.end.sub(1);

while ptr != self.ptr {
// we first get `a` that is at the left of `ptr`
// then `b` that is under the `ptr` position.
let a = &*ptr.sub(1);
let b = &*ptr;

i += 1;

if !(self.predicate)(a, b) {
// the slice to return starts at the `ptr` position
// and `i` is the length of it.
let slice = $mkslice(ptr, i);

// because `end` is always an invalid bound
// we use `ptr` as `end` for the future call to `next`.
self.end = ptr;
return Some(slice)
}

ptr = ptr.sub(1);
}
}

let slice = unsafe { $mkslice(self.ptr, i + 1) };
self.ptr = self.end;
Some(slice)
}
}
impl<'a, T: 'a, P> FusedIterator for LinearGroupBy<'a, T, P> where P: FnMut(&T, &T) -> bool {}

impl<'a, T: 'a, P> std::iter::FusedIterator for $name<'a, T, P>
where P: FnMut(&T, &T) -> bool,
{ }
impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for LinearGroupBy<'a, T, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LinearGroupBy").field("slice", &self.slice).finish()
}
}

/// An iterator that will return non-overlapping groups in the slice
/// using *linear/sequential search*.
///
/// It will give two contiguous elements to the predicate function therefore the slice
/// must not be necessarily sorted.
pub struct LinearGroupBy<'a, T: 'a, P> {
ptr: *const T,
end: *const T,
pub struct LinearGroupByMut<'a, T: 'a, P> {
slice: &'a mut [T],
predicate: P,
_phantom: marker::PhantomData<&'a T>,
}

impl<'a, T: 'a, P> LinearGroupBy<'a, T, P>
where P: FnMut(&T, &T) -> bool,
{
pub fn new(slice: &'a [T], predicate: P) -> Self {
Self {
ptr: slice.as_ptr(),
end: unsafe { slice.as_ptr().add(slice.len()) },
predicate,
_phantom: marker::PhantomData,
}
impl<'a, T: 'a, P> LinearGroupByMut<'a, T, P> {
pub(crate) fn new(slice: &'a mut [T], predicate: P) -> Self {
LinearGroupByMut { slice, predicate }
}
}

impl<'a, T: 'a, P> LinearGroupBy<'a, T, P> {
/// Returns the remainder of the original slice that is going to be
/// returned by the iterator.
pub fn remainder(&self) -> &[T] {
let len = self.remainder_len();
unsafe { from_raw_parts(self.ptr, len) }
impl<'a, T: 'a, P> Iterator for LinearGroupByMut<'a, T, P>
where
P: FnMut(&T, &T) -> bool,
{
type Item = &'a mut [T];

#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.slice.is_empty() {
None
} else {
let mut len = 1;
let mut iter = self.slice.windows(2);
while let Some([l, r]) = iter.next() {
if (self.predicate)(l, r) { len += 1 } else { break }
}
let slice = mem::take(&mut self.slice);
let (head, tail) = slice.split_at_mut(len);
self.slice = tail;
Some(head)
}
}
}

impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for LinearGroupBy<'a, T, P> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("LinearGroupBy")
.field("remainder", &self.remainder())
.finish()
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.slice.is_empty() { (0, Some(0)) } else { (1, Some(self.slice.len())) }
}
}

group_by!{ struct LinearGroupBy, &'a [T], from_raw_parts }

/// An iterator that will return non-overlapping *mutable* groups in the slice
/// using *linear/sequential search*.
///
/// It will give two contiguous elements to the predicate function therefore the slice
/// must not be necessarily sorted.
pub struct LinearGroupByMut<'a, T: 'a, P> {
ptr: *mut T,
end: *mut T,
predicate: P,
_phantom: marker::PhantomData<&'a mut T>,
#[inline]
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
}

impl<'a, T: 'a, P> LinearGroupByMut<'a, T, P>
where P: FnMut(&T, &T) -> bool,
impl<'a, T: 'a, P> DoubleEndedIterator for LinearGroupByMut<'a, T, P>
where
P: FnMut(&T, &T) -> bool,
{
pub fn new(slice: &'a mut [T], predicate: P) -> Self {
Self {
ptr: slice.as_mut_ptr(),
end: unsafe { slice.as_mut_ptr().add(slice.len()) },
predicate,
_phantom: marker::PhantomData,
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.slice.is_empty() {
None
} else {
let mut len = 1;
let mut iter = self.slice.windows(2);
while let Some([l, r]) = iter.next_back() {
if (self.predicate)(l, r) { len += 1 } else { break }
}
let slice = mem::take(&mut self.slice);
let (head, tail) = slice.split_at_mut(slice.len() - len);
self.slice = head;
Some(tail)
}
}
}

impl<'a, T: 'a, P> LinearGroupByMut<'a, T, P> {
/// Returns the remainder of the original slice that is going to be
/// returned by the iterator.
pub fn into_remainder(self) -> &'a mut [T] {
let len = self.remainder_len();
unsafe { from_raw_parts_mut(self.ptr, len) }
}
}
impl<'a, T: 'a, P> FusedIterator for LinearGroupByMut<'a, T, P> where P: FnMut(&T, &T) -> bool {}

impl<'a, T: 'a + fmt::Debug, P> fmt::Debug for LinearGroupByMut<'a, T, P> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let len = self.remainder_len();
let remainder = unsafe { from_raw_parts(self.ptr, len) };

f.debug_struct("LinearGroupByMut")
.field("remainder", &remainder)
.finish()
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LinearGroupByMut").field("slice", &self.slice).finish()
}
}

group_by!{ struct LinearGroupByMut, &'a mut [T], from_raw_parts_mut }

0 comments on commit 0de2daa

Please sign in to comment.