Skip to content

Instantly share code, notes, and snippets.

@PurpleMyst
Created April 15, 2018 16:04
Show Gist options
  • Save PurpleMyst/68606c18870218ee799c3a428d08b5ae to your computer and use it in GitHub Desktop.
Save PurpleMyst/68606c18870218ee799c3a428d08b5ae to your computer and use it in GitHub Desktop.
#!/usr/bin/env run-cargo-script
// cargo-deps: quickcheck
use std::{borrow::Borrow, io::{self, BufRead}};
#[cfg(test)]
#[macro_use]
extern crate quickcheck;
fn longest_common_prefix_length(x: &str, y: &str) -> usize {
x.chars()
.zip(y.chars())
.take_while(|(x_char, y_char)| x_char == y_char)
.count()
}
fn compress(words: &[impl Borrow<str>]) -> Vec<String> {
words
.into_iter()
.scan(None, |maybe_last: &mut Option<String>, word| {
let word = word.borrow();
assert!(
!word.chars().any(char::is_numeric),
"Words can not contain numeric characters, but {:?} did.",
word
);
let new_word: String = if let Some(ref last) = *maybe_last {
let lcp_length = longest_common_prefix_length(last, word);
if lcp_length == 0 {
// no prefix in common, just return the word
word.to_owned()
} else {
// factor out the common part of the word
format!("{}{}", lcp_length, &word[lcp_length..])
}
} else {
word.to_owned()
};
// we want to take the full word as "last", so for cases where we have multiple
// consecutive compression opportunities we take advantage of those.
*maybe_last = Some(word.to_owned());
Some(new_word)
})
.collect::<Vec<String>>()
}
fn decompress(words: &[impl Borrow<str>]) -> Vec<String> {
words
.into_iter()
.scan(None, |maybe_last: &mut Option<String>, word| {
let word = word.borrow();
let new_word: String = if let Some(ref last) = *maybe_last {
let lcp_length = word.chars()
.take_while(|c| c.is_numeric())
.collect::<String>();
let extra_chars = String::from(&word[lcp_length.len()..]);
let lcp_length = lcp_length.parse::<usize>().unwrap_or(0);
format!("{}{}", &last[..lcp_length], extra_chars)
} else {
// just return the last word cloned
word.to_owned()
};
*maybe_last = Some(new_word.clone());
Some(new_word)
})
.collect::<Vec<String>>()
}
fn main() {
let stdin = io::stdin();
stdin
.lock()
.lines()
.collect::<io::Result<Vec<String>>>()
.map(|words| {
compress(&words)
.into_iter()
.for_each(|word| println!("{}", word))
})
.unwrap();
}
#[cfg(test)]
mod tests {
use quickcheck::TestResult;
use {compress, decompress};
#[test]
fn invariant_on_enable1() {
use std::{env, fs::File, io::{self, BufRead, BufReader}, path::Path};
let enable1_path = env::var_os("HOME")
.map(|home| Path::join(Path::new(&home), Path::new("Documents/enable1.txt")))
.unwrap();
File::open(enable1_path)
.and_then(|file| {
BufReader::new(file)
.lines()
.collect::<io::Result<Vec<String>>>()
})
.map(|words| assert_eq!(words.as_slice(), decompress(&compress(&words)).as_slice()))
.unwrap();
}
quickcheck! {
fn invariant_with_quickcheck(xs: Vec<String>) -> TestResult {
if xs.iter().any(|word| word.chars().any(char::is_numeric)) {
TestResult::must_fail(move || decompress(&compress(&xs)))
} else {
TestResult::from_bool(xs == decompress(&compress(&xs)))
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment