OFF 文件是纯文本文件,可用于记录三维(或更高维)多面体信息,其根本格局为
OFF
点数 面数 边数
点表
面表
例如
OFF
4 4 6
0 0 0
1 0 0
0 1 0
0 0 1
3 0 1 2
3 0 1 3
3 1 2 3
3 0 2 3
记录的是四个顶点定义的四面体,其边数为 6。点表记录了四个顶点的坐标信息
0 0 0
1 0 0
0 1 0
0 0 1
面表记录了四个面片的信息
3 0 1 2
3 0 1 3
3 1 2 3
3 0 2 3
面表的第一列数字示意每个面片由多少个顶点形成,后继各列为点表索引号,例如
3 0 1 3
示意面片由 3 个顶点形成,这些顶点在点表中的索引号别离是 0, 1 和 3,别离对应点表中第一个点,第 2 个点和第 4 个点。
留神,OFF 文件头局部的边数能够设为 0,并不影响多面体构造的正确性。
Mesh 构造体
在思考如何解析 OFF 文件之前,须要先确定内存中可能用于表白多面体构造的数据类型,不然解析工作难以落到实处,该数据类型可定义为构造体类型:
struct Mesh {
points: Vec<Vec<f64>>, // 点表
facets: Vec<Vec<usize>> // 面表
}
其中用于示意点和面片的向量(Vec
)实例位于堆上,因而通常不用放心 Mesh
实例造成栈溢出的状况。本来我想将 Mesh
定义为
struct Point {
n: usize, // 维度
body: Box<[f64]> // 坐标
}
struct Facet {
n: usize, // 顶点个数
vertices: Box<[usize]> // 顶点索引
}
struct Mesh {
points: Vec<Point>, // 点表
facets: Vec<Facet> // 面表
}
上述定义逻辑上更合乎多面体模型,然而应用 Box
指针无奈为 Point
和 Facet
动态分配堆空间。实际上该问题已在「点」的那一节的「n 维点」局部呈现过了。
以下代码为 Mesh
定义了 new
和 display
办法:
impl Mesh {fn new() -> Mesh {return Mesh {points: Vec::new(), facets: Vec::new()};
}
fn display(&self) {println!("OFF");
println!("{0}, {1}", self.points.len(), self.facets.len());
for x in &(self.points) {let n = x.len();
for i in 0 .. n {
if i == n - 1 {println!("{}", x[i]);
} else {print!("{}", x[i]);
}
}
}
for f in &(self.facets) {let n = f.len();
print!("{}", n);
for i in 0 .. n {
if i == n - 1 {println!("{}", f[i]);
} else {print!("{}", f[i]);
}
}
}
}
}
其中,&(self.points)
示意对 Mesh
构造体的 points
成员的援用,也可写为 &self.points
,因为 .
运算符的优先级比 &
高。不过,此处为何须要援用呢?
汇合类型的援用
对于向量,在应用 for ... in
语法对其进行遍历时,幕后需将向量实例作为参数传给迭代器。有三种传参模式:
- 传向量实例;
- 传向量实例的援用;
- 传向量实例的可变援用。
因为上述代码中的 points
是 Mesh
构造体的成员,它的所有权假使产生转移会导致 Mesh
失去这个成员,rustc 不容许构造体的定义被如此随便地毁坏,故而报错,而应用传援用的形式则是容许的。
还有一个问题,在遍历向量的过程中,有拜访向量元素的代码:
for x in &(self.points) {let n = x.len();
for i in 0 .. n {
if i == n - 1 {println!("{}", x[i]);
} else {print!("{}", x[i]);
}
}
}
其中 x
是 Vec<f64>
实例还是它的援用?答案是后者。事实上,遍历向量的过程中,获取向量元素的形式也相应分为三种:
- 传向量实例时,获取的是向量元素的所有权;
- 传向量实例的援用,获取的是向量元素的援用;
- 传向量实例的可变援用,获取的是向量元素的可变援用。
泛型
迭代点集和面片集的过程简直一样,亦即
for x in &(self.points) {let n = x.len();
for i in 0 .. n {
if i == n - 1 {println!("{}", x[i]);
} else {print!("{}", x[i]);
}
}
}
for f in &(self.facets) {let n = f.len();
print!("{}", n);
for i in 0 .. n {
if i == n - 1 {println!("{}", f[i]);
} else {print!("{}", f[i]);
}
}
}
假使假如面表的输入过程里没有 `
print!("{}", n);
便可将点表和的输入过程对立为一个泛型函数:
fn display_matrix<T>(v: &Vec<T>) {
for x in v {let n = x.len();
for i in 0 .. n {
if i == n - 1 {println!("{}", x[i]);
} else {print!("{}", x[i]);
}
}
}
}
之所以为这个函数名取名为 display_matrix
,是因为元素为向量的向量,不就是矩阵吗?
而后,可将 Mesh
的 display
办法定义为
impl Mesh {fn display(&self) {println!("OFF");
println!("{0}, {1}", self.points.len(), self.facets.len());
display_matrix(&self.points);
display_matrix(&self.facets);
}
}
后果会受到 rustc 的毒打:
error[E0599]: no method named `len` found for reference `&T` in the current scope
error[E0608]: cannot index into a value of type `&T`
error[E0608]: cannot index into a value of type `&T`
第一个谬误是,rustc 认为 display_matrix
的泛型参数 T
没有 len
办法。后两个谬误是 rustc 认为 T
不能用下标取得值。换言之,rustc 心愿我可能「证实」T
既有 len
办法,也能用通过下标取得值。rustc 是蠢的,它不晓得在我的例子里,T
为 Vec
类型既有 len
办法也能通过下标取得值。
证实各种向量都有长度
证实的路径是基于个性。例如,上面是一份残缺的程序代码,它可能为一个泛型函数证实 T
有 len
办法:
trait HasLength {fn len(&self) -> usize;
}
impl HasLength for Vec<usize> {fn len(&self) -> usize {return self.len();
}
}
fn display_vec_len<T: HasLength>(v: &T) {println!("{}", v.len());
}
fn main() {let mut a: Vec<usize> = Vec::new();
a.push(1);
a.push(2);
display_vec_len(&a);
}
上述代码,首先定义了一个名为 HasLength
的 Trait,而后为 Vec<usize>
类型实现了该 Trait。在泛型函数 display_vec_len
中将泛型参数 T
限定为实现了 HasLength
Trait 的类型。
如果将 display_vec_len
函数利用于 Vec<f64>
类型呢?须要为 Vec<f64>
也实现 HasLength
Trait。于是一个新的问题呈现了,Trait 能不能也泛型化呢?这样便可无需为每种具体的 Vec<T>
类型定义 HasLength
Trait 了。试试看:
trait HasLength {fn len(&self) -> usize;
}
impl<T> HasLength for Vec<T> {fn len(&self) -> usize {return self.len();
}
}
fn display_vec_len<T: HasLength>(v: &T) {println!("{}", v.len());
}
fn main() {let mut a: Vec<usize> = Vec::new();
a.push(1);
a.push(2);
display_vec_len(&a);
let mut b: Vec<f64> = Vec::new();
b.push(0.1);
b.push(0.2);
b.push(0.3);
display_vec_len(&b);
}
后果合乎预期。
下标运算
通过下标拜访数组和向量的元素是 Rust 的语法糖,而该语法糖是基于规范库定义的 std::ops::Index
Trait 实现的。例如
fn main() {let a = vec![1, 2, 3, 4];
println!("{}", a[2]);
}
等同于
use std::ops::Index;
fn main() {let a = vec![1,2,3,4];
println!("{}", a.index(2));
}
对于以下泛型函数
fn matrix_index<T>(v: &Vec<T>, i: usize, j: usize) -> 不知该返回什么类型 {return &v[i][j];
}
若其调用代码为
let a = vec![vec![1, 2], vec![3, 4]];
println!("{}", matrix_index(&a, 0, 1));
这里有两个问题。首先如何向 rustc 证实 matrix_index
的泛型参数 T
反对下标运算呢?其次 matrix_index
的返回类型是什么?答案是
fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display
where <T as Index<usize>>::Output: Sized,
<T as Index<usize>>::Output: Display {return &v[i][j];
}
若想晓得这个答案从何而来,须要在谬误中逐渐尝试。
首先,尝试证实 T
实现了 std::ops::Index
Trait:
use std::ops::Index;
fn matrix_index<T: Index>(v: &Vec<T>, i: usize, j: usize) -> 不知该返回什么类型 {return &v[i][j];
}
然而 rust 会报错,并给出整改倡议:
rror[E0107]: missing generics for trait `Index`
add missing generic argument
|
| fn matrix_index<T: Index<Idx>>(v: &Vec<T>, ... ... ...
看起来 Index
Trait 用起来并不容易。去 Rust 规范库文档搜寻一番,发现 Index
Trait 的定义 [1] 为
pub trait Index<Idx> where
Idx: ?Sized, {
type Output: ?Sized;
fn index(&self, index: Idx) -> &Self::Output;
}
那么 Vec<T>
有没有实现这个 Trait 呢?实现了,源码为
impl<T, I: SliceIndex<[T]>, A: Allocator> Index<I> for Vec<T, A> {
type Output = I::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {Index::index(&**self, index)
}
}
上述代码里有许多语法是我从未见过的,联合 rustc 给出的倡议,只能看出 Index
Trait 用于证实泛型参数反对下标运算的形式是须要为该 Trait 提供一个类型参数,该参数对于 Vec<T>
类型而言,应该是 SliceIndex<[T]>
,后者又是一个 Trait。
再试试看:
use std::ops::Index;
use std::slice::SliceIndex;
fn matrix_index<T: Index<SliceIndex<[T]>>>(v: &Vec<T>, i: usize, j: usize) -> 不知该返回什么类型 {return &v[i][j];
}
对于上述代码,rustc 给出正告:
warning: trait objects without an explicit `dyn` are deprecated
|
| fn matrix_index<T: Index<SliceIndex<[T]>>>(v: &Vec<T>, ... ... ...
| ^^^^^^^^^^^^^^^
依照 rustc 的倡议,将 matrix_index
批改为
fn matrix_index<T: Index<dyn SliceIndex<[T]>>>(v: &Vec<T>, i: usize, j: usize) -> 不知该返回什么类型 {return &v[i][j];
}
捕风捉影,此时我并不知道 dyn
的用处是什么,权且先听命于 rustc。如此这般之后,阿弥陀佛,咱们的 rustc 终于能少说两句了,最初它只是凉飕飕地说:
error[E0191]: the value of the associated type `Output` (from trait `SliceIndex`) must be specified
|
| ...x_index<T: Index<dyn SliceIndex<[T]>>>(v: &Vec<T>, ... ... ...
| ^^^^^^^^^^^^^^^ help: specify the associated type: `SliceIndex<[T], Output = Type>`
好吧,再试试:
fn matrix_index<T: Index<dyn SliceIndex<[T], Output = usize>>>(v: &Vec<T>, i: usize, j: usize) -> 不知该返回什么类型 {return v[i][j];
}
当初给 matrix_index
增加的这些系统曾经差不多像天书了,即便如此,rustc 不仅没有消停,反而疯掉了,报出的错误信息多得让正经人看不下去。于是我只好灵光一闪,大略猜到了 Index<...>
这里的空应该填什么了。要填的应该是实现了 SliceIndex<[T]>
Trait 的类型。对于我的需要,下标的类型应该是 usize
,那么 usize
是否实现了 SliceIndex<[T]>
Trait 呢?答案是实现了,有规范库源码为证 [2]。据此,应将 matrix_index
定义为
fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> 不知该返回什么类型 {return &v[i][j];
}
当初,rustc 终于不再批评泛型参数 T
有什么问题了,它的注意力转移到 matrix_index
的返回值类型上了,报错信息为
error[E0412]: cannot find type ` 不知该返回什么类型 ` in this scope
|
| ...size, j: usize) -> 不知该返回什么类型 {| ^^^^^^^^^^^^^^^^^^ help: a trait with a similar name exists: `AsMut
我决定忽视 rustc 给出的 help,起因是,我不懂它在说什么。matrix_index
是泛型函数,它承受的参数是个矩阵——元素为向量的向量,矩阵的元素并不是特定的某种类型。我都不晓得的事,rustc 凭什么晓得呢?此外,我也切实看不出 不晓得该返回什么类型
跟 AsMut
这两个名字有啥类似的中央。
不过,rustc 的上述倡议还是给了我一些启发,函数的返回类型能够是 Trait [3]!matrix_index
返回值的类型应该是什么 Trait 呢?因为 matrix_index
的返回后果是传递给 println!
的。基于我对 Rust 的肤浅意识,println!
所承受的参数必须提供 Display
Trait 的实现。因而,无妨试试将 matrix_index
的返回值类型设置成 &impl Display
——实现了 Display
Trait 类型的援用——,亦即
use std::ops::Index;
use std::fmt::Display;
fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display {return &v[i][j];
}
rustc 看了我的灵光一闪的做法后,又发表了一通批评和倡议:
error[E0277]: `<T as Index<usize>>::Output` doesn't implement `std::fmt::Display`
|
| ...i: usize, j: usize) -> impl Display {
| ^^^^^^^^^^^^ `<T as Index<usize>>::Output` cannot be formatted with the default formatter
... ... ...
| fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> impl Display
| where <T as Index<usize>>::Output: std::fmt::Display {| ++++++++++++++++++++++++++++++++++++++++++++++++++++
对其倡议,姑且一试:
fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize) -> &impl Display
where <T as Index<usize>>::Output: Display {return &v[i][j];
}
rustc 又给出了新的错误报告以及倡议:
error[E0277]: the size for values of type `<T as Index<usize>>::Output`
cannot be known at compilation time
|
| fn matrix_index<T: Index<usize>>(v: &Vec<T>, i: usize, j: usize)
| -> &impl Display
| ^^^^^^^^^^^^^ doesn't hav a size known at compile-time
= help: the trait `Sized` is not implemented for `<T as Index<usize>>::Output`
... ... ...
help: consider further restricting the associated type
|
| where <T as Index<usize>>::Output: Display, <T as Index<usize>>::Output: Sized {| ++++++++++++++++++++++++++++++++++++
于是,就有了本节一开始的那个答案。至此,我切实不知该如何赞美 rustc 了。兴许要证实一个什么货色是什么货色,始终都是世界级难题吧。在人生中,请务必记住,不要去证实本人,否则本人的人生将像 Rust 代码一样轻便。不过,还有一种可能,Rust 还有什么更好的方法能将本节一开始的代码写得更优雅,只是我不晓得,当初只能心愿如此。
繁文缛节
当初能够写出简直可能对立各种元素为向量的向量(矩阵)的显示函数了,
use std::ops::Index;
use std::fmt::Display;
trait HasLength {fn len(&self) -> usize;
}
impl<T> HasLength for Vec<T> {fn len(&self) -> usize {return self.len();
}
}
fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>)
where <T as Index<usize>>::Output: Display,
<T as Index<usize>>::Output: Sized {
for x in v {let n = x.len();
for i in 0 .. n {
if i == n - 1 {println!("{}", x[i]);
} else {print!("{}", x[i]);
}
}
}
}
impl Mesh {fn display(&self) {println!("OFF");
println!("{0}, {1}", self.points.len(), self.facets.len());
display_matrix(&self.points);
display_matrix(&self.facets);
}
}
感觉代码量比一开始未将点表和面表遍历过程对立的 display
办法所用的代码都要多,所以……我只是为了略加深刻地学习一下 Trait 和泛型啊。
Display
既然已对 Trait 有所理解,为 Mesh
实现 display
办法,不如为 Mesh
实现 Display
Trait。假使依据 [4] 所载办法,很快会发现写不上来,因为 Mesh
是复合构造,须要屡次 write!
,然而能搜到的实现 Display
的示例皆是单次 执行 write!
。一个可行的计划是,先用 String
类型的字符串收集所有要显示的信息,而后对该字符串执行 write!
。
fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>) -> String
where <T as Index<usize>>::Output: Display,
<T as Index<usize>>::Output: Sized {let mut s = String::new();
for x in v {let n = x.len();
for i in 0 .. n {
if i == n - 1 {s += format!("{}\n", x[i]).as_str();} else {s += format!("{}", x[i]).as_str();}
}
}
return s;
}
impl Display for Mesh {fn fmt(&self, f: &mut Formatter) -> Result {let mut s = String::new();
s += format!("OFF\n").as_str();
s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
s += display_matrix(&self.points).as_str();
s += display_matrix(&self.facets).as_str();
write!(f, "{}", s)
}
}
此处要画的重点是 &str
和 String
的分割和区别。这些我已在 rhamal.pdf [5] 有所波及。
闭包
请留神,在希图以泛型函数对立点表和面表的输入过程时,前提是面表和点表的输入过程齐全一样,但实际上并不一样。输入面的信息时,不仅要输入面表里向量的值(面片顶点在点表中的下标),还要输入面片顶点个数。这一差别能够通过为 display_matrix
减少一个参数的形式打消。例如
fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
prefix: 类型待定 ) -> String
where <T as Index<usize>>::Output: Display,
<T as Index<usize>>::Output: Sized {let mut s = String::new();
for x in v {let n = x.len();
s += prefix(x).as_str();
for i in 0 .. n {
if i == n - 1 {s += format!("{}\n", x[i]).as_str();} else {s += format!("{}", x[i]).as_str();}
}
}
return s;
}
prefix
的意思是在输入的每个点或面片信息之前减少一些信息,然而它应该是何种类型,值得探讨,而且这可能是学习更多的 Rust 的好机会。
首先,如果将 prefix
设置为 bool
类型会怎么?试试看
fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>, prefix: bool) -> String
where <T as Index<usize>>::Output: Display,
<T as Index<usize>>::Output: Sized {let mut s = String::new();
for x in v {let n = x.len();
if prefix {s += format!("{}", n).as_str();}
for i in 0 .. n {
if i == n - 1 {s += format!("{}\n", x[i]).as_str();} else {s += format!("{}", x[i]).as_str();}
}
}
return s;
}
相应地,需将 Mesh
的 Display
Trait 的实现批改为
impl Display for Mesh {fn fmt(&self, f: &mut Formatter) -> Result {let mut s = String::new();
s += format!("OFF\n").as_str();
s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
s += display_matrix(&self.points, false).as_str();
s += display_matrix(&self.facets, true).as_str();
write!(f, "{}", s)
}
}
上述实现可能解决问题,然而代码语义不好,并且灵活性太差。试想,如果在输入的面片信息之前要输入的信息不是面片顶点个数,而是由 display_matrix
函数的使用者本人决定的其余信息呢?更好的方法是用函数类型作为 prefix
的类型,亦即
fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
prefix: impl Fn(&T) -> String) -> String
where <T as Index<usize>>::Output: Display,
<T as Index<usize>>::Output: Sized {let mut s = String::new();
for x in v {let n = x.len();
s += prefix(x).as_str();
for i in 0 .. n {
if i == n - 1 {s += format!("{}\n", x[i]).as_str();} else {s += format!("{}", x[i]).as_str();}
}
}
return s;
}
而后应用闭包作为 display_matrix
的参数 prefix
的值:
impl Display for Mesh {fn fmt(&self, f: &mut Formatter) -> Result {let mut s = String::new();
s += format!("OFF\n").as_str();
s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
s += display_matrix(&self.points, |_| "".to_string()).as_str();
s += display_matrix(&self.facets, |x| format!("{}", x.len())).as_str();
write!(f, "{}", s)
}
}
上述代码可能工作,然而效率不高。因为输入点表时,display_matrix
函数本不须要执行 prefix
函数,而当初不得不执行,而且所执行的 prefix
返回的是空字串。在输入点表时,如何令 display_matrix
晓得 prefix
是空值,不须要理会呢?只需令 prefix
函数带有状态即可,上面给出一个可行计划。
首先定义一个泛型的构造体:
struct Prefix<T> {
status: bool,
body: fn(&T) -> String
}
impl<T> Prefix<T> {fn new() -> Prefix<T> {Prefix{status: false, body: |_| "".to_string()}
}
}
而后将 display_matrix
改写为
fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
prefix: &Prefix<T>) -> String
where <T as Index<usize>>::Output: Display,
<T as Index<usize>>::Output: Sized {let mut s = String::new();
for x in v {let n = x.len();
if prefix.status {s += (prefix.body)(x).as_str();}
for i in 0 .. n {
if i == n - 1 {s += format!("{}\n", x[i]).as_str();} else {s += format!("{}", x[i]).as_str();}
}
}
return s;
}
最初将 Mesh
的 Display
实现改写为
impl Display for Mesh {fn fmt(&self, f: &mut Formatter) -> Result {let mut s = String::new();
s += format!("OFF\n").as_str();
s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
s += display_matrix(&self.points, &Prefix::new()).as_str();
s += display_matrix(&self.facets, &Prefix{status: true,
body: |x| format!("{}", x.len())}).as_str();
write!(f, "{}", s)
}
}
OFF 文件 -> Mesh 构造体
经验了几个大插曲,当初能够回归正题了。以下代码可能关上 OFF 文件,
use std::path::Path;
use std::fs::File;
let path = Path::new("foo.off");
let file = File::open(path).unwrap();
看到 unwrap
就该想到与之相干的类型可能是 Result
或 Option
。Rust 规范库总是放心某个函数的返回值太过于间接而导致世界覆灭,因此十分热衷于将函数的返回值先圈禁,再酌情开释。
以下代码可能读取 OFF 文件的第一行并对其核验:
use std::io::{BufRead, BufReader};
let buf = BufReader::new(file);
let mut lines_iter = buf.lines().map(|l| l.unwrap());
// 核验 OFF 文件第一行内容是否为 "OFF"
// 但凡实现了 Eq Trait 的类型,皆能用 assert_eq! 比拟是否相等
assert_eq!(lines_iter.next(), Some(String::from("OFF")));
BufReader
的 lines
办法返回的是迭代器,其类型为 Lines
构造体。Lines
构造体提供了 Iterator
Trait(迭代器)的实现。迭代器可用于遍历数据序列。Iterator
的 map
办法可能承受函数作为参数并将其利用于迭代器所拜访的数据。上述代码中,map
办法承受的是闭包 |l| l.unwrap()
。Iterator
的 next
办法将迭代器向后挪动,令其指向下一个元素并将其作为返回值。对迭代器执行第一次 next
时,失去的值便是迭代器所拜访的数据序列的第一个值,假使继续执行 next
办法,便可顺次拜访数据序列后续的值,该过程通常隐匿于 for ... in
语法背地。假使应用 for ... in
语法,遍历文件内容的每一行,只需
for line in buf.lines() {println!("{}", line.unwrap());
}
因为 OFF 文件的第二行定义了多面体构造的点表和面表的长度,决定了遍历 OFF 文件内容的过程须要由计数器管制,因而为不便取得点表和面表,显式调用迭代器的的 next
办法较 for ... in
语法更为适合。
接下来,解析 OFF 文件的第二行,取得点表和面表的长度信息:
let second_line = lines_iter.next().unwrap();
let mut split = second_line.split_whitespace();
let n_of_points: usize = split.next().unwrap().parse().unwrap();
let n_of_facets: usize = split.next().unwrap().parse().unwrap();
因为 line_iter
迭代器返回值是蕴含着 String
类型实例的援用,对其 unwrap
后,便失去该援用。String
的 split_whitespace
办法可基于空白字符对 String
类型实例进行宰割。上一章我用了一个简略的状态机实现的 split_str
是针对 &str
类型的,宰割后果保留于 Vec<(usize, usize)>
。String
的 split_whitespace
办法返回的并非字符串的宰割后果,而是迭代器。假使应用我实现的 split_str
函数实现上述代码等同性能,只需
let second_line = lines_iter.next().unwrap();
let second_line_as_str = second_line.as_str();
let split = split_str(second_line_as_str);
let n_of_points: usize = second_line_as_str[(split[0].0 .. split[0].1)].parse().unwrap();
let n_of_facets: usize = second_line_as_str[(split[1].0 .. split[1].1)].parse().unwrap();
相形之下,还是用 String
的 split_whitespace
办法吧。
有了点表和面表的长度,便可用迭代器持续读取 OFF 文件的剩下内容,解析点集信息和面集信息:
let mut mesh = Mesh::new();
for _i in 0 .. n_of_points {let line = lines_iter.next().unwrap();
let mut p: Vec<f64> = Vec::new();
for x in line.split_whitespace() {p.push(x.parse().unwrap());
}
mesh.points.push(p);
}
for _i in 0 .. n_of_facets {let line = lines_iter.next().unwrap();
let mut f: Vec<usize> = Vec::new();
let mut split = line.split_whitespace();
let n:usize = split.next().unwrap().parse().unwrap(); // 疏忽第一个元素
for x in split {f.push(x.parse().unwrap());
}
assert_eq!(n, f.len());
mesh.facets.push(f);
}
留神,在上述 for ... in
语句中,变量 i
在迭代过程中并未被应用,rustc 倡议用 _i
代替。此外,在结构面表的过程中,因为文件内容的第一个数字是面片的顶点数,该信息我仅用于检测面片解析后果的顶点数量是否正确。
基于 OFF 文件结构的 Mesh
实例,其正确性如何验证呢?因为 Mesh
类型曾经实现了 Display
Trait,无妨将其内容输入,
print!("{}", mesh);
而后将输入内容与 OFF 文件内容予以比对。
小结
use std::ops::Index;
use std::fmt::{Result, Formatter, Display};
use std::path::Path;
use std::fs::File;
use std::io::{BufRead, BufReader};
struct Mesh {
points: Vec<Vec<f64>>, // 点表
facets: Vec<Vec<usize>> // 面表
}
trait HasLength {fn len(&self) -> usize;
}
impl<T> HasLength for Vec<T> {fn len(&self) -> usize {return self.len();
}
}
struct Prefix<T> {
status: bool,
body: fn(&T) -> String
}
impl<T> Prefix<T> {fn new() -> Prefix<T> {Prefix{status: false, body: |_| "".to_string()}
}
}
fn display_matrix<T: HasLength + Index<usize>>(v: &Vec<T>,
prefix: &Prefix<T>) -> String
where <T as Index<usize>>::Output: Display,
<T as Index<usize>>::Output: Sized {let mut s = String::new();
for x in v {let n = x.len();
if prefix.status {s += (prefix.body)(x).as_str();}
for i in 0 .. n {
if i == n - 1 {s += format!("{}\n", x[i]).as_str();} else {s += format!("{}", x[i]).as_str();}
}
}
return s;
}
impl Display for Mesh {fn fmt(&self, f: &mut Formatter) -> Result {let mut s = String::new();
s += format!("OFF\n").as_str();
s += format!("{0}, {1}\n", self.points.len(), self.facets.len()).as_str();
s += display_matrix(&self.points, &Prefix::new()).as_str();
s += display_matrix(&self.facets, &Prefix{status: true,
body: |x| format!("{}", x.len())}).as_str();
write!(f, "{}", s)
}
}
impl Mesh {fn new() -> Mesh {return Mesh {points: Vec::new(), facets: Vec::new()};
}
fn parse(&mut self, path: &str) {let path = Path::new(path);
let file = File::open(path).unwrap();
let buf = BufReader::new(file);
let mut lines_iter = buf.lines().map(|l| l.unwrap());
assert_eq!(lines_iter.next(), Some(String::from("OFF")));
let second_line = lines_iter.next().unwrap();
let mut split = second_line.split_whitespace();
let n_of_points: usize = split.next().unwrap().parse().unwrap();
let n_of_facets: usize = split.next().unwrap().parse().unwrap();
for _i in 0 .. n_of_points {let line = lines_iter.next().unwrap();
let mut p: Vec<f64> = Vec::new();
for x in line.split_whitespace() {p.push(x.parse().unwrap());
}
self.points.push(p);
}
for _i in 0 .. n_of_facets {let line = lines_iter.next().unwrap();
let mut f: Vec<usize> = Vec::new();
let mut split = line.split_whitespace();
let n:usize = split.next().unwrap().parse().unwrap(); // 疏忽第一个元素
for x in split {f.push(x.parse().unwrap());
}
assert_eq!(n, f.len());
self.facets.push(f);
}
}
}
fn main() {let mut mesh = Mesh::new();
mesh.parse("foo.off");
print!("{}", mesh);
}
参考
[1] https://doc.rust-lang.org/std…
[2] https://doc.rust-lang.org/src…
[3] https://doc.rust-lang.org/sta…
[4] https://rustwiki.org/zh-CN/ru…
[5] https://gitee.com/garfileo/rh…