Решение на Дигитален корен от Илиян Драгнев

Обратно към всички решения

Към профила на Илиян Драгнев

Резултати

  • 20 точки от тестове
  • 1 бонус точка
  • 21 точки общо
  • 6 успешни тест(а)
  • 0 неуспешни тест(а)

Код

pub fn decimal(input: &str) -> Option<u32> {
digital_root(input, Radix::Dec)
}
pub fn hex(input: &str) -> Option<u32> {
digital_root(input, Radix::Hex)
}
pub fn octal(input: &str) -> Option<u32> {
digital_root(input, Radix::Oct)
}
pub fn binary(input: &str) -> Option<u32> {
digital_root(input, Radix::Bin)
}
#[derive(Debug, Copy, Clone)]
enum Radix {
Hex,
Dec,
Oct,
Bin,
}
impl Radix {
fn to_u32(radix: Radix) -> u32 {
match radix {
Radix::Hex => 16,
Radix::Dec => 10,
Radix::Oct => 8,
Radix::Bin => 2,
}
}
}
fn digital_root(num: &str, radix: Radix) -> Option<u32> {
if is_valid_number(num, radix) {
let root = digital_root_as_string(num, radix);
let root = digital_root_str_to_u32(&root, radix);
Some(root)
}
else {
None
}
}
fn digital_root_str_to_u32(num: &str, r: Radix) -> u32 {
let radix = Radix::to_u32(r);
u32::from_str_radix(num, radix).unwrap()
}
fn is_valid_number(num: &str, r: Radix) -> bool {
num.trim()
.chars()
.all(|c| c.is_digit(Radix::to_u32(r)))
}
fn digital_root_as_string(num: &str, radix: Radix) -> String {
let mut result = String::from(num.trim());
while result.len() > 1 {
let sum = sum_digits(&result, radix);
result = num_to_string(sum, radix);
}
result
}
fn sum_digits(num: &str, r: Radix) -> u32 {
num.trim()
.chars()
.map(|c| c.to_digit(Radix::to_u32(r)))
.map(|opt| opt.unwrap())
.sum()
}
fn num_to_string(mut num: u32, radix: Radix) -> String {
if num == 0 {
return String::from("0");
}
let radix = Radix::to_u32(radix);
let mut digits: Vec<char> = Vec::new();
while num > 0 {
let last_digit = num % radix;
let last_digit = u32_digit_to_char(last_digit);
digits.push(last_digit);
num = num / radix;
};
digits.iter().rev().collect()
}
fn u32_digit_to_char(digit: u32) -> char {
match digit {
0 => '0',
1 => '1',
2 => '2',
3 => '3',
4 => '4',
5 => '5',
6 => '6',
7 => '7',
8 => '8',
9 => '9',
10 => 'a',
11 => 'b',
12 => 'c',
13 => 'd',
14 => 'e',
15 => 'f',
_ => panic!("invalid digit given to u32_digit_to_char"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_valid_bin() {
assert!(is_valid_number("01", Radix::Bin));
assert!(!is_valid_number("2", Radix::Bin));
assert!(!is_valid_number("a", Radix::Bin));
}
#[test]
fn is_valid_oct() {
assert!(is_valid_number("01234567", Radix::Oct));
assert!(!is_valid_number("8", Radix::Oct));
assert!(!is_valid_number("9", Radix::Oct));
}
#[test]
fn is_valid_dec() {
assert!(is_valid_number("0123456789", Radix::Dec));
assert!(!is_valid_number("g", Radix::Dec));
}
#[test]
fn is_valid_hex() {
assert!(is_valid_number("0123456789abcdef", Radix::Hex));
assert!(!is_valid_number("g", Radix::Hex));
}
#[test]
fn sum_digits_with_bin_radix() {
for n in 1..10 {
let ones = "1".repeat(n);
let zeros = "0".repeat(n);
let num = ones + &zeros;
assert_eq!(sum_digits(&num, Radix::Bin), n as u32);
}
}
#[test]
fn sum_digits_0_to_7_with_oct_dec_and_hex_radix() {
for radix in [Radix::Hex, Radix::Dec, Radix::Oct].iter() {
assert_eq!(sum_digits("01234567", *radix), (1..8).sum(), "radix: {:?}", radix);
}
}
#[test]
fn sum_digits_8_to_f_with_hex_radix() {
assert_eq!(sum_digits("89abcdef", Radix::Hex), (8..16).sum());
}
#[test]
fn num_to_string_bin() {
let expected_strings = ["0", "1", "10", "11", "100"].iter();
let pairs = (0..5).zip(expected_strings);
for (num, expected) in pairs {
let result = num_to_string(num, Radix::Bin);
assert_eq!(&result, *expected);
}
}
#[test]
fn num_to_string_oct() {
let expected_strings = ["242", "243", "542", "2000", "2322"].iter();
let nums = [162, 163, 354, 1024, 1234].iter();
let pairs = nums.zip(expected_strings);
for (num, expected) in pairs {
let result = num_to_string(*num, Radix::Oct);
assert_eq!(&result, *expected);
}
}
#[test]
fn num_to_string_dec() {
for n in 0..1000 {
assert_eq!(num_to_string(n, Radix::Dec), n.to_string());
}
}
#[test]
fn num_to_string_hex() {
let expected_strings = ["a2", "a3", "162", "400", "4d2"].iter();
let nums = [162, 163, 354, 1024, 1234].iter();
let pairs = nums.zip(expected_strings);
for (num, expected) in pairs {
let result = num_to_string(*num, Radix::Hex);
assert_eq!(&result, *expected);
}
}
#[test]
fn digital_root_as_string_bin() {
let nums = ["0101", "0", "1110011111"].iter();
let expected_results = ["1", "0", "1"].iter();
let pairs = nums.zip(expected_results);
for (num, expected) in pairs {
let result = digital_root_as_string(num, Radix::Bin);
assert_eq!(&result, *expected);
}
}
#[test]
fn digital_root_as_string_oct() {
let nums = ["345", "1024", "564"].iter();
let expected_results = ["5", "7", "1"].iter();
let pairs = nums.zip(expected_results);
for (num, expected) in pairs {
let result = digital_root_as_string(num, Radix::Oct);
assert_eq!(&result, *expected);
}
}
#[test]
fn digital_root_as_string_dec() {
let nums = ["345", "564", "1024"].iter();
let expected_results = ["3", "6", "7"].iter();
let pairs = nums.zip(expected_results);
for (num, expected) in pairs {
let result = digital_root_as_string(num, Radix::Dec);
assert_eq!(&result, *expected);
}
}
#[test]
fn digital_root_as_string_hex() {
let nums = ["345", "564", "1024", "7b"].iter();
let expected_results = ["c", "f", "7", "3"].iter();
let pairs = nums.zip(expected_results);
for (num, expected) in pairs {
let result = digital_root_as_string(num, Radix::Hex);
assert_eq!(&result, *expected);
}
}
#[test]
fn digital_root_bin_valid() {
let nums = ["0101", "0", "1110011111"].iter();
let expected_results = [1, 0, 1].iter();
let pairs = nums.zip(expected_results);
for (num, expected) in pairs {
let result = digital_root(num, Radix::Bin);
assert_eq!(result, Some(*expected));
}
}

Този тип тестове са малко трудни за четене. Щеше да е само 3 реда да се напише без цикъла:

assert_eq!(digital_root("0101",       Radix::Bin), Some(1));
assert_eq!(digital_root("0",          Radix::Bin), Some(0));
assert_eq!(digital_root("1110011111", Radix::Bin), Some(1));

На теория, твоя метод ще е по-кратък ако имаш повече примери, но не е дължината главния проблем. Правенето на списъци с входове, изходи, и резултати има ефекта, че "обръща" логиката. Трябва да напасваш по колони какво се очаква да е равно на какво друго, и ако тези списъци са дълги, това може да е доста досадно. Може да е полезно за някои ситуации, но много често няма да е.

Тестовете често е по-удачно да имат повторение, отколкото твърде много абстракция, понеже стават трудни за четене и трудни за дебъгване. Примерно, ако този тест fail-не, реда който ти бъде даден, ще бъде просто ред в цикъла и ще трябва да гледаш конкретните стойности, за да разбереш кой пример се е провалил. Ако тези стойности са по-сложни, това може да иска доста взиране в екрана.

Property-based testing може би би ти свършило работа, стига да намериш какви property-та искаш да тестваш. Обикновено property-based testing е добро допълнение към тестове, които валидират познати вход и изход.

#[test]
fn digital_root_bin_invalid() {
for i in 2..10 {
let num = i.to_string();
assert_eq!(digital_root(&num, Radix::Bin), None);
}
}
#[test]
fn digital_root_as_string_oct_valid() {
let nums = ["345", "1024", "564"].iter();
let expected_results = [5, 7, 1].iter();
let pairs = nums.zip(expected_results);
for (num, expected) in pairs {
let result = digital_root(num, Radix::Oct);
assert_eq!(result, Some(*expected));
}
}
#[test]
fn digital_root_oct_invalid() {
for i in 8..10 {
let num = i.to_string();
assert_eq!(digital_root(&num, Radix::Oct), None);
}
}
#[test]
fn digital_root_as_string_dec_valid() {
let nums = ["345", "564", "1024"].iter();
let expected_results = [3, 6, 7].iter();
let pairs = nums.zip(expected_results);
for (num, expected) in pairs {
let result = digital_root(num, Radix::Dec);
assert_eq!(result, Some(*expected));
}
}
#[test]
fn digital_root_as_string_dec_invalid() {
for num in ["a", "b", "c", "d", "e", "f"].iter() {
assert_eq!(digital_root(num, Radix::Dec), None);
}
}
#[test]
fn digital_root_as_string_hex_valid() {
let nums = ["345", "564", "1024", "7b"].iter();
let expected_results = [0xc, 0xf, 0x7, 0x3].iter();
let pairs = nums.zip(expected_results);
for (num, expected) in pairs {
let result = digital_root(num, Radix::Hex);
assert_eq!(result, Some(*expected));
}
}
#[test]
fn digital_root_as_string_hex_invalid() {
for num in ["h", "i", "j", "k"].iter() {
assert_eq!(digital_root(num, Radix::Hex), None);
}
}
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20200111-2173579-1iwlg94/solution)
    Finished test [unoptimized + debuginfo] target(s) in 11.18s
     Running target/debug/deps/solution-a73e64ec87929bd0

running 23 tests
test tests::digital_root_as_string_bin ... ok
test tests::digital_root_as_string_dec ... ok
test tests::digital_root_as_string_dec_invalid ... ok
test tests::digital_root_as_string_dec_valid ... ok
test tests::digital_root_as_string_hex ... ok
test tests::digital_root_as_string_hex_invalid ... ok
test tests::digital_root_as_string_hex_valid ... ok
test tests::digital_root_as_string_oct ... ok
test tests::digital_root_as_string_oct_valid ... ok
test tests::digital_root_bin_invalid ... ok
test tests::digital_root_bin_valid ... ok
test tests::digital_root_oct_invalid ... ok
test tests::is_valid_bin ... ok
test tests::is_valid_dec ... ok
test tests::is_valid_hex ... ok
test tests::is_valid_oct ... ok
test tests::num_to_string_bin ... ok
test tests::num_to_string_dec ... ok
test tests::num_to_string_hex ... ok
test tests::num_to_string_oct ... ok
test tests::sum_digits_0_to_7_with_oct_dec_and_hex_radix ... ok
test tests::sum_digits_8_to_f_with_hex_radix ... ok
test tests::sum_digits_with_bin_radix ... ok

test result: ok. 23 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

     Running target/debug/deps/solution_test-38971695424b36d5

running 6 tests
test solution_test::test_binary ... ok
test solution_test::test_decimal_basic ... ok
test solution_test::test_hex_basic ... ok
test solution_test::test_invalid ... ok
test solution_test::test_octal_basic ... ok
test solution_test::test_zeroes ... ok

test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests solution

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

История (1 версия и 9 коментара)

Илиян качи първо решение на 03.11.2019 22:23 (преди почти 6 години)

Имаш интересен подход към задачата. Харесва ми enum-а Radix, макар че за толкова проста задача изглежда като че количеството конвертиране не си заслужава употребата му. Добро упражнение е, ако не друго.

Разделянето на кода на определени парчета е деликатна работа. На няколко места използваш unwrap, понеже валидацията и употребата ти са разделени. Това има проблема, че в рамките на една функция, където се използва unwrap, ти имплицитно разчиташ на това, че преди това някак си валидирал, че този unwrap няма да гръмне. А това може често да е опасно предположение. В една по-голяма система, валидацията и обработката може да са ти много по-ясно отделени и "валидация" да значи и някаква форма на preprocessing. В случая, функциите които валидират и тези, които обработват, са смешени -- на едно и също ниво са и не е очевидно, че едните трябва да се викат преди другите. Basically, is_valid_digits и sum_digits са tightly coupled -- има връзка между тяхната употреба, но няма връзка между тях в кода.

Ако нямаше is_valid_digits, а вместо това sum_digits връщаше опционална стойност, и по-нагоре digital_root_as_string също връщаше Option<String>, мисля, че кода щеше да се опрости доста. Пробвай, ако искаш.

Давам ти и бонус точка за тестовете, дори и да не съм съвсем съгласен как си ги структурирал :).

Качването на друго решение няма как да стане, понеже сайта автоматично блокира качване на решения след крайния срок. Приеми го като feedback, за който сам да си помислиш, и пробвай да го приложиш на второ домашно :).