2015-03-19 2 views
9

Моя первоначальная проблема заключалась в преобразовании кортежа разных типов в строку. В 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), 
      _ =>() 
} 

Итак, мой вопрос:

  1. это возможно, используя функцию библиотеки, чтобы преобразовать кортеж в строку, задающий произвольный разделитель?
  2. Как написать макрос, чтобы иметь возможность сопоставлять функции с произвольными размерами кортежей?
+1

Обратите внимание, что в Русте арностью кортежа известно во время компиляции (в отличие от Python), и никакой Rust не имеет * вариационных параметров *; Кортежи задаются специальным образом с помощью компилятора, и черты реализуются для нескольких атрибутов «вручную». –

+1

Python имеет тенденцию к слиянию типов, где Rust имеет противоположную тенденцию; в Python все кортежи имеют один тип и все функции одного типа; в Rust каждая комбинация типов полей в кортеже - это другой тип, и каждая функция представляет собой свой собственный уникальный тип. Это разница в подходе: в Python все разрешено во время выполнения; в Rust, во время компиляции. Кортежи в Rust просто неназванные кортежные структуры, не имеющие отношения друг к другу. –

+0

@MatthieuM: Можно ли получить атрибут кортежа как постоянный? – oleid

ответ

14

Вот чрезмерно умный макрос решение:

trait JoinTuple { 
    fn join_tuple(&self, sep: &str) -> String; 
} 

// FIXME(#19630) Remove this work-around 
macro_rules! e { 
    ($e:expr) => { $e } 
} 

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] = e!(&[&self.$idx, $(&self.$nidx),*]); 
       parts.iter().rev().map(|x| x.to_string()).collect::<Vec<_>>().connect(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.connect(", "); 

    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.connect(sep) 
    } 
} 

fn main() { 
    let tup = (1.3, 1, 'c'); 

    println!("{}", tup.tuple_join(", ")); 
} 

Это только реализует нашу черту для определенного размера из кортежа, что может быть хорошо для некоторых случаев, но, конечно же, не прохладно еще. standard library использует некоторые макросы, чтобы уменьшить тяжелую работу копирования и вставки, которую вам нужно будет сделать, чтобы получить больше размеров. Я решил быть даже ленивым и уменьшить копию и вставить это решение!

Вместо явного и явного перечисления каждого размера кортежа и соответствующего имени индекса/родословной, я сделал свой макрорекурсивный. Таким образом, мне нужно только перечислить его один раз, а все меньшие размеры - это всего лишь часть рекурсивного вызова. К сожалению, я не мог понять, как сделать это в направлении вперед, поэтому я просто перевернул все вокруг и пошел назад. Это означает, что есть небольшая неэффективность в том, что мы должны использовать обратный итератор, но в целом это будет небольшая цена для оплаты.

2

other answer очень помог мне, потому что он наглядно продемонстрировал мощь простой макросистемы Rust, когда вы используете рекурсию и сопоставление образцов.

Мне удалось сделать несколько грубых улучшений (возможно, они могут сделать шаблоны немного проще, но это довольно сложно) поверх него, так что список типов tuple accessor-> будет изменен на макрос в время компиляции до расширения в реализации признака, так что мы больше не должны иметь .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); 
} 
Смежные вопросы