代码高尔夫挑战:数字转文字

39

“代码高尔夫”系列似乎相当受欢迎。我发现了一些将数字转换为其单词表示的代码。一些示例是(作为编程乐趣的2的幂):

  • 2 -> 二
  • 1024 -> 一千零二十四
  • 1048576 -> 一百万零四十八万五千五百七十六

我的同事想出来的算法几乎有两百行长。看起来应该有更简洁的方法。

当前指南:

  • 欢迎使用任何编程语言提交(对于最初不太清楚的PhiLho表示歉意)
  • 最大输入为2 ^ 64(请参见以下链接以获取单词,感谢mmeyers)
  • 优先选择英文输出的短量数系统,但任何算法都可以。只需在编程语言旁加上注释即可使用方法。

它是否必须考虑本地化问题,例如http://en.wikipedia.org/wiki/Long_and_short_scales? - Michael Myers
如果你愿意的话,奖励分数可以给你,但我的初衷是使用短计数法。 - Jason Z
我假设输出文本必须是英文的。 - Toon Krijthe
也许应该计算键入字符的数量而不是行数?许多编程语言允许一行中有多个步骤,而其他语言则不允许。 - Marcin
22个回答

70

好的,我想现在是时候用 Windows BATCH 脚本进行自己的实现了(应该适用于 Windows 2000 或更高版本)。

以下是代码:

@echo off

set zero_to_nineteen=Zero One Two Three Four Five Six Seven Eight Nine Ten Eleven Twelve Thirteen Fourteen Fifteen Sixteen Seventeen Eighteen Nineteen
set twenty_to_ninety=ignore ignore Twenty Thirty Forty Fifty Sixty Seventy Eighty Ninety
set big_numbers=ignore Thousand Million Billion Trillion Quadrillion Quintillion Sextillion Septillion Octillion Nonillion Decillion Undecillion Duodecillion Tredecillion Quattuordecillion Quindecillion Sexdecillion Septendecillion Octodecillion Novemdecillion Vigintillion
rem             10^0   10^3     10^6    10^9    10^12    10^15       10^18       10^21      10^24      10^27     10^30     10^33     10^36       10^39        10^42        10^45             10^48         10^51        10^54           10^57         10^60          10^63

call :parse_numbers %*

exit /B 0

:parse_numbers
    :parse_numbers_loop
        if "$%~1" == "$" goto parse_numbers_end
        call :parse_number %~1
        echo %~1 -^> %parse_number_result%
        shift
        goto parse_numbers_loop
    :parse_numbers_end
    exit /B 0

:parse_number
    call :get_sign %~1
    set number_sign=%get_sign_result%
    call :remove_groups %get_sign_result_number%
    call :trim_leading_zeros %remove_groups_result%
    set number=%trim_leading_zeros_result%
    if "$%number%" == "$0" (
        set parse_number_result=Zero
        exit /B 0
    )
    set counter=0
    set parse_number_result=
    :parse_number_loop
        set last_three=%number:~-3%
        set number=%number:~0,-3%
        call :parse_three %last_three%
        call :get_from %counter% %big_numbers%
        if "$%get_from_result%" == "$" (
            set parse_number_result=* ERR: the number is too big! Even wikipedia doesn't know how it's called!
            exit /B 0
        )
        if not "$%parse_three_result%" == "$Zero" (
            if %counter% == 0 (
                set parse_number_result=%parse_three_result%
            ) else (
                if not "$%parse_number_result%" == "$" (
                    set parse_number_result=%parse_three_result% %get_from_result% %parse_number_result%
                ) else (
                    set parse_number_result=%parse_three_result% %get_from_result%
                )
            )
        )
        set /A counter+=1
        if not "$%number%" == "$" goto parse_number_loop
    if "$%parse_number_result%" == "$" (
        set parse_number_result=Zero
        exit /B 0
    ) else if not "$%number_sign%" == "$" (
        set parse_number_result=%number_sign% %parse_number_result%
    )
    exit /B 0

:parse_three
    call :trim_leading_zeros %~1
    set three=%trim_leading_zeros_result%
    set /A three=%three% %% 1000
    set /A two=%three% %% 100
    call :parse_two %two%
    set parse_three_result=
    set /A digit=%three% / 100
    if not "$%digit%" == "$0" (
        call :get_from %digit% %zero_to_nineteen%
    )
    if not "$%digit%" == "$0" (
        if not "$%get_from_result%" == "$Zero" (
            set parse_three_result=%get_from_result% Hundred
        )
    )
    if "$%parse_two_result%" == "$Zero" (
        if "$%parse_three_result%" == "$" (
            set parse_three_result=Zero
        )
    ) else (
        if "$%parse_three_result%" == "$" (
            set parse_three_result=%parse_two_result%
        ) else (
            set parse_three_result=%parse_three_result% %parse_two_result%
        )
    )
    exit /B 0

