Skip to content

Commit 6fc28bd

Browse files
committed
Make mapv_into_any() work for ArcArray, resolves #1280
1 parent 492b274 commit 6fc28bd

File tree

4 files changed

+353
-45
lines changed

4 files changed

+353
-45
lines changed

src/data_traits.rs

+74
Original file line numberDiff line numberDiff line change
@@ -793,3 +793,77 @@ impl<'a, A: 'a, B: 'a> RawDataSubst<B> for CowRepr<'a, A>
793793
}
794794
}
795795
}
796+
797+
/// Array representation trait for mapping to an owned array with a different element type.
798+
///
799+
/// Functions such as [`mapv_into_any`](ArrayBase::mapv_into_any) that alter an array's
800+
/// underlying element type often want to preserve the storage type (e.g., `ArcArray`)
801+
/// of the original array. However, because Rust considers `OwnedRepr<A>` and `OwnedRepr<B>`
802+
/// to be completely different types, a trait must be used to indicate what the mapping is.
803+
///
804+
/// This trait will map owning storage types to the element-swapped version of themselves;
805+
/// view types are mapped to `OwnedRepr`. The following table summarizes the mappings:
806+
///
807+
/// | Original Storage Type | Corresponding Array Type | Mapped Storage Type | Output of `from_owned` |
808+
/// | ----------------------- | ------------------------ | ------------------- | ---------------------- |
809+
/// | `OwnedRepr<A>` | `Array<A, D>` | `OwnedRepr<B>` | `Array<B, D>` |
810+
/// | `OwnedArcRepr<A>` | `ArcArray<A, D>` | `OwnedArcRepr<B>` | `ArcArray<B, D>` |
811+
/// | `CowRepr<'a, A>` | `CowArray<'a, A, D>` | `CowRepr<'a, B>` | `CowArray<'a, B, D>` |
812+
/// | `ViewRepr<&'a mut A>` | `ArrayViewMut<'a, A, D>` | `OwnedRepr<B>` | `Array<B, D>` |
813+
pub trait DataMappable<'a>: RawData
814+
{
815+
/// The element-swapped, owning storage representation
816+
type Subst<B: 'a>: Data<Elem = B> + DataOwned;
817+
818+
/// Cheaply convert an owned [`Array<B, D>`] to a different owning array type, as dictated by `Subst`.
819+
///
820+
/// The owning arrays implement `From`, which is the preferred method for changing the underlying storage.
821+
/// This method (and trait) should be reserved for dealing with changing the element type.
822+
fn from_owned<B, D: Dimension>(self_: Array<B, D>) -> ArrayBase<Self::Subst<B>, D>;
823+
}
824+
825+
impl<'a, A: 'a> DataMappable<'a> for OwnedRepr<A>
826+
{
827+
type Subst<B: 'a> = OwnedRepr<B>;
828+
fn from_owned<B: 'a, D: Dimension>(self_: Array<B,D>) -> ArrayBase<Self::Subst<B>,D>
829+
{
830+
self_
831+
}
832+
}
833+
834+
impl<'a, A: 'a> DataMappable<'a> for OwnedArcRepr<A>
835+
{
836+
type Subst<B: 'a> = OwnedArcRepr<B>;
837+
fn from_owned<B: 'a, D: Dimension>(self_: Array<B,D>) -> ArrayBase<Self::Subst<B>,D>
838+
{
839+
self_.into()
840+
}
841+
}
842+
843+
844+
impl<'a, A: 'a> DataMappable<'a> for CowRepr<'a, A>
845+
{
846+
type Subst<B: 'a> = CowRepr<'a, B>;
847+
fn from_owned<B: 'a, D: Dimension>(self_: Array<B,D>) -> ArrayBase<Self::Subst<B>,D>
848+
{
849+
self_.into()
850+
}
851+
}
852+
853+
impl<'a, A: 'a> DataMappable<'a> for ViewRepr<&'a A>
854+
{
855+
type Subst<B: 'a> = OwnedRepr<B>;
856+
fn from_owned<B: 'a, D: Dimension>(self_: Array<B, D>) -> ArrayBase<Self::Subst<B>, D>
857+
{
858+
self_
859+
}
860+
}
861+
862+
impl<'a, A: 'a> DataMappable<'a> for ViewRepr<&'a mut A>
863+
{
864+
type Subst<B: 'a> = OwnedRepr<B>;
865+
fn from_owned<B: 'a, D: Dimension>(self_: Array<B, D>) -> ArrayBase<Self::Subst<B>, D>
866+
{
867+
self_
868+
}
869+
}

src/impl_methods.rs

+106-42
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use std::mem::{size_of, ManuallyDrop};
1717
use crate::imp_prelude::*;
1818

