diff --git a/src/main.rs b/src/main.rs index 981c0a7..1edeeb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,6 +20,9 @@ use std::io::{self, BufReader}; use argh::FromArgs; +mod suffix; +use crate::suffix::{AlphabeticSuffixGenerator, NumericSuffixGenerator}; + #[derive(FromArgs)] #[argh(description = "\ chop off bits of file\n\n\ @@ -51,85 +54,6 @@ struct Options { filename: String, } -struct AlphabeticSuffixGenerator { - suffix: Vec, - remaining: usize, -} - -impl AlphabeticSuffixGenerator { - fn new(count: usize) -> AlphabeticSuffixGenerator { - let suffix_length = AlphabeticSuffixGenerator::calculate_suffix_length(count); - let mut suffix = vec![b'a'; suffix_length]; - suffix[suffix_length - 1] -= 1; - - AlphabeticSuffixGenerator { - suffix, - remaining: count, - } - } - - fn calculate_suffix_length(count: usize) -> usize { - let mut suffix_length = 1; - let mut remainder = count as f64; - loop { - if remainder <= 26.0 { - break; - } - suffix_length += 1; - remainder /= 26.0; - } - suffix_length - } -} - -impl Iterator for AlphabeticSuffixGenerator { - type Item = String; - - fn next(&mut self) -> Option { - if self.remaining == 0 { - return None; - } - for idx in (0..self.suffix.len()).rev() { - if self.suffix[idx] < b'z' { - self.suffix[idx] += 1; - break; - } else { - self.suffix[idx] = b'a'; - } - } - self.remaining -= 1; - Some(String::from_utf8(self.suffix.to_vec()).expect("invalid suffix generated")) - } -} - -struct NumericSuffixGenerator { - current: usize, - last: usize, -} - -impl NumericSuffixGenerator { - fn new(start: usize, count: usize) -> NumericSuffixGenerator { - NumericSuffixGenerator { - current: start, - last: (start + count), - } - } -} - -impl Iterator for NumericSuffixGenerator { - type Item = String; - - fn next(&mut self) -> Option { - if self.current == self.last { - return None; - } - - let next = self.current.to_string(); - self.current += 1; - Some(next) - } -} - fn try_main() -> Result<(), Box> { let opts: Options = argh::from_env(); @@ -205,138 +129,3 @@ fn main() { std::process::exit(1); } } - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_numeric_suffix_generator_output() { - // tuple of start, count - let testcases = vec![(0, 10), (5, 20), (0, 200), (10, 5)]; - - for (start, count) in testcases { - let expected: Vec = (start..(start + count)).map(|i| i.to_string()).collect(); - let actual: Vec = NumericSuffixGenerator::new(start, count).collect(); - assert_eq!(actual, expected) - } - } - - #[test] - fn test_alpha_suffix_generator_min_digits() { - // tuple of count => expected min length of suffix - let testcases = vec![ - (1, 1), - (10, 1), - (26, 1), - (27, 2), - (30, 2), - (700, 3), - (50_000, 4), - ]; - - for (count, expected) in testcases { - let actual = AlphabeticSuffixGenerator::calculate_suffix_length(count); - assert_eq!( - actual, expected, - "for count {} expected suffix length {} got {}", - count, expected, actual - ); - } - } - - #[test] - fn test_alpha_suffix_generator_output() { - // tuple of count => expected suffix vector - let testcases = vec![ - (3, vec!["a", "b", "c"]), - ( - 30, - vec![ - "aa", "ab", "ac", "ad", "ae", "af", "ag", "ah", "ai", "aj", "ak", "al", "am", - "an", "ao", "ap", "aq", "ar", "as", "at", "au", "av", "aw", "ax", "ay", "az", - "ba", "bb", "bc", "bd", - ], - ), - ( - 700, - vec![ - "aaa", "aab", "aac", "aad", "aae", "aaf", "aag", "aah", "aai", "aaj", "aak", - "aal", "aam", "aan", "aao", "aap", "aaq", "aar", "aas", "aat", "aau", "aav", - "aaw", "aax", "aay", "aaz", "aba", "abb", "abc", "abd", "abe", "abf", "abg", - "abh", "abi", "abj", "abk", "abl", "abm", "abn", "abo", "abp", "abq", "abr", - "abs", "abt", "abu", "abv", "abw", "abx", "aby", "abz", "aca", "acb", "acc", - "acd", "ace", "acf", "acg", "ach", "aci", "acj", "ack", "acl", "acm", "acn", - "aco", "acp", "acq", "acr", "acs", "act", "acu", "acv", "acw", "acx", "acy", - "acz", "ada", "adb", "adc", "add", "ade", "adf", "adg", "adh", "adi", "adj", - "adk", "adl", "adm", "adn", "ado", "adp", "adq", "adr", "ads", "adt", "adu", - "adv", "adw", "adx", "ady", "adz", "aea", "aeb", "aec", "aed", "aee", "aef", - "aeg", "aeh", "aei", "aej", "aek", "ael", "aem", "aen", "aeo", "aep", "aeq", - "aer", "aes", "aet", "aeu", "aev", "aew", "aex", "aey", "aez", "afa", "afb", - "afc", "afd", "afe", "aff", "afg", "afh", "afi", "afj", "afk", "afl", "afm", - "afn", "afo", "afp", "afq", "afr", "afs", "aft", "afu", "afv", "afw", "afx", - "afy", "afz", "aga", "agb", "agc", "agd", "age", "agf", "agg", "agh", "agi", - "agj", "agk", "agl", "agm", "agn", "ago", "agp", "agq", "agr", "ags", "agt", - "agu", "agv", "agw", "agx", "agy", "agz", "aha", "ahb", "ahc", "ahd", "ahe", - "ahf", "ahg", "ahh", "ahi", "ahj", "ahk", "ahl", "ahm", "ahn", "aho", "ahp", - "ahq", "ahr", "ahs", "aht", "ahu", "ahv", "ahw", "ahx", "ahy", "ahz", "aia", - "aib", "aic", "aid", "aie", "aif", "aig", "aih", "aii", "aij", "aik", "ail", - "aim", "ain", "aio", "aip", "aiq", "air", "ais", "ait", "aiu", "aiv", "aiw", - "aix", "aiy", "aiz", "aja", "ajb", "ajc", "ajd", "aje", "ajf", "ajg", "ajh", - "aji", "ajj", "ajk", "ajl", "ajm", "ajn", "ajo", "ajp", "ajq", "ajr", "ajs", - "ajt", "aju", "ajv", "ajw", "ajx", "ajy", "ajz", "aka", "akb", "akc", "akd", - "ake", "akf", "akg", "akh", "aki", "akj", "akk", "akl", "akm", "akn", "ako", - "akp", "akq", "akr", "aks", "akt", "aku", "akv", "akw", "akx", "aky", "akz", - "ala", "alb", "alc", "ald", "ale", "alf", "alg", "alh", "ali", "alj", "alk", - "all", "alm", "aln", "alo", "alp", "alq", "alr", "als", "alt", "alu", "alv", - "alw", "alx", "aly", "alz", "ama", "amb", "amc", "amd", "ame", "amf", "amg", - "amh", "ami", "amj", "amk", "aml", "amm", "amn", "amo", "amp", "amq", "amr", - "ams", "amt", "amu", "amv", "amw", "amx", "amy", "amz", "ana", "anb", "anc", - "and", "ane", "anf", "ang", "anh", "ani", "anj", "ank", "anl", "anm", "ann", - "ano", "anp", "anq", "anr", "ans", "ant", "anu", "anv", "anw", "anx", "any", - "anz", "aoa", "aob", "aoc", "aod", "aoe", "aof", "aog", "aoh", "aoi", "aoj", - "aok", "aol", "aom", "aon", "aoo", "aop", "aoq", "aor", "aos", "aot", "aou", - "aov", "aow", "aox", "aoy", "aoz", "apa", "apb", "apc", "apd", "ape", "apf", - "apg", "aph", "api", "apj", "apk", "apl", "apm", "apn", "apo", "app", "apq", - "apr", "aps", "apt", "apu", "apv", "apw", "apx", "apy", "apz", "aqa", "aqb", - "aqc", "aqd", "aqe", "aqf", "aqg", "aqh", "aqi", "aqj", "aqk", "aql", "aqm", - "aqn", "aqo", "aqp", "aqq", "aqr", "aqs", "aqt", "aqu", "aqv", "aqw", "aqx", - "aqy", "aqz", "ara", "arb", "arc", "ard", "are", "arf", "arg", "arh", "ari", - "arj", "ark", "arl", "arm", "arn", "aro", "arp", "arq", "arr", "ars", "art", - "aru", "arv", "arw", "arx", "ary", "arz", "asa", "asb", "asc", "asd", "ase", - "asf", "asg", "ash", "asi", "asj", "ask", "asl", "asm", "asn", "aso", "asp", - "asq", "asr", "ass", "ast", "asu", "asv", "asw", "asx", "asy", "asz", "ata", - "atb", "atc", "atd", "ate", "atf", "atg", "ath", "ati", "atj", "atk", "atl", - "atm", "atn", "ato", "atp", "atq", "atr", "ats", "att", "atu", "atv", "atw", - "atx", "aty", "atz", "aua", "aub", "auc", "aud", "aue", "auf", "aug", "auh", - "aui", "auj", "auk", "aul", "aum", "aun", "auo", "aup", "auq", "aur", "aus", - "aut", "auu", "auv", "auw", "aux", "auy", "auz", "ava", "avb", "avc", "avd", - "ave", "avf", "avg", "avh", "avi", "avj", "avk", "avl", "avm", "avn", "avo", - "avp", "avq", "avr", "avs", "avt", "avu", "avv", "avw", "avx", "avy", "avz", - "awa", "awb", "awc", "awd", "awe", "awf", "awg", "awh", "awi", "awj", "awk", - "awl", "awm", "awn", "awo", "awp", "awq", "awr", "aws", "awt", "awu", "awv", - "aww", "awx", "awy", "awz", "axa", "axb", "axc", "axd", "axe", "axf", "axg", - "axh", "axi", "axj", "axk", "axl", "axm", "axn", "axo", "axp", "axq", "axr", - "axs", "axt", "axu", "axv", "axw", "axx", "axy", "axz", "aya", "ayb", "ayc", - "ayd", "aye", "ayf", "ayg", "ayh", "ayi", "ayj", "ayk", "ayl", "aym", "ayn", - "ayo", "ayp", "ayq", "ayr", "ays", "ayt", "ayu", "ayv", "ayw", "ayx", "ayy", - "ayz", "aza", "azb", "azc", "azd", "aze", "azf", "azg", "azh", "azi", "azj", - "azk", "azl", "azm", "azn", "azo", "azp", "azq", "azr", "azs", "azt", "azu", - "azv", "azw", "azx", "azy", "azz", "baa", "bab", "bac", "bad", "bae", "baf", - "bag", "bah", "bai", "baj", "bak", "bal", "bam", "ban", "bao", "bap", "baq", - "bar", "bas", "bat", "bau", "bav", "baw", "bax", - ], - ), - ]; - - for (count, expected) in testcases { - let gen = AlphabeticSuffixGenerator::new(count); - let actual: Vec = gen.collect(); - assert_eq!( - actual, expected, - "unexpected suffix list for count {}", - count - ); - } - } -} diff --git a/src/suffix.rs b/src/suffix.rs new file mode 100644 index 0000000..495bb67 --- /dev/null +++ b/src/suffix.rs @@ -0,0 +1,213 @@ +pub struct AlphabeticSuffixGenerator { + suffix: Vec, + remaining: usize, +} + +impl AlphabeticSuffixGenerator { + pub fn new(count: usize) -> AlphabeticSuffixGenerator { + let suffix_length = AlphabeticSuffixGenerator::calculate_suffix_length(count); + let mut suffix = vec![b'a'; suffix_length]; + suffix[suffix_length - 1] -= 1; + + AlphabeticSuffixGenerator { + suffix, + remaining: count, + } + } + + fn calculate_suffix_length(count: usize) -> usize { + let mut suffix_length = 1; + let mut remainder = count as f64; + loop { + if remainder <= 26.0 { + break; + } + suffix_length += 1; + remainder /= 26.0; + } + suffix_length + } +} + +impl Iterator for AlphabeticSuffixGenerator { + type Item = String; + + fn next(&mut self) -> Option { + if self.remaining == 0 { + return None; + } + for idx in (0..self.suffix.len()).rev() { + if self.suffix[idx] < b'z' { + self.suffix[idx] += 1; + break; + } else { + self.suffix[idx] = b'a'; + } + } + self.remaining -= 1; + Some(String::from_utf8(self.suffix.to_vec()).expect("invalid suffix generated")) + } +} + +pub struct NumericSuffixGenerator { + current: usize, + last: usize, +} + +impl NumericSuffixGenerator { + pub fn new(start: usize, count: usize) -> NumericSuffixGenerator { + NumericSuffixGenerator { + current: start, + last: (start + count), + } + } +} + +impl Iterator for NumericSuffixGenerator { + type Item = String; + + fn next(&mut self) -> Option { + if self.current == self.last { + return None; + } + + let next = self.current.to_string(); + self.current += 1; + Some(next) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_numeric_suffix_generator_output() { + // tuple of start, count + let testcases = vec![(0, 10), (5, 20), (0, 200), (10, 5)]; + + for (start, count) in testcases { + let expected: Vec = (start..(start + count)).map(|i| i.to_string()).collect(); + let actual: Vec = NumericSuffixGenerator::new(start, count).collect(); + assert_eq!(actual, expected) + } + } + + #[test] + fn test_alpha_suffix_generator_min_digits() { + // tuple of count => expected min length of suffix + let testcases = vec![ + (1, 1), + (10, 1), + (26, 1), + (27, 2), + (30, 2), + (700, 3), + (50_000, 4), + ]; + + for (count, expected) in testcases { + let actual = AlphabeticSuffixGenerator::calculate_suffix_length(count); + assert_eq!( + actual, expected, + "for count {} expected suffix length {} got {}", + count, expected, actual + ); + } + } + + #[test] + fn test_alpha_suffix_generator_output() { + // tuple of count => expected suffix vector + let testcases = vec![ + (3, vec!["a", "b", "c"]), + ( + 30, + vec![ + "aa", "ab", "ac", "ad", "ae", "af", "ag", "ah", "ai", "aj", "ak", "al", "am", + "an", "ao", "ap", "aq", "ar", "as", "at", "au", "av", "aw", "ax", "ay", "az", + "ba", "bb", "bc", "bd", + ], + ), + ( + 700, + vec![ + "aaa", "aab", "aac", "aad", "aae", "aaf", "aag", "aah", "aai", "aaj", "aak", + "aal", "aam", "aan", "aao", "aap", "aaq", "aar", "aas", "aat", "aau", "aav", + "aaw", "aax", "aay", "aaz", "aba", "abb", "abc", "abd", "abe", "abf", "abg", + "abh", "abi", "abj", "abk", "abl", "abm", "abn", "abo", "abp", "abq", "abr", + "abs", "abt", "abu", "abv", "abw", "abx", "aby", "abz", "aca", "acb", "acc", + "acd", "ace", "acf", "acg", "ach", "aci", "acj", "ack", "acl", "acm", "acn", + "aco", "acp", "acq", "acr", "acs", "act", "acu", "acv", "acw", "acx", "acy", + "acz", "ada", "adb", "adc", "add", "ade", "adf", "adg", "adh", "adi", "adj", + "adk", "adl", "adm", "adn", "ado", "adp", "adq", "adr", "ads", "adt", "adu", + "adv", "adw", "adx", "ady", "adz", "aea", "aeb", "aec", "aed", "aee", "aef", + "aeg", "aeh", "aei", "aej", "aek", "ael", "aem", "aen", "aeo", "aep", "aeq", + "aer", "aes", "aet", "aeu", "aev", "aew", "aex", "aey", "aez", "afa", "afb", + "afc", "afd", "afe", "aff", "afg", "afh", "afi", "afj", "afk", "afl", "afm", + "afn", "afo", "afp", "afq", "afr", "afs", "aft", "afu", "afv", "afw", "afx", + "afy", "afz", "aga", "agb", "agc", "agd", "age", "agf", "agg", "agh", "agi", + "agj", "agk", "agl", "agm", "agn", "ago", "agp", "agq", "agr", "ags", "agt", + "agu", "agv", "agw", "agx", "agy", "agz", "aha", "ahb", "ahc", "ahd", "ahe", + "ahf", "ahg", "ahh", "ahi", "ahj", "ahk", "ahl", "ahm", "ahn", "aho", "ahp", + "ahq", "ahr", "ahs", "aht", "ahu", "ahv", "ahw", "ahx", "ahy", "ahz", "aia", + "aib", "aic", "aid", "aie", "aif", "aig", "aih", "aii", "aij", "aik", "ail", + "aim", "ain", "aio", "aip", "aiq", "air", "ais", "ait", "aiu", "aiv", "aiw", + "aix", "aiy", "aiz", "aja", "ajb", "ajc", "ajd", "aje", "ajf", "ajg", "ajh", + "aji", "ajj", "ajk", "ajl", "ajm", "ajn", "ajo", "ajp", "ajq", "ajr", "ajs", + "ajt", "aju", "ajv", "ajw", "ajx", "ajy", "ajz", "aka", "akb", "akc", "akd", + "ake", "akf", "akg", "akh", "aki", "akj", "akk", "akl", "akm", "akn", "ako", + "akp", "akq", "akr", "aks", "akt", "aku", "akv", "akw", "akx", "aky", "akz", + "ala", "alb", "alc", "ald", "ale", "alf", "alg", "alh", "ali", "alj", "alk", + "all", "alm", "aln", "alo", "alp", "alq", "alr", "als", "alt", "alu", "alv", + "alw", "alx", "aly", "alz", "ama", "amb", "amc", "amd", "ame", "amf", "amg", + "amh", "ami", "amj", "amk", "aml", "amm", "amn", "amo", "amp", "amq", "amr", + "ams", "amt", "amu", "amv", "amw", "amx", "amy", "amz", "ana", "anb", "anc", + "and", "ane", "anf", "ang", "anh", "ani", "anj", "ank", "anl", "anm", "ann", + "ano", "anp", "anq", "anr", "ans", "ant", "anu", "anv", "anw", "anx", "any", + "anz", "aoa", "aob", "aoc", "aod", "aoe", "aof", "aog", "aoh", "aoi", "aoj", + "aok", "aol", "aom", "aon", "aoo", "aop", "aoq", "aor", "aos", "aot", "aou", + "aov", "aow", "aox", "aoy", "aoz", "apa", "apb", "apc", "apd", "ape", "apf", + "apg", "aph", "api", "apj", "apk", "apl", "apm", "apn", "apo", "app", "apq", + "apr", "aps", "apt", "apu", "apv", "apw", "apx", "apy", "apz", "aqa", "aqb", + "aqc", "aqd", "aqe", "aqf", "aqg", "aqh", "aqi", "aqj", "aqk", "aql", "aqm", + "aqn", "aqo", "aqp", "aqq", "aqr", "aqs", "aqt", "aqu", "aqv", "aqw", "aqx", + "aqy", "aqz", "ara", "arb", "arc", "ard", "are", "arf", "arg", "arh", "ari", + "arj", "ark", "arl", "arm", "arn", "aro", "arp", "arq", "arr", "ars", "art", + "aru", "arv", "arw", "arx", "ary", "arz", "asa", "asb", "asc", "asd", "ase", + "asf", "asg", "ash", "asi", "asj", "ask", "asl", "asm", "asn", "aso", "asp", + "asq", "asr", "ass", "ast", "asu", "asv", "asw", "asx", "asy", "asz", "ata", + "atb", "atc", "atd", "ate", "atf", "atg", "ath", "ati", "atj", "atk", "atl", + "atm", "atn", "ato", "atp", "atq", "atr", "ats", "att", "atu", "atv", "atw", + "atx", "aty", "atz", "aua", "aub", "auc", "aud", "aue", "auf", "aug", "auh", + "aui", "auj", "auk", "aul", "aum", "aun", "auo", "aup", "auq", "aur", "aus", + "aut", "auu", "auv", "auw", "aux", "auy", "auz", "ava", "avb", "avc", "avd", + "ave", "avf", "avg", "avh", "avi", "avj", "avk", "avl", "avm", "avn", "avo", + "avp", "avq", "avr", "avs", "avt", "avu", "avv", "avw", "avx", "avy", "avz", + "awa", "awb", "awc", "awd", "awe", "awf", "awg", "awh", "awi", "awj", "awk", + "awl", "awm", "awn", "awo", "awp", "awq", "awr", "aws", "awt", "awu", "awv", + "aww", "awx", "awy", "awz", "axa", "axb", "axc", "axd", "axe", "axf", "axg", + "axh", "axi", "axj", "axk", "axl", "axm", "axn", "axo", "axp", "axq", "axr", + "axs", "axt", "axu", "axv", "axw", "axx", "axy", "axz", "aya", "ayb", "ayc", + "ayd", "aye", "ayf", "ayg", "ayh", "ayi", "ayj", "ayk", "ayl", "aym", "ayn", + "ayo", "ayp", "ayq", "ayr", "ays", "ayt", "ayu", "ayv", "ayw", "ayx", "ayy", + "ayz", "aza", "azb", "azc", "azd", "aze", "azf", "azg", "azh", "azi", "azj", + "azk", "azl", "azm", "azn", "azo", "azp", "azq", "azr", "azs", "azt", "azu", + "azv", "azw", "azx", "azy", "azz", "baa", "bab", "bac", "bad", "bae", "baf", + "bag", "bah", "bai", "baj", "bak", "bal", "bam", "ban", "bao", "bap", "baq", + "bar", "bas", "bat", "bau", "bav", "baw", "bax", + ], + ), + ]; + + for (count, expected) in testcases { + let gen = AlphabeticSuffixGenerator::new(count); + let actual: Vec = gen.collect(); + assert_eq!( + actual, expected, + "unexpected suffix list for count {}", + count + ); + } + } +}