:parse_two
    call :trim_leading_zeros %~1
    set two=%trim_leading_zeros_result%
    set /A two=%two% %% 100
    call :get_from %two% %zero_to_nineteen%
    if not "$%get_from_result%" == "$" (
        set parse_two_result=%get_from_result%
        goto parse_two_20_end
    )
    set /A digit=%two% %% 10
    call :get_from %digit% %zero_to_nineteen%
    set parse_two_result=%get_from_result%
    set /A digit=%two% / 10
    call :get_from %digit% %twenty_to_ninety%
    if not "$%parse_two_result%" == "$Zero" (
        set parse_two_result=%get_from_result% %parse_two_result%
    ) else (
        set parse_two_result=%get_from_result%
    )
    goto parse_two_20_end
    :parse_two_20_end
    exit /B 0

:get_from
    call :trim_leading_zeros %~1
    set idx=%trim_leading_zeros_result%
    set /A idx=0+%~1
    shift
    :get_from_loop
        if "$%idx%" == "$0" goto get_from_loop_end
        set /A idx-=1
        shift
        goto get_from_loop
    :get_from_loop_end
    set get_from_result=%~1
    exit /B 0

:trim_leading_zeros
    set str=%~1
    set trim_leading_zeros_result=
    :trim_leading_zeros_loop
        if not "$%str:~0,1%" == "$0" (
            set trim_leading_zeros_result=%trim_leading_zeros_result%%str%
            exit /B 0
        )
        set str=%str:~1%
        if not "$%str%" == "$" goto trim_leading_zeros_loop
    if "$%trim_leading_zeros_result%" == "$" set trim_leading_zeros_result=0
    exit /B 0

:get_sign
    set str=%~1
    set sign=%str:~0,1%
    set get_sign_result=
    if "$%sign%" == "$-" (
        set get_sign_result=Minus
        set get_sign_result_number=%str:~1%
    ) else if "$%sign%" == "$+" (
        set get_sign_result_number=%str:~1%
    ) else (
        set get_sign_result_number=%str%
    )
    exit /B 0

:remove_groups
    set str=%~1
    set remove_groups_result=%str:'=%
    exit /B 0

这是我使用的测试脚本:

@echo off
rem 10^x:x= 66  63  60  57  54  51  48  45  42  39  36  33  30  27  24  21  18  15  12   9   6   3   0
call number                                                                                          0
call number                                                                                          2
call number                                                                                        -17
call number                                                                                         30
call number                                                                                         48
call number                                                                                       -256
call number                                                                                        500
call number                                                                                        874
call number                                                                                      1'024
call number                                                                                    -17'001
call number                                                                                    999'999
call number                                                                                  1'048'576
call number                                                                         -1'000'001'000'000
call number                                                                    912'345'014'587'957'003
call number                                                       -999'912'345'014'587'124'337'999'999
call number                                        111'222'333'444'555'666'777'888'999'000'000'000'001
call number                               -912'345'014'587'912'345'014'587'124'912'345'014'587'124'337
call number    999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999
call number  1'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000
rem 10^x:x= 66  63  60  57  54  51  48  45  42  39  36  33  30  27  24  21  18  15  12   9   6   3   0

这是我从测试脚本中得到的输出结果:

