1 ///
2 /// This example parses, sorts and groups the iris dataset
3 /// and does some simple manipulations.
4 ///
5 /// Iterators and itertools functionality are used throughout.
6 use itertools::Itertools;
7 use std::collections::HashMap;
8 use std::iter::repeat;
9 use std::num::ParseFloatError;
10 use std::str::FromStr;
11 
12 static DATA: &str = include_str!("iris.data");
13 
14 #[derive(Clone, Debug)]
15 struct Iris {
16     name: String,
17     data: [f32; 4],
18 }
19 
20 #[allow(dead_code)] // fields are currently ignored
21 #[derive(Clone, Debug)]
22 enum ParseError {
23     Numeric(ParseFloatError),
24     Other(&'static str),
25 }
26 
27 impl From<ParseFloatError> for ParseError {
from(err: ParseFloatError) -> Self28     fn from(err: ParseFloatError) -> Self {
29         Self::Numeric(err)
30     }
31 }
32 
33 /// Parse an Iris from a comma-separated line
34 impl FromStr for Iris {
35     type Err = ParseError;
36 
from_str(s: &str) -> Result<Self, Self::Err>37     fn from_str(s: &str) -> Result<Self, Self::Err> {
38         let mut iris = Self {
39             name: "".into(),
40             data: [0.; 4],
41         };
42         let mut parts = s.split(',').map(str::trim);
43 
44         // using Iterator::by_ref()
45         for (index, part) in parts.by_ref().take(4).enumerate() {
46             iris.data[index] = part.parse::<f32>()?;
47         }
48         if let Some(name) = parts.next() {
49             iris.name = name.into();
50         } else {
51             return Err(ParseError::Other("Missing name"));
52         }
53         Ok(iris)
54     }
55 }
56 
main()57 fn main() {
58     // using Itertools::fold_results to create the result of parsing
59     let irises = DATA
60         .lines()
61         .map(str::parse)
62         .fold_ok(Vec::new(), |mut v, iris: Iris| {
63             v.push(iris);
64             v
65         });
66     let mut irises = match irises {
67         Err(e) => {
68             println!("Error parsing: {:?}", e);
69             std::process::exit(1);
70         }
71         Ok(data) => data,
72     };
73 
74     // Sort them and group them
75     irises.sort_by(|a, b| Ord::cmp(&a.name, &b.name));
76 
77     // using Iterator::cycle()
78     let mut plot_symbols = "+ox".chars().cycle();
79     let mut symbolmap = HashMap::new();
80 
81     // using Itertools::chunk_by
82     for (species, species_chunk) in &irises.iter().chunk_by(|iris| &iris.name) {
83         // assign a plot symbol
84         symbolmap
85             .entry(species)
86             .or_insert_with(|| plot_symbols.next().unwrap());
87         println!("{} (symbol={})", species, symbolmap[species]);
88 
89         for iris in species_chunk {
90             // using Itertools::format for lazy formatting
91             println!("{:>3.1}", iris.data.iter().format(", "));
92         }
93     }
94 
95     // Look at all combinations of the four columns
96     //
97     // See https://en.wikipedia.org/wiki/Iris_flower_data_set
98     //
99     let n = 30; // plot size
100     let mut plot = vec![' '; n * n];
101 
102     // using Itertools::tuple_combinations
103     for (a, b) in (0..4).tuple_combinations() {
104         println!("Column {} vs {}:", a, b);
105 
106         // Clear plot
107         //
108         // using std::iter::repeat;
109         // using Itertools::set_from
110         plot.iter_mut().set_from(repeat(' '));
111 
112         // using Itertools::minmax
113         let min_max = |data: &[Iris], col| {
114             data.iter()
115                 .map(|iris| iris.data[col])
116                 .minmax()
117                 .into_option()
118                 .expect("Can't find min/max of empty iterator")
119         };
120         let (min_x, max_x) = min_max(&irises, a);
121         let (min_y, max_y) = min_max(&irises, b);
122 
123         // Plot the data points
124         let round_to_grid = |x, min, max| ((x - min) / (max - min) * ((n - 1) as f32)) as usize;
125         let flip = |ix| n - 1 - ix; // reverse axis direction
126 
127         for iris in &irises {
128             let ix = round_to_grid(iris.data[a], min_x, max_x);
129             let iy = flip(round_to_grid(iris.data[b], min_y, max_y));
130             plot[n * iy + ix] = symbolmap[&iris.name];
131         }
132 
133         // render plot
134         //
135         // using Itertools::join
136         for line in plot.chunks(n) {
137             println!("{}", line.iter().join(" "))
138         }
139     }
140 }
141