1919
use crate::argument_traits::AssignElem;
20+
use crate::data_traits::DataMappable;
2021
use crate::dimension;
2122
use crate::dimension::broadcast::co_broadcast;
2223
use crate::dimension::reshape_dim;
@@ -2803,48 +2804,6 @@ where
28032804
self
28042805
}
28052806

2806-
/// Consume the array, call `f` by **v**alue on each element, and return an
2807-
/// owned array with the new values. Works for **any** `F: FnMut(A)->B`.
2808-
///
2809-
/// If `A` and `B` are the same type then the map is performed by delegating
2810-
/// to [`mapv_into`] and then converting into an owned array. This avoids
2811-
/// unnecessary memory allocations in [`mapv`].
2812-
///
2813-
/// If `A` and `B` are different types then a new array is allocated and the
2814-
/// map is performed as in [`mapv`].
2815-
///
2816-
/// Elements are visited in arbitrary order.
2817-
///
2818-
/// [`mapv_into`]: ArrayBase::mapv_into
2819-
/// [`mapv`]: ArrayBase::mapv
2820-
pub fn mapv_into_any<B, F>(self, mut f: F) -> Array<B, D>
2821-
where
2822-
S: DataMut,
2823-
F: FnMut(A) -> B,
2824-
A: Clone + 'static,
2825-
B: 'static,
2826-
{
2827-
if core::any::TypeId::of::<A>() == core::any::TypeId::of::<B>() {
2828-
// A and B are the same type.
2829-
// Wrap f in a closure of type FnMut(A) -> A .
2830-
let f = |a| {
2831-
let b = f(a);
2832-
// Safe because A and B are the same type.
2833-
unsafe { unlimited_transmute::<B, A>(b) }
2834-
};
2835-
// Delegate to mapv_into() using the wrapped closure.
2836-
// Convert output to a uniquely owned array of type Array<A, D>.
2837-
let output = self.mapv_into(f).into_owned();
2838-
// Change the return type from Array<A, D> to Array<B, D>.
2839-
// Again, safe because A and B are the same type.
2840-
unsafe { unlimited_transmute::<Array<A, D>, Array<B, D>>(output) }
2841-
} else {
2842-
// A and B are not the same type.
2843-
// Fallback to mapv().
2844-
self.mapv(f)
2845-
}
2846-
}
2847-
28482807
/// Modify the array in place by calling `f` by mutable reference on each element.
28492808
///
28502809
/// Elements are visited in arbitrary order.
@@ -3059,6 +3018,111 @@ where
30593018
}
30603019
}
30613020

