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}