1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
//! A library for calculating the least number of flips needed to make a binary string monotone increasing.
//! Also contains utility functions for allocating WebAssembly linear memory and returning a pointer to it, as well
//! as deallocating said memory.

#[cfg(test)]
mod tests {
    use super::solution::*;
    use super::wasm_memory::call_solution_with_input;

    #[test]
    fn test_call_solution_with_input() {
        let input = String::from("Cumulative\011010\0");
        let mut buf = input.into_bytes();
        let ptr = buf.as_mut_ptr();

        unsafe {
            call_solution_with_input(ptr);
        }
    }

    #[test]
    fn test_with_prefix_sums() {
        assert_eq!(monotone_crescendo_prefix_sums("11010"), 2);
    }

    #[test]
    fn test_with_prefix_sums_without_redundant_zero() {
        assert_eq!(
            monotone_crescendo_prefix_sums_without_redundant_zero("11010"),
            2
        );
    }

    #[test]
    fn test_cumulative() {
        assert_eq!(monototone_crescendo_cumulative("11010"), 2);
    }
}

pub mod wasm_memory {
    //! # Utilities for interacting with WASM's linear memory

    use super::solution::*;
    use std::{ffi::CString, mem, os::raw::c_void};

    /// # Allocate memory in WebAssembly's linear memory and return its offset
    #[no_mangle]
    pub extern "C" fn alloc() -> *mut c_void {
        let mut buf = Vec::with_capacity(1024);
        let ptr = buf.as_mut_ptr();

        mem::forget(buf);

        ptr
    }

    /// # Deallocate memory in WebAssembly's linear memory at the offset located at `ptr`
    ///
    /// No explicit calls to [std::mem::drop] are needed as [std::vec::Vec::from_raw_parts] takes ownership
    /// of the memory pointed at by `ptr`.
    #[no_mangle]
    pub unsafe extern "C" fn dealloc(ptr: *mut c_void) {
        let _ = Vec::from_raw_parts(ptr, 1024, 1024);
    }

    /// # Call the specified [solution][super::solution] method with the given input
    ///
    /// `ptr` should be a pointer containing UTF-8 encoded characters separated by a null character. The first block of characters
    /// should be the name of solution to use, and the second block of characters should be the input to the solution itself.
    #[no_mangle]
    pub unsafe extern "C" fn call_solution_with_input(ptr: *mut u8) {
        let mut iter = ptr;

        let mut solution_name = String::new();
        let mut input = String::new();

        let mut null_count = 0;
        while null_count < 2 {
            let c = *iter as char;

            iter = iter.add(1);

            if c == '\0' {
                null_count += 1;
                continue;
            }

            match null_count {
                0 => solution_name.push(c),
                1 => input.push(c),
                _ => panic!("Unexpected null_count value"),
            }
        }

        let flips = match solution_name.as_str() {
            "Prefix Sums" => monotone_crescendo_prefix_sums(&input),
            "Cumulative" => monototone_crescendo_cumulative(&input),
            "Prefix Sums w/o Redundant Zero" => {
                monotone_crescendo_prefix_sums_without_redundant_zero(&input) as i32
            }
            _ => -1,
        };

        let answer = match flips {
            -1 => format!("Solution name \"{}\" does not exist", solution_name),
            _ => format!("The minimum number of flips needed is: {}", flips),
        };

        let c_str = CString::new(answer).unwrap();
        let c_str_bytes = c_str.as_bytes_with_nul();

        let return_bytes = std::slice::from_raw_parts_mut(ptr, 1024);
        return_bytes[..c_str_bytes.len()].copy_from_slice(c_str_bytes);
    }
}

pub mod solution {
    //! # Implementations of the monotone increasing problem

    use std::cmp::min;
    /// # This is the [official solution][0] to [LeetCode problem #926][1]
    ///
    /// The solution was simply translated by me into rust from java.
    ///
    /// ## Explanation
    ///
    /// I find the explanation on LeetCode to this solution somewhat overcomplicated and hard to understand, so I will attempt
    /// to explain it (hopefully) a bit simpler here.
    ///
    /// 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
    /// 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.
    ///
    /// This solution then works by looping through the given string and, for each character in the string, analyzing the string as two halves partioned
    /// 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
    /// needed to make the string monotone increasing for that location in the string.
    ///
    /// We get the number of 1's in the first half by looking up the corresponding index in the prefix sum array.
    /// 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
    /// of the second half.
    ///
    /// We then compare this number to the current minimum flips value and take the minimum of these two values again. The minimium
    /// we have in the end is what we return.
    ///
    /// Note: the prefix sum array could also be optimized to remove the redundant element at index 0. The way the solution on LeetCode
    /// calculates the prefix sum array, the first element will always be zero. For example, if we had the string `11010`, the prefix sum array
    /// 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
    /// 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.
    ///
    /// [0]: <https://leetcode.com/problems/flip-string-to-monotone-increasing/solution/>
    /// [1]: <https://leetcode.com/problems/flip-string-to-monotone-increasing/>
    /// [2]: <https://en.wikipedia.org/wiki/Prefix_sum>
    /// [3]: <https://leetcode.com/problems/flip-string-to-monotone-increasing/solution/1047706>
    pub fn monotone_crescendo_prefix_sums(s: &str) -> i32 {
        let str_size = s.chars().count() as i32;
        let mut prefix_sums = vec![0; str_size as usize + 1];
        let mut min_flips: i32 = i32::MAX;

        for (i, char) in s.chars().enumerate() {
            prefix_sums[i + 1] = prefix_sums[i] + (if char == '1' { 1 } else { 0 });
        }

        let mut j = 0;

        while j <= str_size {
            let ones_before_j = prefix_sums[j as usize];
            let ones_after_j = prefix_sums[str_size as usize] - prefix_sums[j as usize];

            let length_of_str_after_j = str_size - j;
            let zeroes_after_j = length_of_str_after_j - ones_after_j;

            min_flips = min(min_flips, ones_before_j + zeroes_after_j);

            println!(
                "min_flips: {:?}, j: {:?}, ones_before_j: {:?}, zeroes_after_j: {:?}",
                min_flips, j, ones_before_j, zeroes_after_j
            );

            j += 1;
        }

        min_flips
    }

    /// # Essentially the same solution as [monotone_crescendo_prefix_sums] except without the redundant leading zero in the prefix sum array
    ///
    /// This function also utilizes [u8]'s instead of [i32]'s.
    /// This could have also been done in [monotone_crescendo_prefix_sums] as our input will always lead to positive prefix sums and a positive
    /// 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
    /// 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.
    pub fn monotone_crescendo_prefix_sums_without_redundant_zero(s: &str) -> u8 {
        let str_size = s.chars().count();
        let mut prefix_sums: Vec<u8> = Vec::with_capacity(str_size);
        let mut min_flips: u8 = u8::MAX;

        println!("min_flips: {:?}", min_flips);

        for (i, char) in s.chars().enumerate() {
            let num = match char {
                '1' => 1,
                _ => 0,
            };
            if i == 0 {
                prefix_sums.push(num);
                continue;
            }

            prefix_sums.push(prefix_sums.get(i - 1).unwrap() + num);
        }

        let mut j = 0;

        while j < str_size {
            let ones_before_j = match j {
                0 => 0,
                _ => prefix_sums[j],
            };
            let ones_after_j = prefix_sums[str_size - 1] - prefix_sums[j];

            let length_of_str_after_j = (str_size - 1 - j) as u8;
            let zeroes_after_j = length_of_str_after_j - ones_after_j;

            min_flips = min(min_flips, ones_before_j + zeroes_after_j);

            println!(
                "min_flips: {:?}, j: {:?}, ones_before_j: {:?}, zeroes_after_j: {:?}",
                min_flips, j, ones_before_j, zeroes_after_j
            );

            j += 1;
        }

        min_flips
    }

    /// # This is an ingenious solution which was [contributed][0] by LeetCode user [tarunbisht][1] and translated to Rust by me
    ///
    /// ## Explanation
    ///
    /// 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`.
    /// The variable `flips` is then set to the minimum value of the running total of zeroes or running total of ones.
    ///
    /// 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.
    ///
    /// [0]: <https://leetcode.com/problems/flip-string-to-monotone-increasing/solution/725259>
    /// [1]: <https://leetcode.com/tarunbisht>
    pub fn monototone_crescendo_cumulative(s: &str) -> i32 {
        let mut ones = 0;
        let mut flips = 0;

        for char in s.chars() {
            match char {
                '1' => ones += 1,
                _ => flips += 1,
            };

            flips = min(ones, flips);
        }

        flips
    }
}