0 -> Zero
2 -> Two
-17 -> Minus Seventeen
30 -> Thirty
48 -> Forty Eight
-256 -> Minus Two Hundred Fifty Six
500 -> Five Hundred
874 -> Eight Hundred Seventy Four
1'024 -> One Thousand Twenty Four
-17'001 -> Minus Seventeen Thousand One
999'999 -> Nine Hundred Ninety Nine Thousand Nine Hundred Ninety Nine
1'048'576 -> One Million Forty Eight Thousand Five Hundred Seventy Six
-1'000'001'000'000 -> Minus One Trillion One Million
912'345'014'587'957'003 -> Nine Hundred Twelve Quadrillion Three Hundred Forty Five Trillion Fourteen Billion Five Hundred Eighty Seven Million Nine Hundred Fifty Seven Thousand Three
-999'912'345'014'587'124'337'999'999 -> Minus Nine Hundred Ninety Nine Septillion Nine Hundred Twelve Sextillion Three Hundred Forty Five Quintillion Fourteen Quadrillion Five Hundred Eighty Seven Trillion One Hundred Twenty Four Billion Three Hundred Thirty Seven Million Nine Hundred Ninety Nine Thousand Nine Hundred Ninety Nine
111'222'333'444'555'666'777'888'999'000'000'000'001 -> One Hundred Eleven Undecillion Two Hundred Twenty Two Decillion Three Hundred Thirty Three Nonillion Four Hundred Forty Four Octillion Five Hundred Fifty Five Septillion Six Hundred Sixty Six Sextillion Seven Hundred Seventy Seven Quintillion Eight Hundred Eighty Eight Quadrillion Nine Hundred Ninety Nine Trillion One
-912'345'014'587'912'345'014'587'124'912'345'014'587'124'337 -> Minus Nine Hundred Twelve Tredecillion Three Hundred Forty Five Duodecillion Fourteen Undecillion Five Hundred Eighty Seven Decillion Nine Hundred Twelve Nonillion Three Hundred Forty Five Octillion Fourteen Septillion Five Hundred Eighty Seven Sextillion One Hundred Twenty Four Quintillion Nine Hundred Twelve Quadrillion Three Hundred Forty Five Trillion Fourteen Billion Five Hundred Eighty Seven Million One Hundred Twenty Four Thousand Three Hundred Thirty Seven
999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999'999 -> Nine Hundred Ninety Nine Vigintillion Nine Hundred Ninety Nine Novemdecillion Nine Hundred Ninety Nine Octodecillion Nine Hundred Ninety Nine Septendecillion Nine Hundred Ninety Nine Sexdecillion Nine Hundred Ninety Nine Quindecillion Nine Hundred Ninety Nine Quattuordecillion Nine Hundred Ninety Nine Tredecillion Nine Hundred Ninety Nine Duodecillion Nine Hundred Ninety Nine Undecillion Nine Hundred Ninety Nine Decillion Nine Hundred Ninety Nine Nonillion Nine Hundred Ninety Nine Octillion Nine Hundred Ninety Nine Septillion Nine Hundred Ninety Nine Sextillion Nine Hundred Ninety Nine Quintillion Nine Hundred Ninety Nine Quadrillion Nine Hundred Ninety Nine Trillion Nine Hundred Ninety Nine Billion Nine Hundred Ninety Nine Million Nine Hundred Ninety Nine Thousand Nine Hundred Ninety Nine
1'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000'000 -> * ERR: the number is too big! Even wikipedia doesn't know how it's called!

如果我能找到更多的大数名称, 这个脚本将支持更大的数字。但目前,该脚本可以处理从-(10^66-1)到(10^66-1)的所有数字。
我必须提到,用批处理解决这个问题非常有趣。 :)

1
太棒了,让我回想起在90年代初的工作中处理成千上万行DOS批处理文件的不太愉快的回忆。 - Darius Bacon
15
好的主啊,+1 给予了我无法拥有的耐心水平。 - Robert Gamble
是的,我从来不必使用批处理 - 我只是喜欢它,所以我在代码高尔夫问题中使用它... ;) 顺便感谢您的点赞。 :) - Paulius
真是惊人。还有什么比这更难开发呢?也许是玩汇编语言吧?+1 - Fernando Miguélez
可能无法在代码高尔夫中获胜...但我必须向你的技能/受虐倾向致敬。 - bogertron
显示剩余3条评论

45

C# - 30行,包括方法声明和{ }:

考虑到之前提到的所有逗号、and和连字符。我只包括了到千亿位,因为decimal.MaxValue只有千亿位的数量级。如果需要更大的整数,您需要将相应的项目添加到thou[]数组中,并可能将数字作为字符串传递,修改提取块的那一行,使用最后3个字符而不是像我这里使用模数。

    static string wordify(decimal v)
    {
        if (v == 0) return "zero";
        var units = " one two three four five six seven eight nine".Split();
        var teens = " eleven twelve thir# four# fif# six# seven# eigh# nine#".Replace("#", "teen").Split();
        var tens = " ten twenty thirty forty fifty sixty seventy eighty ninety".Split();
        var thou = " thousand m# b# tr# quadr# quint# sext# sept# oct#".Replace("#", "illion").Split();
        var g = (v < 0) ? "minus " : "";
        var w = "";
        var p = 0;
        v = Math.Abs(v);
        while (v > 0)
        {
            int b = (int)(v % 1000);
            if (b > 0)
            {
                var h = (b / 100);
                var t = (b - h * 100) / 10;
                var u = (b - h * 100 - t * 10);
                var s = ((h > 0) ? units[h] + " hundred" + ((t > 0 | u > 0) ? " and " : "") : "")
                      + ((t > 0) ? (t == 1 && u > 0) ? teens[u] : tens[t] + ((u > 0) ? "-" : "") : "")
                      + ((t != 1) ? units[u] : "");
                s = (((v > 1000) && (h == 0) && (p == 0)) ? " and " : (v > 1000) ? ", " : "") + s;
                w = s + " " + thou[p] + w;
            }
            v = v / 1000;
            p++;
        }
        return g + w;
    }