3021+
/// # Additional Mapping Methods
3022+
impl<'a, A, S, D> ArrayBase<S, D>
3023+
where
3024+
D: Dimension,
3025+
// Need 'static lifetime bounds for TypeId to work.
3026+
// mapv() requires that A be Clone.
3027+
A: Clone + 'a + 'static,
3028+
// Output is same memory representation as input, substituting B for A.
3029+
S: Data<Elem = A> + DataMappable<'a>,
3030+
{
3031+
/// Consume the array, call `f` by **v**alue on each element, and return an
3032+
/// owned array with the new values. Works for **any** `F: FnMut(A)->B`.
3033+
///
3034+
/// If `A` and `B` are the same type then the map is performed by delegating
3035+
/// to [`mapv_into`](`ArrayBase::mapv_into`) and then converting into an
3036+
/// owned array. This avoids unnecessary memory allocations in
3037+
/// [`mapv`](`ArrayBase::mapv`).
3038+
///
3039+
/// If `A` and `B` are different types then a new array is allocated and the
3040+
/// map is performed as in [`mapv`](`ArrayBase::mapv`).
3041+
///
3042+
/// Elements are visited in arbitrary order.
3043+
///
3044+
/// Example:
3045+
///
3046+
/// ```rust
3047+
/// # use ndarray::{array, Array};
3048+
/// let a: Array<f32, _> = array![[1., 2., 3.]];
3049+
/// let b = a.clone();
3050+
/// // Same type, no new memory allocation.
3051+
/// let a_plus_one = a.mapv_into_any(|a| a + 1.);
3052+
/// // Different types, allocates new memory.
3053+
/// let rounded = b.mapv_into_any(|a| a.round() as i32);
3054+
/// ```
3055+
///
3056+
/// The return data representation/type depends on the input type and is the
3057+
/// same as the input type in most cases. See [`DataMappable`] for details.
3058+
///
3059+
/// - [`OwnedRepr`](`crate::OwnedRepr`)/[`Array`] -> [`OwnedRepr`](`crate::OwnedRepr`)/[`Array`]
3060+
/// - [`OwnedArcRepr`](`crate::OwnedArcRepr`)/[`ArcArray`] -> [`OwnedArcRepr`](`crate::OwnedArcRepr`)/[`ArcArray`]
3061+
/// - [`CowRepr`](`crate::CowRepr`)/[`CowArray`] -> [`CowRepr`](`crate::CowRepr`)/[`CowArray`]
3062+
/// - [`ViewRepr`](`crate::ViewRepr`)/[`ArrayView`] or [`ArrayViewMut`] -> [`OwnedRepr`](`crate::OwnedRepr`)/[`Array`]
3063+
///
3064+
/// Mapping from `A` to a different type `B` will always require new memory
3065+
/// to be allocated. Mapping when `A` and `B` are the same type may not need
3066+
/// a new memory allocation depending on the input data representation/type.
3067+
///
3068+
/// - [`OwnedRepr`](`crate::OwnedRepr`)/[`Array`]: No new memory allocation.
3069+
/// - [`OwnedArcRepr`](`crate::OwnedArcRepr`)/[`ArcArray`]: No new memory allocated if data is uniquely owned.
3070+
/// - [`CowRepr`](`crate::CowRepr`)/[`CowArray`]: No new memory allocated if data is uniquely owned.
3071+
/// - [`ViewRepr`](`crate::ViewRepr`)/[`ArrayView`] or [`ArrayViewMut`]: Always requires new memory allocation.
3072+
/// Consider using [`map_inplace`](`ArrayBase::map_inplace`) instead.
3073+
///
3074+
/// Example:
3075+
///
3076+
/// ```rust
3077+
/// # use ndarray::{array, ArcArray};
3078+
/// // Uniquely owned data, no new memory allocation.
3079+
/// let a: ArcArray<f32, _> = array![[1., 2., 3.]].into_shared();
3080+
/// let a_plus_one = a.mapv_into_any(|a| a + 1.);
3081+
/// // Shared data, requires new memory allocation.
3082+
/// let a: ArcArray<f32, _> = array![[1., 2., 3.]].into_shared();
3083+
/// let b = a.clone(); // `a` is shared here
3084+
/// let a_plus_one = a.mapv_into_any(|a| a + 1.); // new allocation
3085+
/// ```
3086+
///
3087+
/// See also:
3088+
///
3089+
/// - [`map_inplace`](`ArrayBase::map_inplace`)
3090+
/// - [`mapv_into`](`ArrayBase::mapv_into`)
3091+
/// - [`mapv`](`ArrayBase::mapv`)
3092+
pub fn mapv_into_any<B, F>(self, mut f: F) -> ArrayBase<<S as DataMappable<'a>>::Subst<B>, D>
3093+
where
3094+
// Need 'static lifetime bounds for TypeId to work.
3095+
B: 'static,
3096+
// Mapping function maps from A to B.
3097+
F: FnMut(A) -> B,
3098+
{
3099+
if core::any::TypeId::of::<A>() == core::any::TypeId::of::<B>() {
3100+
// A and B are the same type.
3101+
// Wrap f in a closure of type FnMut(A) -> A .
3102+
let f = |a| {
3103+
let b = f(a);
3104+
// Safe because A and B are the same type.
3105+
unsafe { unlimited_transmute::<B, A>(b) }
3106+
};
3107+
// Convert to a uniquely-owned data representation: Array<A, D>
3108+
// This will require cloning the data if it is not uniquely owned.
3109+
let input = self.into_owned();
3110+
// Delegate to mapv_into() to map from element type A to type A.
3111+
let output = input.mapv_into(f);
3112+
// Convert to the output data representation, but still with data A.
3113+
let output = <S as DataMappable>::from_owned::<A,D>(output);
3114+
// Transmute to the output representation to data B.
3115+
// Safe because A and B are the same type,
3116+
// and we are not changing the data representation.
3117+
unsafe { unlimited_transmute::<ArrayBase<<S as DataMappable>::Subst<A>, D>, ArrayBase<<S as DataMappable>::Subst<B>, D>>(output) }
3118+
} else {
3119+
// A and B are not the same type.
3120+
// Fallback to mapv().
3121+
<S as DataMappable>::from_owned::<B,D>(self.mapv(f))
3122+
}
3123+
}
3124+
}
3125+
30623126
/// Transmute from A to B.
30633127
///
30643128
/// Like transmute, but does not have the compile-time size check which blocks

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ mod data_traits;
174174

175175
pub use crate::aliases::*;
176176

177-
pub use crate::data_traits::{Data, DataMut, DataOwned, DataShared, RawData, RawDataClone, RawDataMut, RawDataSubst};
177+
pub use crate::data_traits::{Data, DataMut, DataOwned, DataShared, RawData, RawDataClone, RawDataMut, RawDataSubst, DataMappable};
178178

179179
mod free_functions;
180180
pub use crate::free_functions::*;

0 commit comments

Comments
 (0)