-
Notifications
You must be signed in to change notification settings - Fork 30
Implement segtree #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement segtree #37
Conversation
Basic implementation has ended, so I'm opening this PR. The following is my additional TODO:
|
We may use this as a reference though with current implementation we provide common use std::{convert::Infallible, marker::PhantomData, ops::Add};
enum Additive<S> {
__Void(Infallible, PhantomData<fn() -> S>),
}
impl<S: ConstZero> Monoid for Additive<S> {
type S = S;
const IDENTITY: S = S::ZERO;
#[inline]
fn binary_operation(a: S, b: S) -> S {
a + b
}
}
trait ConstZero: Sized + Copy + Add<Output = Self> {
const ZERO: Self;
}
macro_rules! impl_const_zero {
($($ty:ty),*) => {
$(
impl ConstZero for $ty {
const ZERO: Self = 0;
}
)*
}
}
impl_const_zero!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize); |
src/segtree.rs
Outdated
|
||
pub trait Monoid { | ||
type S: Copy; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will possibly want to use non Copy
types such as matrix for S
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then should we use Clone
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is what I meant. identit
ies and binary_operation
s will be inlined.
@qryxip About monoid trait, that's what I was going to do. Is there any difference between my current implementation and |
I'm now wondering how can I declare the identity of I have currently two ideas:
|
Sorry if I misunderstood, but if we want to use matrices for As an alternative, we can limit the |
Thank you. The matrix is indeed good example where we can never use constant. I guess using functions for identity value is the simplest and most reasonable way. @statiolake |
With |
@qryxip |
I'm also not sure. I'm not familiar with those kind of abstraction. The current implementation also does not seem to cause confliction with the orphan rule. use lib::{Max, Monoid};
struct Foo;
enum Op {}
impl Monoid for Op {
type S = Foo;
fn identity() -> Foo {
Foo
}
fn op(_: &Foo, _: &Foo) -> Foo {
Foo
}
}
// This will cause error
//impl Monoid for Max<Foo> {
// type S = Foo;
//
// fn identity() -> Foo {
// Foo
// }
//
// fn op(_: &Foo, _: &Foo) -> Foo {
// Foo
// }
//} |
src/segtree.rs
Outdated
pub trait Monoid { | ||
type S: Clone; | ||
fn identity() -> Self::S; | ||
fn binary_operation(a: Self::S, b: Self::S) -> Self::S; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't require S: Copy
, why not change binary_operation(S, S) -> S
to 〃(&S, &S) -> S
? The binary operations will be inlined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about this
pub enum Max<S> {
__Void(Infallible, PhantomData<fn() -> S>),
}
impl<S: Zero + Copy + Ord + Bounded> Monoid for Max<S> {
type S = S;
fn identity() -> S {
S::zero()
}
fn op(lhs: &S, rhs: &S) -> S {
*lhs + *rhs
}
}
// Like `num_traits::Zero`.
// https://docs.rs/num-traits/0.2.12/num_traits/identities/trait.Zero.html
pub trait Zero: Sized + Add<Output = Self> {
fn zero() -> Self;
}
// Like `num_traits::Bounded`.
// https://docs.rs/num-traits/0.2.12/num_traits/bounds/trait.Bounded.html
pub trait Bounded {
fn min_value() -> Self;
fn max_value() -> Self;
}
macro_rules! impl_for_primitive_integers {
($($ty:ty),*) => {
$(
impl Zero for $ty {
fn zero() -> Self {
0
}
}
impl Bounded for $ty {
fn min_value() -> Self {
Self::min_value()
}
fn max_value() -> Self {
Self::max_value()
}
}
)*
}
}
impl_for_primitive_integers!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The design of Zero
looks good to me. (Is it redundant if we use the name like AdditiveIdentity
or something?)
Changing the signature to take reference instead of value may be good, but then the users should not forget writing &
for each argument in trait implementation right? Doesn't it bother? (Yes, it's only a couple-of-bit change)
Also I didn't understand why the binary operations are inlined when references are used. Is there any documentation about it?
Thirdly, why do you always prefer enum Max<S> { __Void(Infallible, PhantomData<fn() -> S>) }
to struct Max<S>(PhantomData<fn() -> S>)
? I want to know the reason if any.
(BTW I've learned that we should use PhantomData<fn() -> S>
rather than PhantomData<S>
from this article, which is very unnatural to me)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
num_traits::Zero
is defined asan additive identity
and users (particularly who are familiar with Rust) will expect that our
Zero
is so. I think that's enough. -
I hadn't thought of that. I thought you were worrying about performance. If we will allow non-
Copy
large heavy-to-copy data, then we shouldn't force users toclone
them. Actually we can take both ofS
and&S
by setting type of the arguments toimpl Borrow<S>
or something, but I don't think this is idiomatic.use std::borrow::Borrow; fn pass(_: impl Borrow<i32>) {} pass(0); pass(&0); pass(&mut 0);
And
why the binary operations are inlined when references are used
Rust inlines functions with small body as other general-purpose programming languages do.
-
Because the instances of marker types don't need to exist. If a marker type does not take generic parameter, this kind of definition is commonly used.
enum Marker {}
But I'm not sure adding
Infallible
is idiomatic when we need to includePhantomData
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Got it. I'll use the simple name
Zero
, and additionallyOne
for product identity. - I understood. I'll change the signature later (and that of lazy segtree as well).
- I see. If the purpose is to prevent creating instance, then isn't
struct Max<S>(Infallible, PhantomData<fn() -> S>)
enough?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, probably it's enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we change the types of f
in max_right
and min_left
too so that it takes reference as an argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think we should change them for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
* Changed the signature of binary_operation so that it takes references * Added some default Monoid implementation
Done fixing. Changing the signature of |
Future Task: maybe we should receive |
That is good. Then we can also recive range types such as |
Let's do it in the next refactor phase. |
@qryxip Maybe we're ready to merge |
No description provided.