How do I write the transpose function example from Rust by Example? - rust

I'm following the Rust by Example tutorial and am on the second part of the Tuples activity which is to add a transpose function using the reverse function as a template. This will accept a matrix as an argument and return a matrix in which two elements have been swapped. For example:
println!("Matrix:\n{}", matrix);
println!("Transpose:\n{}", transpose(matrix));
Expected results:
Input Matrix:
( 1.1 1.2 2.1 2.2 )
Transposed output:
( 1.1 2.1 1.2 2.2 )
I can't find the right code, here is what I'm trying:
// this is defined in the tutorial
#[derive(Debug)]
struct Matrix(f32, f32, f32, f32);
// this is my attempt that does not compile
fn transpose(maat: Matrix) -> (Matrix) {
let matrix = maat;
(matrix.0, matrix.2, matrix.1, matrix.3)
}

I don't want to give you the full solution because i would do you a disservice if you're learning Rust.
There's a key ingredient that you're missing at this point of the tutorial. Not your fault.
Matrix is a "tuple struct" (also sometimes called a newtype) and it's covered in a later section of Rust by example.
If you want to peek ahead, in the section on structs you'll learn the two pieces you're missing.
Piece one: struct Matrix(f32, f32, f32, f32); as defined in the tutorial can be destructured in a similar way as the simple tuple.
If you have a let matrix = Matrix(1.1, 1.2, 2.1, 2.2); you can do this to create names for its individual elements:
let Matrix(r1c1, r2c2, r2c1, r2c2) = matrix
What you did (matrix.0, matrix.1...) works too, though...
Piece two. When you want to create a new instance of Matrix, you do Matrix(1.1, 1.2, 2.1, 2.2). From your attempt at writing transpose you're trying to return a tuple, but a tuple struct like Matrix is a different, incompatible type (that's why it's also called a "newtype")

Using reverse as the example says and rewriting the reverse function to accept f32,
fn reverse(pair: (f32, f32)) -> (f32, f32) {
let (a, b) = pair;
(b, a)
}
fn transpose(mat: Matrix) -> Matrix {
let (a, b) = reverse((mat.1, mat.2));
Matrix(mat.0, a, b, mat.3)
}

You need to do three things here:
Tell the Display format how to structure the output so that it divides the Matrix into two rows of equal length.
impl fmt::Display for Matrix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "( {} {} )\n( {} {} )", self.0, self.1, self.2, self.3)
}
}
Write the transpose function that will return the matrix in transposed order.
fn transpose(mat: Matrix) -> Matrix {
return Matrix(mat.0, mat.2, mat.1, mat.3);
}
call it inside println
println!("Transpose:\n{}", transpose(matrix));

Related

Create new Vec<T> from a vector Vec<T> and data T

I want to be able to create a new vector c: Vec<T> from a: Vec<T> and b: T where c is equal to b appended to a, without using mutables. Say I have this code-
fn concat_vec<T>(a: Vec<T>, b: T) -> Vec<T> {
return Vec::<T>::new(a, b);
}
I am aware this does not work, because Vec::new() has no input parameters. What I am wondering is if there is an function or macro in std::vec that can initialize a vector from another vector and a set of additional values to append onto the vector. I know a function can be created very easily to do this, I am just wondering if functionality exists in the standard library to work with vectors without them being mutable in the first place.
Note that you can use the tap crate to push b to a in an expression that evaluates to a:
use tap::Tap;
fn concat_vec<T>(a: Vec<T>, b: T) -> Vec<T> {
a.tap_mut(|a| a.push(b))
}
This combines the performance benefits of push() (in comparison to building a brand new vector) with the elegance of having a short expression.
Vec::from_iter should work if you're willing to use iterators.
pub fn concat_vec<T>(a: Vec<T>, b: T) -> Vec<T> {
return Vec::from_iter(a.into_iter().chain(std::iter::once(b)));
}
Edit(s): Also as far as I have seen: the most common way to work with vectors, slices, and arrays is through the use of the Iterator trait and the functionality it provides.
Also on the topic of speed: the above approach is slightly faster if you avoid cloning. On my computer iterators take about 1.571µs per call whereas cloning and pushing to a vector took 1.580µs per call. (Tested by running the functions 300 000 times on a debug build.)
The universal form of the inefficient solution should look like this:
fn concat_vec<T>(a: Vec<T>, b: T) -> Vec<T> {
Vec::from_iter(a.into_iter().chain(std::iter::once(b)))
}
…but its strongly recommended to use push:
fn concat_vec<T: std::clone::Clone>(a: Vec<T>, b: T) -> Vec<T> {
let mut rslt = a.to_vec();
rslt.push(b);
rslt
}

What is the difference between TryFrom<&[T]> and TryFrom<Vec<T>>?

