如何迭代或映射元组?
How to iterate or map over tuples?
我最初的问题是将不同类型的元组转换为字符串。在 Python 中,这将类似于:
>> a = ( 1.3, 1, 'c' )
>> b = map( lambda x: str(x), a )
['1.3', '1', 'c']
>> " ".join(b)
'1.3 1 c"
然而,Rust 不支持元组上的映射——仅支持类似向量的结构。显然,这是由于能够将不同的类型打包到一个元组中,并且缺少函数重载。另外,我找不到在运行时获取元组长度的方法。所以,我想,需要一个宏来进行转换。
一开始,我尝试匹配元组的头部,例如:
// doesn't work
match some_tuple {
(a, ..) => println!("{}", a),
_ => ()
}
那么,我的问题是:
- 是否可以使用库函数将元组转换为字符串,指定任意分隔符?
- 如何编写能够将函数映射到任意大小的元组的宏?
这是一个非常聪明的宏解决方案:
trait JoinTuple {
fn join_tuple(&self, sep: &str) -> String;
}
macro_rules! tuple_impls {
() => {};
( ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )* ) => {
impl<$typ, $( $ntyp ),*> JoinTuple for ($typ, $( $ntyp ),*)
where
$typ: ::std::fmt::Display,
$( $ntyp: ::std::fmt::Display ),*
{
fn join_tuple(&self, sep: &str) -> String {
let parts: &[&::std::fmt::Display] = &[&self.$idx, $( &self.$nidx ),*];
parts.iter().rev().map(|x| x.to_string()).collect::<Vec<_>>().join(sep)
}
}
tuple_impls!($( ($nidx => $ntyp), )*);
};
}
tuple_impls!(
(9 => J),
(8 => I),
(7 => H),
(6 => G),
(5 => F),
(4 => E),
(3 => D),
(2 => C),
(1 => B),
(0 => A),
);
fn main() {
let a = (1.3, 1, 'c');
let s = a.join_tuple(", ");
println!("{}", s);
assert_eq!("1.3, 1, c", s);
}
基本思想是我们可以获取一个元组并将其解包为 &[&fmt::Display]
。一旦我们有了它,就可以直接将每个项目映射到一个字符串中,然后将它们全部与分隔符组合起来。这是它自己的样子:
fn main() {
let tup = (1.3, 1, 'c');
let slice: &[&::std::fmt::Display] = &[&tup.0, &tup.1, &tup.2];
let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect();
let joined = parts.join(", ");
println!("{}", joined);
}
下一步是创建特征并针对特定情况实施它:
trait TupleJoin {
fn tuple_join(&self, sep: &str) -> String;
}
impl<A, B, C> TupleJoin for (A, B, C)
where
A: ::std::fmt::Display,
B: ::std::fmt::Display,
C: ::std::fmt::Display,
{
fn tuple_join(&self, sep: &str) -> String {
let slice: &[&::std::fmt::Display] = &[&self.0, &self.1, &self.2];
let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect();
parts.join(sep)
}
}
fn main() {
let tup = (1.3, 1, 'c');
println!("{}", tup.tuple_join(", "));
}
这只针对特定大小的元组实现了我们的特征,这在某些情况下可能没问题,但肯定还 酷。 standard library 使用一些宏来减少您需要做的复制和粘贴的苦差事,以获得更多的尺寸。我决定 更懒惰 并减少该解决方案的复制和粘贴!
我没有清楚明确地列出元组的每个大小和相应的 index/generic 名称,而是使我的宏递归。这样,我只需要列出一次,所有较小的尺寸都只是递归调用的一部分。不幸的是,我不知道如何让它向前发展,所以我只是把所有东西都翻转过来然后倒退。这意味着我们必须使用反向迭代器,这会导致效率低下,但总体而言,这应该是一个很小的代价。
对我帮助很大,因为它清楚地说明了使用递归和模式匹配后 Rust 的简单宏系统的强大功能。
我已经设法在它之上做了一些粗略的改进(也许能够使模式更简单一些,但它相当棘手),以便元组访问器->类型列表被宏反转在扩展到 trait 实现之前的编译时,这样我们就不再需要在运行时调用 .rev()
,从而提高效率:
trait JoinTuple {
fn join_tuple(&self, sep: &str) -> String;
}
macro_rules! tuple_impls {
() => {}; // no more
(($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
/*
* Invoke recursive reversal of list that ends in the macro expansion implementation
* of the reversed list
*/
tuple_impls!([($idx, $typ);] $( ($nidx => $ntyp), )*);
tuple_impls!($( ($nidx => $ntyp), )*); // invoke macro on tail
};
/*
* ([accumulatedList], listToReverse); recursively calls tuple_impls until the list to reverse
+ is empty (see next pattern)
*/
([$(($accIdx: tt, $accTyp: ident);)+] ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
tuple_impls!([($idx, $typ); $(($accIdx, $accTyp); )*] $( ($nidx => $ntyp), ) *);
};
// Finally expand into the implementation
([($idx:tt, $typ:ident); $( ($nidx:tt, $ntyp:ident); )*]) => {
impl<$typ, $( $ntyp ),*> JoinTuple for ($typ, $( $ntyp ),*)
where $typ: ::std::fmt::Display,
$( $ntyp: ::std::fmt::Display ),*
{
fn join_tuple(&self, sep: &str) -> String {
let parts = vec![self.$idx.to_string(), $( self.$nidx.to_string() ),*];
parts.join(sep)
}
}
}
}
tuple_impls!(
(9 => J),
(8 => I),
(7 => H),
(6 => G),
(5 => F),
(4 => E),
(3 => D),
(2 => C),
(1 => B),
(0 => A),
);
#[test]
fn test_join_tuple() {
let a = ( 1.3, 1, 'c' );
let s = a.join_tuple(", ");
println!("{}", s);
assert_eq!("1.3, 1, c", s);
}
我最初的问题是将不同类型的元组转换为字符串。在 Python 中,这将类似于:
>> a = ( 1.3, 1, 'c' )
>> b = map( lambda x: str(x), a )
['1.3', '1', 'c']
>> " ".join(b)
'1.3 1 c"
然而,Rust 不支持元组上的映射——仅支持类似向量的结构。显然,这是由于能够将不同的类型打包到一个元组中,并且缺少函数重载。另外,我找不到在运行时获取元组长度的方法。所以,我想,需要一个宏来进行转换。
一开始,我尝试匹配元组的头部,例如:
// doesn't work
match some_tuple {
(a, ..) => println!("{}", a),
_ => ()
}
那么,我的问题是:
- 是否可以使用库函数将元组转换为字符串,指定任意分隔符?
- 如何编写能够将函数映射到任意大小的元组的宏?
这是一个非常聪明的宏解决方案:
trait JoinTuple {
fn join_tuple(&self, sep: &str) -> String;
}
macro_rules! tuple_impls {
() => {};
( ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )* ) => {
impl<$typ, $( $ntyp ),*> JoinTuple for ($typ, $( $ntyp ),*)
where
$typ: ::std::fmt::Display,
$( $ntyp: ::std::fmt::Display ),*
{
fn join_tuple(&self, sep: &str) -> String {
let parts: &[&::std::fmt::Display] = &[&self.$idx, $( &self.$nidx ),*];
parts.iter().rev().map(|x| x.to_string()).collect::<Vec<_>>().join(sep)
}
}
tuple_impls!($( ($nidx => $ntyp), )*);
};
}
tuple_impls!(
(9 => J),
(8 => I),
(7 => H),
(6 => G),
(5 => F),
(4 => E),
(3 => D),
(2 => C),
(1 => B),
(0 => A),
);
fn main() {
let a = (1.3, 1, 'c');
let s = a.join_tuple(", ");
println!("{}", s);
assert_eq!("1.3, 1, c", s);
}
基本思想是我们可以获取一个元组并将其解包为 &[&fmt::Display]
。一旦我们有了它,就可以直接将每个项目映射到一个字符串中,然后将它们全部与分隔符组合起来。这是它自己的样子:
fn main() {
let tup = (1.3, 1, 'c');
let slice: &[&::std::fmt::Display] = &[&tup.0, &tup.1, &tup.2];
let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect();
let joined = parts.join(", ");
println!("{}", joined);
}
下一步是创建特征并针对特定情况实施它:
trait TupleJoin {
fn tuple_join(&self, sep: &str) -> String;
}
impl<A, B, C> TupleJoin for (A, B, C)
where
A: ::std::fmt::Display,
B: ::std::fmt::Display,
C: ::std::fmt::Display,
{
fn tuple_join(&self, sep: &str) -> String {
let slice: &[&::std::fmt::Display] = &[&self.0, &self.1, &self.2];
let parts: Vec<_> = slice.iter().map(|x| x.to_string()).collect();
parts.join(sep)
}
}
fn main() {
let tup = (1.3, 1, 'c');
println!("{}", tup.tuple_join(", "));
}
这只针对特定大小的元组实现了我们的特征,这在某些情况下可能没问题,但肯定还 酷。 standard library 使用一些宏来减少您需要做的复制和粘贴的苦差事,以获得更多的尺寸。我决定 更懒惰 并减少该解决方案的复制和粘贴!
我没有清楚明确地列出元组的每个大小和相应的 index/generic 名称,而是使我的宏递归。这样,我只需要列出一次,所有较小的尺寸都只是递归调用的一部分。不幸的是,我不知道如何让它向前发展,所以我只是把所有东西都翻转过来然后倒退。这意味着我们必须使用反向迭代器,这会导致效率低下,但总体而言,这应该是一个很小的代价。
我已经设法在它之上做了一些粗略的改进(也许能够使模式更简单一些,但它相当棘手),以便元组访问器->类型列表被宏反转在扩展到 trait 实现之前的编译时,这样我们就不再需要在运行时调用 .rev()
,从而提高效率:
trait JoinTuple {
fn join_tuple(&self, sep: &str) -> String;
}
macro_rules! tuple_impls {
() => {}; // no more
(($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
/*
* Invoke recursive reversal of list that ends in the macro expansion implementation
* of the reversed list
*/
tuple_impls!([($idx, $typ);] $( ($nidx => $ntyp), )*);
tuple_impls!($( ($nidx => $ntyp), )*); // invoke macro on tail
};
/*
* ([accumulatedList], listToReverse); recursively calls tuple_impls until the list to reverse
+ is empty (see next pattern)
*/
([$(($accIdx: tt, $accTyp: ident);)+] ($idx:tt => $typ:ident), $( ($nidx:tt => $ntyp:ident), )*) => {
tuple_impls!([($idx, $typ); $(($accIdx, $accTyp); )*] $( ($nidx => $ntyp), ) *);
};
// Finally expand into the implementation
([($idx:tt, $typ:ident); $( ($nidx:tt, $ntyp:ident); )*]) => {
impl<$typ, $( $ntyp ),*> JoinTuple for ($typ, $( $ntyp ),*)
where $typ: ::std::fmt::Display,
$( $ntyp: ::std::fmt::Display ),*
{
fn join_tuple(&self, sep: &str) -> String {
let parts = vec![self.$idx.to_string(), $( self.$nidx.to_string() ),*];
parts.join(sep)
}
}
}
}
tuple_impls!(
(9 => J),
(8 => I),
(7 => H),
(6 => G),
(5 => F),
(4 => E),
(3 => D),
(2 => C),
(1 => B),
(0 => A),
);
#[test]
fn test_join_tuple() {
let a = ( 1.3, 1, 'c' );
let s = a.join_tuple(", ");
println!("{}", s);
assert_eq!("1.3, 1, c", s);
}