使用以下方式调用:

static void Main(string[] args)
{
  Console.WriteLine(wordify(decimal.MaxValue));
}

输出:

七十九千万亿,二百二十八万亿,一百六十二万亿,五百十四万亿,二百六十四万亿,三百三十七万亿,五千五百九十三亿,五千四百三十万,九百五十三千三百三十五


3
因为十进制类型是一个128位的数字,这意味着它非常巨大,无论“十进制”精度如何。他只是想试试用一个巨大的数字。 - Anthony
1
你确定不是“sextillion”吗?你目前写的是“sexillion”。 - Maxim Zaslavsky
如果你使用递归,你可以将这个程序缩短一行。 - tvanfosson
@tvanfosson 嗯...我原本写这个时尝试过,但认为没有更简短的方式。不过我会再看一遍的。 - BenAlabaster
var g = (v < 0) ? "minus " : ""; 可以变成 if (v < 0) return "minus " + wordify(-v); -- 这样就不需要 v = Math.Abs(v);. - tvanfosson
显示剩余2条评论

40

A86汇编器中 - 组装为一个 .COM 可执行文件:

dd 0ba02c6bfh, 0b8bd10c1h, 0e808b512h, 0ea870100h, 08700e9e8h, 010273eah
dd 0e0e8c2h, 06b51872h, 0c000ebe8h, 0b3c02e8h, 03368067dh, 0b2e901h
dd 0baaa5004h, 0fd8110c1h, 0cd7c1630h, 0bf3031bbh, 0a0571000h, 0ec880080h
dd 0c581c589h, 023c0081h, 0e7f087ch, 0823e38h, 027b00875h, 0e901d068h
dd 0b6400080h, 04f6f603h, 080d08a1ch, 0b60f80c4h, 07f06c7f4h, 088303000h
dd 0ac00813eh, 087ef828h, 0b00056e8h, 051e81dh, 0d83850adh, 0e7f157ch
dd 0a74fc38h, 0262ce088h, 0e901a368h, 01d2c003bh, 0580036e8h, 0b7efc38h
dd 0774d838h, 0f828e088h, 0800026e8h, 0127e1dfah, 0afd448ah, 0440afe44h
dd 074f838ffh, 0e8c28a05h, 0cafe000fh, 0ab7cee39h, 05a2405c6h, 021cd09b4h
dd 05e856c3h, 020b05e00h, 0c5bec3aah, 074c00a02h, 03c80460ah, 0fefa755bh
dd 046f675c8h, 0745b3cach, 0f8ebaae8h, 0eec1d689h, 08a3c8a03h, 07e180cah
dd 0cfd2c1feh, 0ebe8c342h, 0fed8d0ffh, 0c3f775cdh, 01e581e8fh, 0303c5ea8h
dd 0df6f652ah, 078bde03ch, 05e027500h, 01ec1603ch, 07d40793dh, 0603c8080h
dd 09f6f2838h, 040f17a3dh, 080f17a22h, 0403d7264h, 0793cdee1h, 0140740f1h
dd 01e2f7d32h, 02f488948h, 0a7c43b05h, 0a257af9bh, 0be297b6ch, 04609e30ah
dd 0b8f902abh, 07c21e13eh, 09a077d9eh, 054f82ab5h, 0fabe2af3h, 08a6534cdh
dd 0d32b4c97h, 035c7c8ceh, 082bcc833h, 0f87f154fh, 0650ff7eah, 02f143fdfh
dd 0a1fd687fh, 0c3e687fdh, 0c6d50fe0h, 075f13574h, 0898c335bh, 0e748ce85h
dd 08769676fh, 0ad2cedd3h, 0928c77c7h, 077e2d18eh, 01a77e8f6h
db 0bah, 01bh

这是一个 454 字节的可执行文件。

