qfall_math/integer/mat_z/
to_string.rs

1// Copyright © 2023 Marcel Luca Schmidt
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! This module contains all options to convert a matrix of type
10//! [`MatZ`] into a [`String`].
11//!
12//! This includes the [`Display`](std::fmt::Display) trait.
13
14use super::MatZ;
15use crate::{
16    macros::for_others::implement_for_owned,
17    traits::{MatrixDimensions, MatrixGetEntry},
18    utils::parse::matrix_to_string,
19};
20use core::fmt;
21use std::string::FromUtf8Error;
22
23impl From<&MatZ> for String {
24    /// Converts a [`MatZ`] into its [`String`] representation.
25    ///
26    /// Parameters:
27    /// - `value`: specifies the matrix that will be represented as a [`String`]
28    ///
29    /// Returns a [`String`] of the form `"[[row_0],[row_1],...[row_n]]"`.
30    ///
31    /// # Examples
32    /// ```
33    /// use qfall_math::integer::MatZ;
34    /// use std::str::FromStr;
35    /// let matrix = MatZ::from_str("[[6, 1],[5, 2]]").unwrap();
36    ///
37    /// let string: String = matrix.into();
38    /// ```
39    fn from(value: &MatZ) -> Self {
40        value.to_string()
41    }
42}
43
44implement_for_owned!(MatZ, String, From);
45
46impl fmt::Display for MatZ {
47    /// Allows to convert a matrix of type [`MatZ`] into a [`String`].
48    ///
49    /// Returns the Matrix in form of a [`String`]. For matrix `[[1, 2, 3],[4, 5, 6]]`
50    /// the String looks like this `[[1, 2, 3],[4, 5, 6]]`.
51    ///
52    /// # Examples
53    /// ```
54    /// use qfall_math::integer::MatZ;
55    /// use core::fmt;
56    /// use std::str::FromStr;
57    ///
58    /// let matrix = MatZ::from_str("[[1, 2, 3],[4, 5, 6]]").unwrap();
59    /// println!("{matrix}");
60    /// ```
61    ///
62    /// ```
63    /// use qfall_math::integer::MatZ;
64    /// use core::fmt;
65    /// use std::str::FromStr;
66    ///
67    /// let matrix = MatZ::from_str("[[1, 2, 3],[4, 5, 6]]").unwrap();
68    /// let matrix_string = matrix.to_string();
69    /// ```
70    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71        write!(f, "{}", matrix_to_string(self))
72    }
73}
74
75impl MatZ {
76    /// Enables conversion to a UTF8-Encoded [`String`] for [`MatZ`] values.
77    /// Every entry is padded with `00`s s.t. all entries contain the same number of bytes.
78    /// Afterwards, they are appended row-by-row and converted.
79    /// The inverse to this function is [`MatZ::from_utf8`] for valid UTF8-Encodings.
80    ///
81    /// **Warning**: Not every byte-sequence forms a valid UTF8-Encoding.
82    /// In these cases, an error is returned. Please check the format of your message again.
83    /// The matrix entries are evaluated row by row, i.e. in the order of the output of `mat_z.to_string()`.
84    ///
85    /// Returns the corresponding UTF8-encoded [`String`] or a
86    /// [`FromUtf8Error`] if the byte sequence contains an invalid UTF8-character.
87    ///
88    /// # Examples
89    /// ```
90    /// use qfall_math::integer::MatZ;
91    /// use std::str::FromStr;
92    /// let matrix = MatZ::from_str("[[104, 101, 108],[108, 111, 33]]").unwrap();
93    ///
94    /// let message = matrix.to_utf8().unwrap();
95    ///
96    /// assert_eq!("hello!", message);
97    /// ```
98    ///
99    /// # Errors and Failures
100    /// - Returns a [`FromUtf8Error`] if the integer's byte sequence contains
101    ///   invalid UTF8-characters.
102    pub fn to_utf8(&self) -> Result<String, FromUtf8Error> {
103        let mut byte_vectors: Vec<Vec<u8>> =
104            Vec::with_capacity((self.get_num_rows() * self.get_num_columns()) as usize);
105        let mut max_length = 0;
106
107        // Fill byte vector
108        for row in 0..self.get_num_rows() as usize {
109            for col in 0..self.get_num_columns() as usize {
110                let entry_value = unsafe { self.get_entry_unchecked(row as i64, col as i64) };
111                let entry_bytes = entry_value.to_bytes();
112
113                // Find maximum length of bytes in one entry of the matrix
114                if max_length < entry_bytes.len() {
115                    max_length = entry_bytes.len();
116                }
117
118                byte_vectors.push(entry_bytes);
119            }
120        }
121
122        // Pad every entry to the same length with `0`s
123        // to ensure any matrix given a string provides the same matrix
124        // and append them in the same iteration
125        let mut bytes = Vec::with_capacity(byte_vectors.len() * max_length);
126        for mut byte_vector in byte_vectors {
127            // 0 encodes a control character �, which can be followed by anything
128            // Hence, this might change the encoding of any trailing sequences
129            byte_vector.resize(max_length, 0u8);
130
131            bytes.append(&mut byte_vector);
132        }
133
134        String::from_utf8(bytes)
135    }
136}
137
138crate::macros::serialize::matrix_pretty_string!(MatZ, Z);
139
140#[cfg(test)]
141mod test_to_string {
142    use crate::integer::MatZ;
143    use std::str::FromStr;
144
145    /// Tests whether a matrix with a large entry works in a roundtrip
146    #[test]
147    fn working_large_positive() {
148        let cmp = MatZ::from_str(&format!("[[{}, 1, 3],[5, 6, 7]]", u64::MAX)).unwrap();
149
150        assert_eq!(format!("[[{}, 1, 3],[5, 6, 7]]", u64::MAX), cmp.to_string());
151    }
152
153    /// Tests whether a matrix with a large negative entry works in a roundtrip
154    #[test]
155    fn working_large_negative() {
156        let cmp = MatZ::from_str(&format!("[[-{}, 1, 3],[5, 6, 7]]", u64::MAX)).unwrap();
157
158        assert_eq!(
159            format!("[[-{}, 1, 3],[5, 6, 7]]", u64::MAX),
160            cmp.to_string()
161        )
162    }
163
164    /// Tests whether a matrix with positive entries works in a roundtrip
165    #[test]
166    fn working_positive() {
167        let cmp = MatZ::from_str("[[2, 1, 3],[5, 6, 7]]").unwrap();
168
169        assert_eq!("[[2, 1, 3],[5, 6, 7]]", cmp.to_string());
170    }
171
172    /// Tests whether a matrix with negative entries works in a roundtrip
173    #[test]
174    fn working_negative() {
175        let cmp = MatZ::from_str("[[-2, 1, 3],[5, -6, 7]]").unwrap();
176
177        assert_eq!("[[-2, 1, 3],[5, -6, 7]]", cmp.to_string());
178    }
179
180    /// Tests whether a matrix with positive entries works in a roundtrip
181    #[test]
182    fn working_large_dimensions() {
183        let cmp_1 = MatZ::from_str(&format!("[{}[5, 6, 7]]", "[1, 2, 3],".repeat(99))).unwrap();
184        let cmp_2 = MatZ::from_str(&format!("[[{}1]]", "1, ".repeat(99))).unwrap();
185
186        assert_eq!(
187            format!("[{}[5, 6, 7]]", "[1, 2, 3],".repeat(99)),
188            cmp_1.to_string()
189        );
190        assert_eq!(format!("[[{}1]]", "1, ".repeat(99)), cmp_2.to_string());
191    }
192
193    /// Tests whether a matrix that is created using a string, returns a
194    /// string that can be used to create a [`MatZ`]
195    #[test]
196    fn working_use_result_of_to_string_as_input() {
197        let cmp = MatZ::from_str("[[-2, 1, 3],[5, -6, 7]]").unwrap();
198
199        let cmp_str_2 = cmp.to_string();
200
201        assert!(MatZ::from_str(&cmp_str_2).is_ok());
202    }
203
204    /// Ensures that the `Into<String>` trait works properly
205    #[test]
206    fn into_works_properly() {
207        let cmp = "[[6, 1, 3],[5, 2, 7]]";
208        let matrix = MatZ::from_str(cmp).unwrap();
209
210        let string: String = matrix.clone().into();
211        let borrowed_string: String = (&matrix).into();
212
213        assert_eq!(cmp, string);
214        assert_eq!(cmp, borrowed_string);
215    }
216}
217
218#[cfg(test)]
219mod test_to_utf8 {
220    use crate::integer::MatZ;
221    use std::str::FromStr;
222
223    /// Ensures that [`MatZ::to_utf8`] is inverse to [`MatZ::from_utf8`].
224    #[test]
225    fn inverse_of_from_utf8() {
226        let message = "some_random_string_1-9A-Z!?-_;:#";
227
228        let matrix = MatZ::from_utf8(message, 4, 4);
229
230        let string = matrix.to_utf8().unwrap();
231
232        assert_eq!(message, string);
233    }
234
235    /// Ensures that [`MatZ::from_utf8`] is inverse to [`MatZ::to_utf8`].
236    #[test]
237    fn inverse_to_from_utf8() {
238        let matrix_cmp_w_padding = MatZ::from_str("[[104, 101, 108],[28524, 48, 48]]").unwrap();
239        let matrix_cmp_wo_padding = MatZ::from_str("[[104, 101],[108, 108],[111, 33]]").unwrap();
240
241        let string_w_padding = matrix_cmp_w_padding.to_utf8().unwrap();
242        let string_wo_padding = matrix_cmp_wo_padding.to_utf8().unwrap();
243
244        let matrix_w_padding = MatZ::from_utf8(&string_w_padding, 2, 3);
245        let matrix_wo_padding = MatZ::from_utf8(&string_wo_padding, 3, 2);
246
247        assert_eq!(matrix_cmp_w_padding, matrix_w_padding);
248        assert_eq!(matrix_cmp_wo_padding, matrix_wo_padding);
249    }
250
251    /// Ensures that [`MatZ::to_utf8`] is inverse to [`MatZ::from_utf8`]
252    /// and padding is applied if necessary.
253    #[test]
254    fn inverse_incl_padding() {
255        let message = "some_random_string_1-9A-Z!?-_;";
256        let cmp_text = "some_random_string_1-9A-Z!?-_;00";
257
258        let matrix = MatZ::from_utf8(message, 4, 8);
259
260        let string = matrix.to_utf8().unwrap();
261
262        assert_eq!(cmp_text, string);
263    }
264
265    /// Ensures that [`MatZ::to_utf8`] outputs an error
266    /// if the integer contains an invalid UTF8-Encoding.
267    #[test]
268    fn invalid_encoding() {
269        // 128 is an invalid UTF8-character (at least at the end and on its own)
270        let matrix = MatZ::from_str("[[1,2],[3,128]]").unwrap();
271        let string = matrix.to_utf8();
272
273        assert!(string.is_err());
274    }
275}