2016-05-26 16 views
8

Tôi có một chức năng hoạt động, nhưng chuyên môn hơn tôi muốn và có một số thiếu hiệu quả, tôi muốn khắc phục điều đó.Làm thế nào tôi có thể làm cho chức năng Rust của tôi trở nên chung chung và hiệu quả hơn?

Chức năng thiếu sót làm việc nhưng:

fn iter_to_min<T>(i:T) -> i64 where T:Iterator<Item=String>{ 
    i.collect::<Vec<String>>() 
     .iter() 
     .flat_map(|s|s.split_whitespace()) 
     .map(str::trim) 
     .map(str::parse::<i64>) 
     .map(Result::unwrap) 
     .min() 
     .expect("No min found.") 
} 

Những lý do tôi không thích điều này thực hiện là:

  • i64 được cứng mã hóa và tôi muốn tái sử dụng chức năng này cho u64 và có thể trở lại khác các loại
  • nó thu thập thông tin đầu vào của nó chỉ để lặp qua nó ngay lập tức không hiệu quả (phân bổ đống không có lý do)
  • việc đóng cửa truyền cho flat_map có thể không được tối ưu hóa đi bởi LLVM trong mọi trường hợp

và gần nhất tôi có thể nhận được tới chức năng lý tưởng của tôi:

use std::str::FromStr; 

fn iter_to_min<T,U>(i:T) -> U where T:Iterator<Item=String>,U: Ord+FromStr{ 
    i.flat_map(str::split_whitespace) 
     .map(str::trim) 
     .map(str::parse::<U>) 
     .map(Result::unwrap) 
     .min() 
     .expect("No min found.") 
} 

Những vấn đề tôi đang gặp là:

  • đối số được chuyển đến str::split_whitespaceString và sẽ không ép buộc vào str
  • đối số truyền cho str::split_whitespace không biết sống đủ lâu
  • Result::unwrap không phàn nàn rằng đặc tính core::fmt::Debug không được thực hiện cho các loại <U as core::str::FromStr>::Err

Tôi nghĩ rằng với ký hiệu đời thông minh và yêu cầu Trait tại ít nhất hai trong số đó có thể được sửa chữa, và ai biết có thể có một cách để đi ba cho ba.

Ví dụ mã sử dụng một số các bản sửa lỗi đề nghị:

use std::io::BufRead; 
use std::str::FromStr; 
use std::fmt::Debug; 

fn iter_to_min<T,U>(i:T) -> U where T:Iterator<Item=String>,U: Ord+FromStr, U::Err: Debug{ 
    i.collect::<Vec<String>>() 
     .iter() 
     .flat_map(|s|s.split_whitespace()) 
     .map(str::trim) 
     .map(str::parse::<U>) 
     .map(Result::unwrap) 
     .min() 
     .expect("No min found.") 
} 

fn main() { 
    let a: Vec<_> = std::env::args().skip(1).collect(); 
    let m:i64 = if a.is_empty() { 
     let s = std::io::stdin(); 
     let m = iter_to_min(s.lock().lines().map(Result::unwrap)); 
     m 
    }else{ 
     iter_to_min(a.into_iter()) 
    }; 
    println!("{}", m); 
} 
+1

Nó sẽ là tuyệt vời nếu bạn có thể đăng một ví dụ hoàn chỉnh, tối thiểu mà tôi có thể dán vào một tập tin cục bộ để có được và chạy :) –

Trả lời

7

Thật không may, không có cách nào để làm những gì bạn muốn khi giữ cả genericity và sự vắng mặt của phân bổ. Lý do là bạn cần một ai đó để sở hữu dữ liệu chuỗi của bạn, nhưng nếu flat_map(str::split_whitespace) được thực hiện qua một vòng lặp của chuỗi sở hữu, thì không có ai giữ các chuỗi được sở hữu này nữa bởi vì str::split_whitespace chỉ mượn chuỗi nó được gọi. Tuy nhiên, nếu bạn "đẩy" quyền sở hữu lên chuỗi cuộc gọi, bạn sẽ không thể hoàn toàn chung chung và chấp nhận chuỗi sở hữu theo giá trị. Do đó, có hai giải pháp: để precollect toàn bộ iterator một Vec<String> (hoặc để chuyển đổi các mặt hàng mang lại bởi split_whitespace() để sở hữu chuỗi riêng biệt và thu thập chúng lại một lần nữa), hoặc chấp nhận một iterator của tài liệu tham khảo.

Dưới đây là phiên bản generic nhất mà tôi có thể đưa ra các giải pháp đầu tiên:

use std::str::FromStr; 
use std::fmt::Debug; 

