monotone_crescendo/
lib.rs

1//! A library for calculating the least number of flips needed to make a binary string monotone increasing.
2//! Also contains utility functions for allocating WebAssembly linear memory and returning a pointer to it, as well
3//! as deallocating said memory.
4
5#[cfg(test)]
6mod tests {
7    use super::solution::*;
8    use super::wasm_memory::call_solution_with_input;
9
10    #[test]
11    fn test_call_solution_with_input() {
12        let input = String::from("Cumulative\011010\0");
13        let mut buf = input.into_bytes();
14        let ptr = buf.as_mut_ptr();
15
16        unsafe {
17            call_solution_with_input(ptr);
18        }
19    }
20
21    #[test]
22    fn test_with_prefix_sums() {
23        assert_eq!(monotone_crescendo_prefix_sums("11010"), 2);
24    }
25
26    #[test]
27    fn test_with_prefix_sums_without_redundant_zero() {
28        assert_eq!(
29            monotone_crescendo_prefix_sums_without_redundant_zero("11010"),
30            2
31        );
32    }
33
34    #[test]
35    fn test_cumulative() {
36        assert_eq!(monototone_crescendo_cumulative("11010"), 2);
37    }
38}
39
40pub mod wasm_memory {
41    //! # Utilities for interacting with WASM's linear memory
42
43    use super::solution::*;
44    use std::{ffi::CString, mem, os::raw::c_void};
45
46    /// # Allocate memory in WebAssembly's linear memory and return its offset
47    #[no_mangle]
48    pub extern "C" fn alloc() -> *mut c_void {
49        let mut buf = Vec::with_capacity(1024);
50        let ptr = buf.as_mut_ptr();
51
52        mem::forget(buf);
53
54        ptr
55    }
56
57    /// # Deallocate memory in WebAssembly's linear memory at the offset located at `ptr`
58    ///
59    /// No explicit calls to [std::mem::drop] are needed as [std::vec::Vec::from_raw_parts] takes ownership
60    /// of the memory pointed at by `ptr`.
61    #[no_mangle]
62    pub unsafe extern "C" fn dealloc(ptr: *mut c_void) {
63        let _ = Vec::from_raw_parts(ptr, 1024, 1024);
64    }
65
66    /// # Call the specified [solution][super::solution] method with the given input
67    ///
68    /// `ptr` should be a pointer containing UTF-8 encoded characters separated by a null character. The first block of characters
69    /// should be the name of solution to use, and the second block of characters should be the input to the solution itself.
70    #[no_mangle]
71    pub unsafe extern "C" fn call_solution_with_input(ptr: *mut u8) {
72        let mut iter = ptr;
73
74        let mut solution_name = String::new();
75        let mut input = String::new();
76
77        let mut null_count = 0;
78        while null_count < 2 {
79            let c = *iter as char;
80
81            iter = iter.add(1);
82
83            if c == '\0' {
84                null_count += 1;
85                continue;
86            }
87
88            match null_count {
89                0 => solution_name.push(c),
90                1 => input.push(c),
91                _ => panic!("Unexpected null_count value"),
92            }
93        }
94
95        let flips = match solution_name.as_str() {
96            "Prefix Sums" => monotone_crescendo_prefix_sums(&input),
97            "Cumulative" => monototone_crescendo_cumulative(&input),
98            "Prefix Sums w/o Redundant Zero" => {
99                monotone_crescendo_prefix_sums_without_redundant_zero(&input) as i32
100            }
101            _ => -1,
102        };
103
104        let answer = match flips {
105            -1 => format!("Solution name \"{}\" does not exist", solution_name),
106            _ => format!("The minimum number of flips needed is: {}", flips),
107        };
108
109        let c_str = CString::new(answer).unwrap();
110        let c_str_bytes = c_str.as_bytes_with_nul();
111
112        let return_bytes = std::slice::from_raw_parts_mut(ptr, 1024);
113        return_bytes[..c_str_bytes.len()].copy_from_slice(c_str_bytes);
114    }
115}
116
117pub mod solution {
118    //! # Implementations of the monotone increasing problem
119
120    use std::cmp::min;
121    /// # This is the [official solution][0] to [LeetCode problem #926][1]
122    ///
123    /// The solution was simply translated by me into rust from java.
124    ///
125    /// ## Explanation
126    ///
127    /// I find the explanation on LeetCode to this solution somewhat overcomplicated and hard to understand, so I will attempt
128    /// to explain it (hopefully) a bit simpler here.
129    ///
130    /// The first thing this solution does is calculate the [prefix sum][2] of every item in the string and store it in an array. So for example, if we had the string `11010`, the prefix sum array
131    /// for that string would look like: `[0,1,2,2,3,3]`. This prefix sum array represents the the number of 1's that are present before the corresponding character index in the string.
132    ///
133    /// This solution then works by looping through the given string and, for each character in the string, analyzing the string as two halves partioned
134    /// at that character. The number of 1's in the first half are added to the number of 0's in the second half to determine the total number of flips
135    /// needed to make the string monotone increasing for that location in the string.
136    ///
137    /// We get the number of 1's in the first half by looking up the corresponding index in the prefix sum array.
138    /// We get the number of 0's in the second half by subtracting the number of 1's in the second half from the length
139    /// of the second half.
140    ///
141    /// We then compare this number to the current minimum flips value and take the minimum of these two values again. The minimium
142    /// we have in the end is what we return.
143    ///
144    /// Note: the prefix sum array could also be optimized to remove the redundant element at index 0. The way the solution on LeetCode
145    /// calculates the prefix sum array, the first element will always be zero. For example, if we had the string `11010`, the prefix sum array
146    /// could be optimized to `[1,2,2,3,3]`. Apparently the official solution calculates the prefix sum array with the redundant zero at the beginning
147    /// to [prevent some sort of possible overflow condition][3], out of principle more than anything else. See [monotone_crescendo_prefix_sums_without_redundant_zero] for an example.
148    ///
149    /// [0]: <https://leetcode.com/problems/flip-string-to-monotone-increasing/solution/>
150    /// [1]: <https://leetcode.com/problems/flip-string-to-monotone-increasing/>
151    /// [2]: <https://en.wikipedia.org/wiki/Prefix_sum>
152    /// [3]: <https://leetcode.com/problems/flip-string-to-monotone-increasing/solution/1047706>
153    pub fn monotone_crescendo_prefix_sums(s: &str) -> i32 {
154        let str_size = s.chars().count() as i32;
155        let mut prefix_sums = vec![0; str_size as usize + 1];
156        let mut min_flips: i32 = i32::MAX;
157
158        for (i, char) in s.chars().enumerate() {
159            prefix_sums[i + 1] = prefix_sums[i] + (if char == '1' { 1 } else { 0 });
160        }
161
162        let mut j = 0;
163
164        while j <= str_size {
165            let ones_before_j = prefix_sums[j as usize];
166            let ones_after_j = prefix_sums[str_size as usize] - prefix_sums[j as usize];
167
168            let length_of_str_after_j = str_size - j;
169            let zeroes_after_j = length_of_str_after_j - ones_after_j;
170
171            min_flips = min(min_flips, ones_before_j + zeroes_after_j);
172
173            println!(
174                "min_flips: {:?}, j: {:?}, ones_before_j: {:?}, zeroes_after_j: {:?}",
175                min_flips, j, ones_before_j, zeroes_after_j
176            );
177
178            j += 1;
179        }
180
181        min_flips
182    }
183
184    /// # Essentially the same solution as [monotone_crescendo_prefix_sums] except without the redundant leading zero in the prefix sum array
185    ///
186    /// This function also utilizes [u8]'s instead of [i32]'s.
187    /// This could have also been done in [monotone_crescendo_prefix_sums] as our input will always lead to positive prefix sums and a positive
188    /// return value which should all be less than or equal to 255. The only reason [monotone_crescendo_prefix_sums] uses [i32]'s is that I made an
189    /// arbitrary choice to do so, and when removing the redundant zero I thought about it a bit more and realized [u8]'s would be fine.
190    pub fn monotone_crescendo_prefix_sums_without_redundant_zero(s: &str) -> u8 {
191        let str_size = s.chars().count();
192        let mut prefix_sums: Vec<u8> = Vec::with_capacity(str_size);
193        let mut min_flips: u8 = u8::MAX;
194
195        println!("min_flips: {:?}", min_flips);
196
197        for (i, char) in s.chars().enumerate() {
198            let num = match char {
199                '1' => 1,
200                _ => 0,
201            };
202            if i == 0 {
203                prefix_sums.push(num);
204                continue;
205            }
206
207            prefix_sums.push(prefix_sums.get(i - 1).unwrap() + num);
208        }
209
210        let mut j = 0;
211
212        while j < str_size {
213            let ones_before_j = match j {
214                0 => 0,
215                _ => prefix_sums[j],
216            };
217            let ones_after_j = prefix_sums[str_size - 1] - prefix_sums[j];
218
219            let length_of_str_after_j = (str_size - 1 - j) as u8;
220            let zeroes_after_j = length_of_str_after_j - ones_after_j;
221
222            min_flips = min(min_flips, ones_before_j + zeroes_after_j);
223
224            println!(
225                "min_flips: {:?}, j: {:?}, ones_before_j: {:?}, zeroes_after_j: {:?}",
226                min_flips, j, ones_before_j, zeroes_after_j
227            );
228
229            j += 1;
230        }
231
232        min_flips
233    }
234
235    /// # This is an ingenious solution which was [contributed][0] by LeetCode user [tarunbisht][1] and translated to Rust by me
236    ///
237    /// ## Explanation
238    ///
239    /// This solution works by keeping a running total of all ones and all zeroes, where the count of zeroes is initlally contained in the variable `flips`.
240    /// The variable `flips` is then set to the minimum value of the running total of zeroes or running total of ones.
241    ///
242    /// Overall this solution is not only simpler to understand, at least for me, but also requires only O(1) space as opposed to O(N) space.
243    ///
244    /// [0]: <https://leetcode.com/problems/flip-string-to-monotone-increasing/solution/725259>
245    /// [1]: <https://leetcode.com/tarunbisht>
246    pub fn monototone_crescendo_cumulative(s: &str) -> i32 {
247        let mut ones = 0;
248        let mut flips = 0;
249
250        for char in s.chars() {
251            match char {
252                '1' => ones += 1,
253                _ => flips += 1,
254            };
255
256            flips = min(ones, flips);
257        }
258
259        flips
260    }
261}