There seem to be two ways to try to turn a vector into an array, either via a slice (fn a) or directly (fn b):
use std::array::TryFromSliceError;
use std::convert::TryInto;
type Input = Vec<u8>;
type Output = [u8; 1000];
// Rust 1.47
pub fn a(vec: Input) -> Result<Output, TryFromSliceError> {
vec.as_slice().try_into()
}
// Rust 1.48
pub fn b(vec: Input) -> Result<Output, Input> {
vec.try_into()
}
Practically speaking, what's the difference between these? Is it just the error type? The fact that the latter was added makes me wonder whether there's more to it than that.
They have slightly different behavior.
The slice to array implementation will copy the elements from the slice. It has to copy instead of move because the slice doesn't own the elements.
The Vec to array implementation will consume the Vec and move its contents to the new array. It can do this because it does own the elements.

How do I create a macro that transforms inputs into a tuple?

I want to avoid creating many numbered functions and duplicated code if possible. I'm writing a program that is parsing a config file containing lines like the following and I want to simplify my logic for parsing it with some helper functions.
I would like advice on the idiomatic Rust way of approaching this to avoid code duplication while keeping it readable. My best guess is if I could use a macro that could somehow convert the input into a block that results in a tuple but don't know how to write that while including the iteration and transformation steps.
Example Input
attribute-name
1 2
other-attribute
3 4 5
Current parsing implementation
/// Splits `s` into two values, using `pattern` to find the split. If not enough values
/// are present, `missing_err` is returned.
/// It then `transform`s each entry, returning the result as a tuple
fn split_str_into_2<'a, T, E>(
s: &'a str,
pattern: &str,
transform: &dyn Fn(&str) -> T,
missing_err: &E,
) -> Result<(T, T), E> where E: Copy {
let mut split = s.splitn(2, pattern);
Ok((
transform(split.next().ok_or_else(|| *missing_err)?),
transform(split.next().ok_or_else(|| *missing_err)?),
))
}
/// Same as above but parses into a tuple of 3
fn split_str_into_3<'a, T, E>( ...
Calling Code
let (width, height) = split_str_into_2(
input_line, " ", |entry| i32::from_str_radix(entry, 10), &MyError::new("Missing number entry"))?;
I do not know your exact use, but one possibility if you want to collect an iterator into a tuple would be Itertools::collect_tuple.
This, for now is implemented for tuples up to length 4. If you need more elements, you could try adapting the approach taken in itertools or filing an issue/PR on the project.

Alternative to f32 and f64 that implements core::cmp::Ord [duplicate]