这是(稍微小一些的)代码。由于 A86 是仅支持 8086 的汇编器,我不得不手动编写 32 位扩展:

    mov di,strings
    mov dx,tree_data * 8 + 1
    mov bp,code_data * 8
l1:
    mov ch,8
    call extract_bits
    xchg dx,bp
    call extract_bit
    xchg dx,bp
    jnc l2
    add dx,ax
l2:
    call extract_bit
    jc l3
    mov ch,6
    call extract_bits
    shr al,2
    cmp al,11
    push l27
    jl get_string
l25:
    add al,48+32
    stosb
l27:
    mov dx,tree_data * 8 + 1
l3:
    cmp bp,end_data * 8
    jl l1

convert:
    mov bx,'01'
    mov di,01000h
    push di

    mov al,[80h]
    mov ah,ch
    mov bp,ax
    add bp,81h
    cmp al,2
    jl zero
    jg l90
    cmp byte ptr [82h],bh
    jne l90
zero:   
    mov al,39
    push done

get_string:
    mov si,strings-1
    or al,al
    je l36
l35:
    inc si
    cmp byte ptr [si],';'+32
    jne l35
    dec al
    jnz l35
l36:
    inc si
l37:
    lodsb
    cmp al,';'+32
    je ret
    stosb
    jmp l37


l90:
    inc ax
    mov dh,3
    div dh
    add al,28
    mov dl,al
    add ah,80h
    db 0fh, 0b6h, 0f4h ; movzx si,ah
    mov word ptr [80h],'00'

l95:    
    lodsb

    sub al,bh
    jle l100
    call get_string2
    mov al,29
    call get_string2

l100:
    lodsw
    push ax
    cmp al,bl
    jl l150
    jg l140
    cmp ah,bh
    je l140

    mov al,ah
    sub al,'0'-10
    push l150

get_string2:
    push si
    call get_string
    pop si
    mov al,' '
    stosb
    ret

l140:
    sub al,'0'-19
    call get_string2

l150:
    pop ax
    cmp ah,bh
    jle l200
    cmp al,bl
    je l200
    mov al,ah
    sub al,bh
    call get_string2

l200:
    cmp dl,29
    jle l300

    mov al,[si-3]
    or al,[si-2]
    or al,[si-1]
    cmp al,bh
    je l300

    mov al,dl
    call get_string2

l300:
    dec dl
    cmp si,bp
    jl l95

done:   
    mov byte ptr [di],'$'
    pop dx
    mov ah,9
    int 21h 
    int 20h

l41:
    rcr al,1
    dec ch
    jz ret

extract_bits:
    push l41
extract_bit:
    mov si,dx
    shr si,3
    mov bh,[si]
    mov cl,dl
    and cl,7
    inc cl
    ror bh,cl
    inc dx
    ret

tree_data:
    dw 01e8fh, 01e58h, 05ea8h, 0303ch, 0652ah, 0df6fh, 0e03ch, 078bdh
    dw 07500h, 05e02h, 0603ch, 01ec1h, 0793dh, 07d40h, 08080h, 0603ch
    dw 02838h, 09f6fh, 07a3dh, 040f1h, 07a22h, 080f1h, 07264h, 0403dh
    dw 0dee1h, 0793ch, 040f1h, 01407h, 07d32h, 01e2fh, 08948h
    db 048h
code_data:
    dw 052fh, 0c43bh, 09ba7h, 057afh, 06ca2h, 0297bh, 0abeh, 09e3h
    dw 0ab46h, 0f902h, 03eb8h, 021e1h, 09e7ch, 077dh, 0b59ah, 0f82ah
    dw 0f354h, 0be2ah, 0cdfah, 06534h, 0978ah, 02b4ch, 0ced3h, 0c7c8h
    dw 03335h, 0bcc8h, 04f82h, 07f15h, 0eaf8h, 0ff7h, 0df65h, 0143fh
    dw 07f2fh, 0fd68h, 0fda1h, 0e687h, 0e0c3h, 0d50fh, 074c6h, 0f135h
    dw 05b75h, 08c33h, 08589h, 048ceh, 06fe7h, 06967h, 0d387h, 02cedh
    dw 0c7adh, 08c77h, 08e92h, 0e2d1h, 0f677h, 077e8h, 0ba1ah
    db 01bh
end_data:

strings:

这段文本使用哈夫曼编码进行存储。命令行以字符串形式传递,因此转换很简单-将字符串分成三组,并解析每个组(百位、十位和个位),然后在每个组之后跟随当前乘数(百万、千等)。


