C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

Rust で実行中にバッファーの次元解釈を変更できる DimensionShiftableBuffer と翻訳時に任意の次元解釈をVec<T>に追加する vec-dimension-shift を公開しました。のメモ

前提として、どちらも単一のヒープに全体が連続したメモリーアドレスを持つバッファーを扱う、という事があります。そのうえで、バッファーを任意次元に再解釈します。

dimension_shiftable_buffer

実行時に任意の次元にバッファーの解釈を変更したビューを用いてバッファーを扱える、そういうものです。こちらの実装はすべて Safe です。 unsafe せずに Vec<T> を実行時に任意次元へ再解釈する方法は思いつかなかったので getter(get,pop,remove)/setter(push,append) と for_each を用意しました。こちらの利点は「実行時に」「任意次元へ」です。

// make a 2d-empty DimensionShiftableBuffer
let mut dsb = DimensionShiftableBuffer::<u8>::new(vec![], 2).unwrap();
// push a 2d-datum
dsb.push(&[0u8, 1]).unwrap();
// push a 2d-datum
dsb.push(&[2u8, 3]).unwrap();
// append a 2d-datum sequence
dsb.append(&[4u8, 5, 6, 7, 8, 9, 10, 11]).unwrap();
for index in 0..dsb.len().unwrap()
{
 // get a 2d slice
 assert_eq!(dsb.get(index).unwrap(), &[index as u8 * 2, index as u8 * 2 + 1]);
}
// shift dimension to 3 from 2
dsb.shift_dimension(3).unwrap();
// push a 3d-datum
dsb.push(&[12u8, 13, 14]).unwrap();
// get a 3d-datum
assert_eq!(dsb.get(0).unwrap(), &[0u8, 1, 2]);
assert_eq!(dsb.get(1).unwrap(), &[3u8, 4, 5]);
assert_eq!(dsb.get(2).unwrap(), &[6u8, 7, 8]);
assert_eq!(dsb.get(3).unwrap(), &[9u8, 10, 11]);
assert_eq!(dsb.get(4).unwrap(), &[12u8, 13, 14]);
// get a linear slice
let linear_slice = dsb.as_slice();
assert_eq!(linear_slice, &[0u8, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]);

vec-dimension-shift

こちらは翻訳時にN次元から実際に対応する具体的な次元の次元再解釈機能を選択的に Vec<T> へ付与する trait 集です。 usize 次元のすべてのパターンを lib に埋め尽くすわけにはいかないので、2..16次元は features で選択的に使用可能に、 default で 2,3,4 次元の traits を定義としつつ、 make_vec_dimension_shift_n_dimension! マクロをユーザーが任意に呼べるように pub り、 crate のユーザーが任意に欲しい次元を選択的に扱えるようにしています。

こっちの中身は黒魔術と unsafe でできています。次元再解釈時の境界チェック、直接の変換が不可能な場合のErr|truncate|paddingなど基本的には安全に使いやすいように実装していますが、N次元から1次元化するための flatten の実装では Vec の実装とメモリーレイアウトをにゃーんしたりしていたりします。

use vec_dimension_shift::{
 VecDimensionShift2D,
 VecDimensionShift2DFlatten,
 VecDimensionShift3D,
 VecDimensionShift3DFlatten
};

fn d2_and_d3()
{
 let original = vec![0.0, 1.1, 2.2, 3.3, 4.4, 5.5];
 dbg!(&original);

 let mut d2_shifted = original.as_2d_array().unwrap();
 dbg!(&d2_shifted);
 assert_eq!(d2_shifted[0], [0.0, 1.1]);
 assert_eq!(d2_shifted[1], [2.2, 3.3]);
 assert_eq!(d2_shifted[2], [4.4, 5.5]);
 d2_shifted[1][1] = -1.0;

 let flatten = d2_shifted.as_flatten();
 dbg!(&flatten);

 let mut d3_shifted = flatten.as_3d_array().unwrap();
 dbg!(&d3_shifted);
 assert_eq!(d3_shifted[0], [0.0, 1.1, 2.2]);
 assert_eq!(d3_shifted[1], [-1.0, 4.4, 5.5]);
 d3_shifted[1][1] = -2.0;

 let flatten = d3_shifted.as_flatten();
 dbg!(&flatten);

 assert_eq!(flatten, vec![0.0, 1.1, 2.2, -1.0, -2.0, 5.5])
}

ちなみに、 1D -> 2D とした後に 1D へ flattening せず、 1D -> 2D -> 3D と次元再解釈すると、

use vec_dimension_shift::make_vec_dimension_shift_n_dimension;

fn n_dimension_macro_generator()
{
 make_vec_dimension_shift_n_dimension! { VecDimensionShift2D, VecDimensionShift2DFlatten, as_2d_array_no_check, to_2d_array_no_check, as_2d_array, to_2d_array, as_2d_array_truncate, to_2d_array_truncate, as_2d_array_padding, to_2d_array_padding, 2 }
 make_vec_dimension_shift_n_dimension! { VecDimensionShift3D, VecDimensionShift3DFlatten, as_3d_array_no_check, to_3d_array_no_check, as_3d_array, to_3d_array, as_3d_array_truncate, to_3d_array_truncate, as_3d_array_padding, to_3d_array_padding, 3 }

 let original = vec![0.0, 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10, 11.11];
 dbg!(&original);

 let d2 = original.as_2d_array().unwrap();
 assert_eq!(d2[0], [0.0, 1.1]);
 assert_eq!(d2[1], [2.2, 3.3]);
 assert_eq!(d2[2], [4.4, 5.5]);
 assert_eq!(d2[3], [6.6, 7.7]);
 assert_eq!(d2[4], [8.8, 9.9]);
 assert_eq!(d2[5], [10.10, 11.11]);
 dbg!(&d2);

 let d3 = d2.as_3d_array().unwrap();
 assert_eq!(d3[0], [[0.0, 1.1], [2.2, 3.3], [4.4, 5.5]]);
 assert_eq!(d3[1], [[6.6, 7.7], [8.8, 9.9], [10.10, 11.11]]);
 dbg!(&d3);
}

こういう多N次元(N次元×N次元×…)も作れます。