If you have a Vec<u32> you would use the slice::binary_search method.
For reasons I don't understand, f32 and f64 do not implement Ord. Since the primitive types are from the standard library, you cannot implement Ord on them yourself, so it does not appear you can use this method.
How can you effectively do this?
Do I really have to wrap f64 in a wrapper struct and implement Ord on it? It seems extremely painful to have to do this, and involves a great deal of transmute to cast blocks of data back and forth unsafely for effectively no reason.
for reasons I don't understand, f32 and f64 do not implement Ord.
Because floating point is hard! The short version is that floating point numbers have a special value NaN - Not a Number. The IEEE spec for floating point numbers states that 1 < NaN, 1 > NaN, and NaN == NaN are all false.
Ord says:
Trait for types that form a total order.
This means that the comparisons need to have totality:
a ≤ b or b ≤ a
but we just saw that floating points do not have this property.
So yes, you will need to create a wrapper type that somehow deals with comparing the large number of NaN values. Maybe your case you can just assert that the float value is never NaN and then call out to the regular PartialOrd trait. Here's an example:
use std::cmp::Ordering;
#[derive(PartialEq,PartialOrd)]
struct NonNan(f64);
impl NonNan {
fn new(val: f64) -> Option<NonNan> {
if val.is_nan() {
None
} else {
Some(NonNan(val))
}
}
}
impl Eq for NonNan {}
impl Ord for NonNan {
fn cmp(&self, other: &NonNan) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
fn main() {
let mut v: Vec<_> = [2.0, 1.0, 3.0].iter().map(|v| NonNan::new(*v).unwrap()).collect();
v.sort();
let r = v.binary_search(&NonNan::new(2.0).unwrap());
println!("{:?}", r);
}
One of the slice methods is binary_search_by, which you could use. f32/f64 implement PartialOrd, so if you know they can never be NaN, you can unwrap the result of partial_cmp:
fn main() {
let values = [1.0, 2.0, 3.0, 4.0, 5.0];
let location = values.binary_search_by(|v| {
v.partial_cmp(&3.14).expect("Couldn't compare values")
});
match location {
Ok(i) => println!("Found at {}", i),
Err(i) => println!("Not found, could be inserted at {}", i),
}
}
A built-in total-ordering comparison method for floats named .total_cmp() is now stable, as of Rust 1.62.0. This implements that total ordering defined in IEEE 754, with every possible f64 bit value being sorted distinctly, including positive and negative zero, and all of the possible NaNs.
Floats still won't implement Ord, so they won't be directly sortable, but the boilerplate has been cut down to a single line, without any external imports or chance of panicking:
fn main() {
let mut v: Vec<f64> = vec![2.0, 2.5, -0.5, 1.0, 1.5];
v.sort_by(f64::total_cmp);
let target = 1.25;
let result = v.binary_search_by(|probe| probe.total_cmp(&target));
match result {
Ok(index) => {
println!("Found target {target} at index {index}.");
}
Err(index) => {
println!("Did not find target {target} (expected index was {index}).");
}
}
}
If you are sure that your floating point values will never be NaN, you can express this semantic with the wrappers in decorum. Specifically, the type Ordered implements Ord and panics whenever the program tries to do something invalid:
use decorum::Ordered;
fn foo() {
let ordered = Ordered<f32>::from_inner(10.);
let normal = ordered.into()
}
https://github.com/emerentius/ord_subset implements a ord_subset_binary_search() method that you can use for this.
from their README:
let mut s = [5.0, std::f64::NAN, 3.0, 2.0];
s.ord_subset_sort();
assert_eq!(&s[0..3], &[2.0, 3.0, 5.0]);
assert_eq!(s.ord_subset_binary_search(&5.0), Ok(2));
assert_eq!(s.iter().ord_subset_max(), Some(&5.0));
assert_eq!(s.iter().ord_subset_min(), Some(&2.0));

How to do a binary search on a Vec of floats?

If you have a Vec<u32> you would use the slice::binary_search method.
For reasons I don't understand, f32 and f64 do not implement Ord. Since the primitive types are from the standard library, you cannot implement Ord on them yourself, so it does not appear you can use this method.
How can you effectively do this?
Do I really have to wrap f64 in a wrapper struct and implement Ord on it? It seems extremely painful to have to do this, and involves a great deal of transmute to cast blocks of data back and forth unsafely for effectively no reason.
for reasons I don't understand, f32 and f64 do not implement Ord.
Because floating point is hard! The short version is that floating point numbers have a special value NaN - Not a Number. The IEEE spec for floating point numbers states that 1 < NaN, 1 > NaN, and NaN == NaN are all false.
Ord says:
Trait for types that form a total order.
This means that the comparisons need to have totality:
a ≤ b or b ≤ a
but we just saw that floating points do not have this property.
So yes, you will need to create a wrapper type that somehow deals with comparing the large number of NaN values. Maybe your case you can just assert that the float value is never NaN and then call out to the regular PartialOrd trait. Here's an example:
use std::cmp::Ordering;
#[derive(PartialEq,PartialOrd)]
struct NonNan(f64);
impl NonNan {
fn new(val: f64) -> Option<NonNan> {
if val.is_nan() {
None
} else {
Some(NonNan(val))
}
}
}
impl Eq for NonNan {}
impl Ord for NonNan {
fn cmp(&self, other: &NonNan) -> Ordering {
self.partial_cmp(other).unwrap()
}
}
fn main() {
let mut v: Vec<_> = [2.0, 1.0, 3.0].iter().map(|v| NonNan::new(*v).unwrap()).collect();
v.sort();
let r = v.binary_search(&NonNan::new(2.0).unwrap());
println!("{:?}", r);
}
One of the slice methods is binary_search_by, which you could use. f32/f64 implement PartialOrd, so if you know they can never be NaN, you can unwrap the result of partial_cmp:
fn main() {
let values = [1.0, 2.0, 3.0, 4.0, 5.0];
let location = values.binary_search_by(|v| {
v.partial_cmp(&3.14).expect("Couldn't compare values")
});
match location {
Ok(i) => println!("Found at {}", i),
Err(i) => println!("Not found, could be inserted at {}", i),
}
}
A built-in total-ordering comparison method for floats named .total_cmp() is now stable, as of Rust 1.62.0. This implements that total ordering defined in IEEE 754, with every possible f64 bit value being sorted distinctly, including positive and negative zero, and all of the possible NaNs.
Floats still won't implement Ord, so they won't be directly sortable, but the boilerplate has been cut down to a single line, without any external imports or chance of panicking:
fn main() {
let mut v: Vec<f64> = vec![2.0, 2.5, -0.5, 1.0, 1.5];
v.sort_by(f64::total_cmp);
let target = 1.25;
let result = v.binary_search_by(|probe| probe.total_cmp(&target));
match result {
Ok(index) => {
println!("Found target {target} at index {index}.");
}
Err(index) => {
println!("Did not find target {target} (expected index was {index}).");
}
}
}
If you are sure that your floating point values will never be NaN, you can express this semantic with the wrappers in decorum. Specifically, the type Ordered implements Ord and panics whenever the program tries to do something invalid:
use decorum::Ordered;
fn foo() {
let ordered = Ordered<f32>::from_inner(10.);
let normal = ordered.into()
}
https://github.com/emerentius/ord_subset implements a ord_subset_binary_search() method that you can use for this.
from their README:
let mut s = [5.0, std::f64::NAN, 3.0, 2.0];
s.ord_subset_sort();
assert_eq!(&s[0..3], &[2.0, 3.0, 5.0]);
assert_eq!(s.ord_subset_binary_search(&5.0), Ok(2));
assert_eq!(s.iter().ord_subset_max(), Some(&5.0));
assert_eq!(s.iter().ord_subset_min(), Some(&2.0));

Resources