36

仅使用标准函数的 Lisp:

(format nil "~r" 1234) ==> "one thousand two hundred thirty-four"

奖金:

(format nil "~@r" 1234)  ==> "MCCXXXIV"

2
你在“一百”和“三十四”之间漏掉了“和” :-) - Adrian Pronk
3
好的。OP举的例子没有包括“and”,其他我看到的解决方案也是一样。英语不是我的强项,但我认为拼写大数字的正确方式是不用“and”。 - Robert Gamble
6
我认为正确的方式是不加“and”,除非它们是两个独立的数字(或者是组合形式:约翰有两美元,吉姆有五十美分,他们一共有两美元五十美分)。 - Michael Paulukonis
11
据我所知,“and”更多地是英式英语的用法(正确的方式 ;)。 - Lucas Jones
1
这算作弊吗?从实际意义上讲,这是有道理的。这是什么样的代码高尔夫比赛? - Macha
显示剩余4条评论

28

C ++,15行:

#include <string>
using namespace std;

string Thousands[] = { "zero", "thousand", "million", "billion", "trillion", "quadrillion", "quintillion", "sexillion", "septillion", "octillion", "nonillion", "decillion" };
string Ones[] = { "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen" };
string Tens[] = { "zero", "ten", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety" };
string concat(bool cond1, string first, bool cond2, string second) { return (cond1 ? first : "") + (cond1 && cond2 ? " " : "") + (cond2 ? second : ""); }

string toStringBelowThousand(unsigned long long n) {
  return concat(n >= 100, Ones[n / 100] + " hundred", n % 100 != 0, (n % 100 < 20 ? Ones[n % 100] : Tens[(n % 100) / 10] + (n % 10 > 0 ? " " + Ones[n % 10] : "")));
}

string toString(unsigned long long n, int push = 0) {
  return n == 0 ? "zero" : concat(n >= 1000, toString(n / 1000, push + 1), n % 1000 != 0, concat(true, toStringBelowThousand(n % 1000), push > 0, Thousands[push]));
}

使用方法:

cout << toString(51351);   // => fifty one thousand three hundred fifty one

好的代码...但是为什么要用C++呢?这是代码高尔夫 - 使用你能找到的最好的"球杆"! - Jason Sundram
2
杰森·桑德拉姆,我不同意。用你自己的语言打高尔夫。嫉妒那些有不同障碍(也就是使用Lisp的任何人)。 - Jason Z

23

这算作弊吗?

perl -MNumber::Spell -e 'print spell_number(2);'

3
你需要计算你所使用的库中的行数。 - Marcin
3
哈哈,肯定是作弊了! =) 没有什么能够打败 Perl 在高尔夫比赛中的啦,呵呵呵。 - Chii
2
可能是:perl -MNumber::Spell -E"say spell_number 2 - jfs
1
我不知道为什么这个版本只有两票,而Lisp版本却有超过10票? - OscarRyz

18

保罗·菲舍尔和达里乌斯:你们有一些很棒的想法,但我不希望看到它们以如此冗长的方式实现。 :)开个玩笑,你们的解决方案真棒,但我压缩了更多的14 30字节,同时保持在79列内,并保持python 3兼容性。

所以这是我的416字节python代码,宽度限制在79列内:(感谢大家,我站在你们的肩膀上)

w=lambda n:_(n,["","thousand "]+p("m b tr quadr quint","illion"))[:-1]or"zero"
_=lambda n,S:n*"x"and _(n//M,S[1:])+(Z[n%M//C]+"hundred ")*(n%M//C>0)+(n%C>19
and p("twen thir fo"+R,"ty")[n%C//10-2]+Z[n%10]or Z[n%C])+S[0]*(n%M>0)
p=lambda a,b="":[i+b+" "for i in a.split()]
R="r fif six seven eigh nine"
M=1000
C=100
Z=[""]+p("one two three four five%st nine ten eleven twelve"%R[5:20])+p(
"thir fou"+R,"teen")

测试代码:

if __name__ == "__main__":
    import sys
    assert(w(0)=="zero")
    assert(w(100)=="one hundred")
    assert(w(1000000)=="one million")
    assert(w(1024)=="one thousand twenty four")
    assert(w(1048576)=="one million forty eight thousand five hundred seventy six")

很不错。 :) 我还没有阅读它,但是第二行末尾的 \ 不是可以去掉吗? - Darius Bacon
糟了!没错!又完成了一个字节,还有423个。谢谢。 - recursive
另外还有5个字节,我无法停止自己。我知道还有一些膨胀在那里... - recursive
1
如果你再挤一些字节,你就可以把它放在注释里;-) - Anders Hansson
你在开玩笑吗??我不知道 Python 代码可以看起来如此性感! - juliomalegria

