如何在元组上迭代或映射? [英] How to iterate or map over tuples?
问题描述
我最初的问题是将不同类型的元组转换为字符串.在Python中,这类似于:
My initial problem was to convert a tuple of different types to a string. In Python, this would be something like:
>> a = ( 1.3, 1, 'c' )
>> b = map( lambda x: str(x), a )
['1.3', '1', 'c']
>> " ".join(b)
'1.3 1 c"
但是,Rust不支持元组上的映射-仅支持类似矢量的结构.显然,这是由于能够将不同类型打包到一个元组中,并且缺少函数重载.另外,我找不到在运行时获取元组长度的方法.因此,我想可能需要一个宏来进行转换.
Yet, Rust doesn't support map on tuples -- only on vector-like structures. Obviously, this is due to being able to pack different types into a tuple and the lack of function overloading. Also, I couldn't find a way to get the tuple length at runtime. So, I guess, a macro would be needed to do the conversion.
首先,我尝试匹配一个元组的头部,例如:
As a start, I tried to match the head of an tuple, something like:
// doesn't work
match some_tuple {
(a, ..) => println!("{}", a),
_ => ()
}
所以,我的问题:
- 是否可以使用库函数将元组转换为字符串,并指定任意分隔符?
- 如何编写宏以将函数映射到任意大小的元组?
推荐答案
这是一个过于聪明的宏解决方案:
Here's an overly-clever macro solution:
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]
.一旦有了这些,就可以直接将每个项目映射到一个字符串中,然后将它们全部与一个分隔符组合在一起.这是单独的样子:
The basic idea is that we can take a tuple and unpack it into a &[&fmt::Display]
. Once we have that, it's straight-forward to map each item into a string and then combine them all with a separator. Here's what that would look like on its own:
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);
}
下一步是创建一个特征并针对特定情况实现它:
The next step would be to create a trait and implement it for the specific case:
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(", "));
}
这仅针对特定大小的元组实现了我们的特征,这在某些情况下可能很好,但肯定还不强.标准库使用了一些宏,以减少繁琐的复制和粘贴操作,您需要这样做才能获得更大的尺寸.我决定变得更懒惰并减少该解决方案的复制粘贴!
This only implements our trait for a specific size of tuple, which may be fine for certain cases, but certainly isn't cool yet. The standard library uses some macros to reduce the drudgery of the copy-and-paste that you would need to do to get more sizes. I decided to be even lazier and reduce the copy-and-paste of that solution!
我并没有明确列出每个元组的大小以及相应的索引/泛型名称,而是使我的宏递归了.这样,我只需要列出一次,所有较小的大小只是递归调用的一部分.不幸的是,我无法弄清楚如何使其向前移动,所以我只是翻转了一切并向后移动.这意味着效率低下,因为我们必须使用反向迭代器,但是总体来说应该付出很小的代价.
Instead of clearly and explicitly listing out each size of tuple and the corresponding index/generic name, I made my macro recursive. That way, I only have to list it out once, and all the smaller sizes are just part of the recursive call. Unfortunately, I couldn't figure out how to make it go in a forwards direction, so I just flipped everything around and went backwards. This means there's a small inefficiency in that we have to use a reverse iterator, but that should overall be a small price to pay.
这篇关于如何在元组上迭代或映射?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!