fn iter_to_min<S, T, U>(i: T) -> U 
    where S: Into<String>, 
      T: IntoIterator<Item=S>, 
      U: Ord + FromStr, 
      U::Err: Debug 
{ 
    i.into_iter() 
     .map(Into::into) 
     .collect::<Vec<_>>() 
     .iter() 
     .flat_map(|s| s.split_whitespace()) 
     .map(str::parse::<U>) 
     .map(Result::unwrap) 
     .min() 
     .expect("No min found") 
} 

(thử nó here)

Nó là cơ bản giống như lần đầu tiên một nhưng chung chung hơn của bạn.Cũng lưu ý rằng bạn không cần phải cắt các phần của chuỗi sau split_whitespace() - sau này sẽ đảm bảo rằng các phần của chuỗi không có khoảng trắng ở hai bên của chúng. Into<String> bị ràng buộc cho phép một người vượt qua cả hai vòng lặp &strString và trong trường hợp thứ hai, sẽ không có bản sao bổ sung nào được thực hiện.

Ngoài ra, bạn có thể chia nhỏ mỗi dòng thành chuỗi thuộc sở hữu riêng:

fn iter_to_min<S, T, U>(i: T) -> U 
    where S: AsRef<str>, 
      T: IntoIterator<Item=S>, 
      U: Ord + FromStr, 
      U::Err: Debug 
{ 
    i.into_iter() 
     .flat_map(|s| s.as_ref().split_whitespace().map(String::from).collect::<Vec<_>>()) 
     .map(|s| s.parse::<U>()) 
     .map(Result::unwrap) 
     .min() 
     .expect("No min found") 
} 

Ở đây chúng ta chỉ cần để có được một &str s từ mục iterator, không String s, vì vậy tôi sử dụng AsRef<str>. Tuy nhiên, mỗi dòng không được chuyển đổi thành chuỗi String s; trình tự này phải được thu thập thành một véc tơ cho chính xác cùng một lý do được mô tả ở trên - nếu không sẽ không có ai giữ các giá trị ban đầu của loại S từ sự hủy diệt.

Nhưng nó có thể tránh được .map(String::from).collect::<Vec<_>>() nếu bạn sẵn sàng mất một số tính tổng thể. Đây là giải pháp thứ hai tôi đã đề cập ở trên. Chúng ta có thể chấp nhận một iterator qua tham khảo:

fn iter_to_min<'a, S: ?Sized, T, U>(i: T) -> U 
    where S: AsRef<str> + 'a, 
      T: IntoIterator<Item=&'a S>, 
      U: Ord + FromStr, 
      U::Err: Debug 
{ 
    i.into_iter() 
     .map(AsRef::as_ref) 
     .flat_map(str::split_whitespace) 
     .map(|s| s.parse::<U>()) 
     .map(Result::unwrap) 
     .min() 
     .expect("No min found") 
} 

(thử nó here)

Nói đại khái, bây giờ S giá trị này được sở hữu bởi người khác, và tuổi thọ của họ là lớn hơn phạm vi của iter_to_min(), vì vậy bạn cần không được chuyển đổi từng phần thành String cũng như không thu thập toàn bộ kết quả được tách thành Vec<String>. Tuy nhiên, bạn sẽ không thể chuyển một hàm Vec<String> cho hàm này; bạn sẽ có thể để vượt qua vec.iter(), tuy nhiên:

let v: Vec<String> = vec!["0".into(), "1".into()]; 
iter_to_min(v.iter()) 

Trong tất cả các ví dụ tôi đã thay đổi Iterator-IntoIterator - đây là hầu như luôn luôn những gì bạn muốn sử dụng thay vì chỉ Iterator. Nó cho phép bạn, ví dụ, để chuyển các bộ sưu tập đến các hàm như vậy một cách trực tiếp. Thứ hai, tôi đã thêm điều kiện U::Err: Debug, điều này là cần thiết cho Result::unwrap để hoạt động. Và cuối cùng, để khắc phục vấn đề với "Chuỗi không ép buộc để & str` bạn luôn có thể sử dụng đóng cửa rõ ràng và cú pháp phương pháp đó sẽ làm gì ép buộc này cho bạn.

2

Một giải pháp mà không phân bổ thêm

use std::str::FromStr; 

fn iter_to_min<T, U>(i: T) -> Option<U> 
    where T: Iterator<Item = String>, 
      U: Ord + FromStr 
{ 
    i.filter_map(|string| { 
      string.split_whitespace() 
       .map(str::trim) 
       .map(str::parse::<U>) 
       .filter_map(Result::ok) 
       .min() 
     }) 
     .min() 
} 
Các vấn đề liên quan