17

看看recursive更好的答案,它更好。

Darius致以崇高的敬意,这个问题得到了他的启发。你的大写W(现在是我的p)特别聪明。

w=lambda n:["zero"," ".join(_(n,0))][n>0]
_=lambda n,l:_(n//M,l+1)+[E,Z[n%M//C]+["hundred"]][n%M//C>0]+\
(p("twen thir fo"+R,"ty")[n%C//10-2]+Z[n%10]if n%C>19 else Z[n%C])+\
[E,([E,["thousand"]]+p("m b tr quadr quint","illion"))[l]][n%M>0]if n else E
p=lambda a,b:[[i+b]for i in a.split()]
E=[];R="r fif six seven eigh nine";M=1000;C=100
Z=[E]+p("one two three four five six seven eight nine ten eleven twelve","")+\
p("thir fou"+R,"teen")

我用以下内容进行测试:

if __name__ == "__main__":
    import sys
    print w(int(sys.argv[1]))
    assert(w(100)=="one hundred")
    assert(w(1000000)=="one million")
    assert(w(1024)=="one thousand twenty four")
    assert(w(1048576)=="one million forty eight thousand five hundred seventy six")

目前,这是对 Darius 的当前解决方案的微调,而他的方案又是我早期方案的微调,而我早期方案则是受到他的启发,并在评论中给了一些错误提示。这也是对 Python 的犯罪。

以下内容有剧透,rot13 用于保护您的信息,因为其中一半的趣味是找出如何实现高尔夫球。强烈推荐使用 Firefox 扩展程序 mnenhy 对此进行解码(以及其他简单编码方案)。

Pbafgnagf (V eranzrq gurz guvf erivfvba gb ubcrshyyl znxr gurz pyrnere.)

  • R: The emperor set.
  • E: That which is in common between words starting with "e" (egrra, svsgrra, fvkgrra...) and words ending with egl, svsgl, fvkgl....
  • Z, P: What they are in Roman numerals.
  • M: All the numbers from one to ten.

Shapgvbaf (fbzr nyfb eranzrq guvf ebhaq)

  • j: The pivotal-cipher encryption, which maps a number to a word.
  • _: Perversely maps the number to words, letter-by-letter. a is the number, y is how many jumps through the alphabet we are. Returns a list of lists of each word in the number, e.g. [['one'],['two'],['three'],['four']].
  • c: for each word in the space-separated word list n, generates o as a rotation and adds each result to a list-of-lists. For example, c("z o ge","vyyvba") == [['zvyyvba'],['ovyyvba'],['gevyyvba']].

很酷。 :) 乍一看我完全不理解这个,但是你可以通过删除空格(第1行、第3行和最后一行)以及第1行的“!=0”来节省7个字节。 - Darius Bacon
错误:g(19) 是 'nine'。在 s 的定义中将 "n<19" 更改为 "n<20"。 - Darius Bacon
太棒了!而且我已经太累了,无法再搞明白了——但我确实看到前三行中有4个字节可以挤出来:g=lambda n:["零"," ".join(w(n,0))][n>0] w=lambda n,l:e if n<1 else
w(n//m,l+1)+(z[n%m//d]+["百"]if n%m//d else[])+s(n%m%d)+([],k[l])[n%m>0]
- Darius Bacon
十的21次方在2的64次方中是不必要的;去掉它可以将字节数减少到480个,我正在进行压缩。这种做法有点无意义,但还是很有趣的。 :) - Darius Bacon
我在睡前回来看了一下这个(这个代码高尔夫应该带有警告),但发现它令人惊讶地易读。随着理解的加深,我找到了一个将其压缩至473字节的方法:w=lambda n,l:e if n<1 else
w(n//m,l+1)+[e,z[n%m//d]+["hundred"]][n%m//d>0]+s(n%d)+(e,k[l])[n%m>0]
- Darius Bacon
我通过修改代码,压缩了14字节。请查看我的回答。 - recursive

10

Python,446字节。该死的,所有行都在80个字符以下。这是Paul Fisher的解决方案,几乎每一行都进行了编码调整,从他的488字节版本中缩小;他之后又压缩了几个字节,我认输了。去投票支持他的答案吧!

g=lambda n:["zero"," ".join(w(n,0))][n>0]
w=lambda n,l:w(n//m,l+1)+[e,z[n%m//100]+["hundred"]][n%m//100>0]+\
(p("twen thir fo"+r,"ty")[n%100//10-2]+z[n%10]if n%100>19 else z[n%100])+\
[e,k[l]][n%m>0]if n else e
p=lambda a,b:[[i+b]for i in a.split()]
e=[];r="r fif six seven eigh nine";m=1000
k=[e,["thousand"]]+p("m b tr quadr quint","illion")
z=[e]+p("one two three four five six seven eight nine ten eleven twelve","")+\
p("thir fou"+r,"teen")

历史变得复杂了。我从下面的未混淆代码开始,它支持负数和范围检查,还允许在某些数字中使用破折号以更好地表达英语:

>>> n2w(2**20)
'one million forty-eight thousand five hundred seventy-six'

def n2w(n):
    if n < 0:  return 'minus ' + n2w(-n)
    if n < 10: return W('zero one two three four five six seven eight nine')[n]
    if n < 20: return W('ten eleven twelve',
                        'thir four fif six seven eigh nine',
                        'teen')[n-10]
    if n < 100: 
        tens = W('', 'twen thir for fif six seven eigh nine', 'ty')[n//10-2]
        return abut(tens, '-', n2w(n % 10))
    if n < 1000:
        return combine(n, 100, 'hundred')
    for i, word in enumerate(W('thousand', 'm b tr quadr quint', 'illion')):
        if n < 10**(3*(i+2)):
            return combine(n, 10**(3*(i+1)), word)
    assert False

def W(b, s='', suff=''): return b.split() + [s1 + suff for s1 in s.split()]
def combine(n, m, term): return abut(n2w(n // m) + ' ' + term, ' ', n2w(n % m))
def abut(w10, sep, w1):  return w10 if w1 == 'zero' else w10 + sep + w1

然后,我通过混淆将其压缩到大约540字节(对我来说是新的),而Paul Fisher找到了一种更短的算法(去掉连字符),以及一些令人惊叹的可怕Python编码技巧。 我偷了这些编码技巧,将代码压缩到508字节(仍然没有获胜)。 我尝试用新算法重新开始,但无法打败Fisher的算法。 最后,这是他代码的微调。 至此致敬!

经过检查眼球的一堆情况,已经测试了混淆代码与干净代码。


将数字100替换为常量,代码会变得更简短,头疼也会变得更大。 - Paul Fisher
唉!我真是太傻了,在重新尝试并且没有发布之前,我做了同样的事情。你赢了! - Darius Bacon

6

好的,这里是关于F#的内容,为了保持可读性,大约830个字节:

#light
let thou=[|"";"thousand";"million";"billion";"trillion";"quadrillion";"quintillion"|]
let ones=[|"";"one";"two";"three";"four";"five";"six";"seven";"eight";"nine";"ten";"eleven";
  "twelve";"thirteen";"fourteen";"fifteen";"sixteen";"seventeen";"eighteen";"nineteen"|]
let tens=[|"";"";"twenty";"thirty";"forty";"fifty";"sixty";"seventy";"eighty";"ninety"|]
let (^-) x y = if y="" then x else x^"-"^y
let (^+) x y = if y="" then x else x^" "^y
let (^?) x y = if x="" then x else x^+y
let (+^+) x y = if x="" then y else x^+y
let Tiny n = if n < 20 then ones.[n] else tens.[n/10] ^- ones.[n%10]
let Small n = (ones.[n/100] ^? "hundred") +^+ Tiny(n%100)
let rec Big n t = if n = 0UL then "" else
  (Big (n/1000UL) (t+1)) +^+ (Small(n%1000UL|>int) ^? thou.[t])
let Convert n = if n = 0UL then "zero" else Big n 0

以下是单元测试

let Show n = 
    printfn "%20u -> \"%s\"" n (Convert n)

let tinyTests = [0; 1; 10; 11; 19; 20; 21; 30; 99] |> List.map uint64
let smallTests = tinyTests @ (tinyTests |> List.map (fun n -> n + 200UL))
let MakeTests t1 t2 = 
    List.map (fun n -> n * (pown 1000UL t1)) smallTests
    |> List.map_concat (fun n -> List.map (fun x -> x * (pown 1000UL t2) + n) smallTests)
for n in smallTests do
    Show n
for n in MakeTests 1 0 do
    Show n
for n in MakeTests 5 2 do
    Show n            
Show 1000001000678000001UL
Show 17999999999999999999UL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接