Number Names - 函数式编程 Kata

TDD · mebusw · Created at · Last by asj Replied at · 1507 hits
14

这是CyberDojo上的一个题目,之前自己用Python做过一次。今天下午与站长结对想用Java再做一次。
一个有趣的觉察是,用不同语言时,思考方式就会有不同。据说一个不喜欢日本人的人,如果学会了说日语,也就对日本人有所改观。

题目

将一个数字翻译成英语,例如

99 --> ninety nine
300 --> three hundred
310 --> three hundred and ten
1501 --> one thousand, five hundred and one
12609 --> twelve thousand, six hundred and nine
512607 --> five hundred and twelve thousand, six hundred and seven
43112603 --> forty three million, one hundred and twelve thousand, six hundred and three
[Source http://rosettacode.org]

TDD单元测试

import org.junit.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;

public class NumberNamesTest {
    @Test
    public void single() {
        assertThat(NumberNames.translate(0), is("zero"));
        assertThat(NumberNames.translate(1), is("one"));
        assertThat(NumberNames.translate(2), is("two"));
        assertThat(NumberNames.translate(3), is("three"));
        assertThat(NumberNames.translate(4), is("four"));
        assertThat(NumberNames.translate(5), is("five"));
        assertThat(NumberNames.translate(6), is("six"));
        assertThat(NumberNames.translate(7), is("seven"));
        assertThat(NumberNames.translate(8), is("eight"));
        assertThat(NumberNames.translate(9), is("nine"));
        assertThat(NumberNames.translate(10), is("ten"));
    }

    @Test
    public void teen() {
        assertThat(NumberNames.translate(11), is("eleven"));
        assertThat(NumberNames.translate(12), is("twelve"));
        assertThat(NumberNames.translate(13), is("thirteen"));
        assertThat(NumberNames.translate(14), is("fourteen"));
        assertThat(NumberNames.translate(15), is("fifteen"));
        assertThat(NumberNames.translate(16), is("sixteen"));
        assertThat(NumberNames.translate(17), is("seventeen"));
        assertThat(NumberNames.translate(18), is("eighteen"));
        assertThat(NumberNames.translate(19), is("nineteen"));
    }

    @Test
    public void ty() {
        assertThat(NumberNames.translate(20), is("twenty"));
        assertThat(NumberNames.translate(30), is("thirty"));
        assertThat(NumberNames.translate(40), is("forty"));
        assertThat(NumberNames.translate(50), is("fifty"));
        assertThat(NumberNames.translate(60), is("sixty"));
        assertThat(NumberNames.translate(70), is("seventy"));
        assertThat(NumberNames.translate(80), is("eighty"));
        assertThat(NumberNames.translate(90), is("ninety"));
    }

    @Test
    public void ty_plus_single() {
        assertThat(NumberNames.translate(21), is("twenty one"));
        assertThat(NumberNames.translate(22), is("twenty two"));
        assertThat(NumberNames.translate(45), is("forty five"));
    }

    @Test
    public void hundred() {
        assertThat(NumberNames.translate(100), is("one hundred"));
        assertThat(NumberNames.translate(200), is("two hundred"));
    }

    @Test
    public void hundred_plus_ty() {
        assertThat(NumberNames.translate(120), is("one hundred and twenty"));
        assertThat(NumberNames.translate(220), is("two hundred and twenty"));
    }

    @Test
    public void hundred_plus_ty_plus_single() {
        assertThat(NumberNames.translate(356), is("three hundred and fifty six"));
        assertThat(NumberNames.translate(306), is("three hundred and six"));
        assertThat(NumberNames.translate(310), is("three hundred and ten"));
    }

    @Test
    public void thousand() {
        assertThat(NumberNames.translate(1000), is("one thousand"));
        assertThat(NumberNames.translate(2000), is("two thousand"));
    }

    @Test
    public void thousand_plus_x() {
        assertThat(NumberNames.translate(1501), is("one thousand, five hundred and one"));
    }

    @Test
    public void ten_thousands() {
        assertThat(NumberNames.translate(12609), is("twelve thousand, six hundred and nine"));
        assertThat(NumberNames.translate(512607), is("five hundred and twelve thousand, six hundred and seven"));

    }

    @Test
    public void million() {
        assertThat(NumberNames.translate(43112603), is("forty three million, one hundred and twelve thousand, six hundred and three"));
        assertThat(NumberNames.translate(43000000), is("forty three million"));
    }
}

实现代码

这里经过重构,采用了Java8的Lambda表达式。其实Lambda在Python等语言中很容易想到做到,但是自己对Java固有的思维方式,使得在Java中很难想到往高阶函数的方向去重构,还需要练习。

import java.util.function.Function;

public class NumberNames {

    public static final int MILLION = 1000000;
    public static final int THOUSAND = 1000;
    public static final int HUNDRED = 100;
    public static final int TEN = 10;
    private static String[] LITERALS = new String[]{
            "zero", "one", "two", "three", "four",
            "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"};
    private static String[] TYS = new String[]{"zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"};


    public static String translate(int num) {
        if (num >= MILLION) return translate(num / MILLION) + " million" + remnant(MILLION, ", ").apply(num);
        if (num >= THOUSAND) return translate(num / THOUSAND) + " thousand" + remnant(THOUSAND, ", ").apply(num);
        if (num >= HUNDRED) return LITERALS[(num / HUNDRED)] + " hundred" + remnant(HUNDRED, " and ").apply(num);
        if (num >= 20) return TYS[(num / TEN)] + remnant(TEN, " ").apply(num);
        return LITERALS[num];
    }

    private static Function<Integer, String> remnant(int unit, String separator) {
        return n -> n % unit != 0 ? separator + translate(n % unit) : "";
    }


}

Github源码

https://github.com/mebusw/NumberNamesKata


「软件匠艺社区」旨在传播匠艺精神,通过分享好的「工作方式」和「习惯」以帮助程序员更加快乐高效地编程。
共收到 1 条回复
377

Python 三脚猫的代码

test case

from number import numberName

def test_one_word_names():
    '''less than 10 '''
    assert numberName(0) == 'zero'
    assert numberName(1) == 'one'
    assert numberName(2) == 'two'
    assert numberName(3) == 'three'
    assert numberName(4) == 'four'
    assert numberName(5) == 'five'
    assert numberName(6) == 'six'
    assert numberName(7) == 'seven'
    assert numberName(8) == 'eight'
    assert numberName(9) == 'nine'
    '''10 to 20'''
    assert numberName(10) == 'ten'
    assert numberName(11) == 'eleven'
    assert numberName(12) == 'twelve'
    assert numberName(13) == 'thirteen'
    assert numberName(14) == 'fourteen'
    assert numberName(15) == 'fifteen'
    assert numberName(16) == 'sixteen'
    assert numberName(17) == 'seventeen'
    assert numberName(18) == 'eighteen'
    assert numberName(19) == 'nineteen'
    assert numberName(20) == 'twenty'
    '''30 to 90'''
    assert numberName(30) == 'thirty'
    assert numberName(40) == 'forty'
    assert numberName(50) == 'fifty'
    assert numberName(60) == 'sixty'
    assert numberName(70) == 'seventy'
    assert numberName(80) == 'eighty'
    assert numberName(90) == 'ninety'

def test_name_and_remaining_names():
    assert numberName(21) == 'twenty one'
    assert numberName(99) == 'ninety nine'

def test_count_and_name_names():
    assert numberName(100) == 'one hundred'
    assert numberName(200) == 'two hundred'

def test_count_and_remaining_names():
    assert numberName(570) == 'five hundred and seventy'

def test_count_thousand_and_remaining_names():
    assert numberName(1000) == 'one thousand'
    assert numberName(1101) == 'one thousand, one hundred and one'
    assert numberName(51000) == 'fifty one thousand'

def test_million():
    assert numberName(3000000) == 'three million'

def test_raidom_big_number():
    assert numberName(512607) == 'five hundred and twelve thousand, six hundred and seven'
    assert numberName(43112603) == 'forty three million, '\
        + 'one hundred and twelve thousand, '\
        + 'six hundred and three'

代码

def numberName(value):
    names = {0:'zero',1:'one',2:'two',3:'three',4:'four',5:'five',6:'six',7:'seven',8:'eight',9:'nine'
        ,10:'ten',11:'eleven',12:'twelve',13:'thirteen',14:'fourteen',15:'fifteen',16:'sixteen',17:'seventeen',18:'eighteen',19:'nineteen'
        ,20:'twenty',30: 'thirty',40:'forty',50:'fifty',60:'sixty',70:'seventy',80:'eighty',90:'ninety'}
    countable_names = {100:'hundred', 1000:'thousand', 1000000:'million'}
    name = ''
    maxNameIndex = getMaxNoMoreThan(value, countable_names.keys())
    if maxNameIndex > 0:
        name = numberName(value//maxNameIndex) +' '+ countable_names.get(maxNameIndex)
        remaining = value % maxNameIndex
        separator = ' and '
        if maxNameIndex > 100:
            separator = ', '
        if remaining > 0:          
            name = name + separator + numberName(remaining)
    else:
        maxNameIndex = getMaxNoMoreThan(value, names.keys())
        name = names.get(maxNameIndex)
        remaining = value - maxNameIndex
        if remaining > 0:
            name = name +' '+ numberName(remaining)
    return name

def getMaxNoMoreThan(value, list):
    indexes = filter(lambda key: key <= value, list)
    if indexes:
        return max(indexes)
    return 0
需要 Sign In 后回复方可回复, 如果你还没有账号你可以 Sign Up 一个帐号。