与英语不同,当两个单词组合在一起时,日语的声音会发生变化。详细信息在问题末尾给出。
该程序处理范围\ $-2 ^ {63}中的整数\ $ n \ $ \ le n <2 ^ {63} \ $ 。
代码
/**
* Integer to Japanese reading
*
* Japanese reading is written in Hepburn Roomaji. Traditional
* Hepburn is used to avoid the need of macrons.
*/
#include <cassert>
#include <cstdint>
#include <iostream>
#include <string>
#include <vector>
using number_t = std::int_fast64_t;
const std::vector<std::string> magnitudes = {
"", "man", "oku", "choo", "kee",
};
const std::vector<std::string> thousands = {
"", "sen", "nisen", "sanzen", "yonsen",
"gosen", "rokusen", "nanasen", "hassen", "kyuusen",
};
const std::vector<std::string> hundreds = {
"", "hyaku", "nihyaku", "sanbyaku", "yonhyaku",
"gohyaku", "roppyaku", "nanahyaku", "happyaku", "kyuuhyaku",
};
const std::vector<std::string> tens = {
"", "juu", "nijuu", "sanjuu", "yonjuu",
"gojuu", "rokujuu", "nanajuu", "hachijuu", "kyuujuu",
};
const std::vector<std::string> ones = {
"", "ichi", "ni", "san", "yon",
"go", "roku", "nana", "hachi", "kyuu",
};
// returns 10000^n
constexpr number_t magnitude(std::size_t n)
{
number_t result = 1;
while (n--)
result *= 10000;
return result;
}
constexpr bool is_vowel(char c)
{
switch (c) {
case 'a': case 'e': case 'i': case 'o': case 'u': case 'y':
return true;
default:
return false;
}
}
// joins two strings according to Japanese rules
void push(std::string& lhs, const std::string& rhs)
{
if (lhs.back() == 'n' && is_vowel(rhs.front()))
lhs += '\'';
lhs += rhs;
}
// converts nonnegative numbers less than 10000
std::string group_name(number_t number)
{
assert(0 <= number && number < 10000);
std::string result = thousands[number / 1000];
number %= 1000;
push(result, hundreds[number / 100]);
number %= 100;
push(result, tens[number / 10]);
number %= 10;
push(result, ones[number]);
return result;
}
std::string to_Japanese(number_t number)
{
if (number == 0)
return "zero";
std::string result;
if (number < 0) {
result = "mainasu";
number = -number;
}
number_t mag = magnitude(magnitudes.size() - 1);
for (std::size_t i = magnitudes.size(); i-- > 0; mag /= 10000) {
if (auto group = number / mag; group > 0) {
push(result, group_name(group));
push(result, magnitudes[i]);
}
number %= mag;
}
return result;
}
int main()
{
for (number_t number; std::cin >> number;)
std::cout << to_Japanese(number) << "\n";
}
示例会话
0
zero
1
ichi
2
ni
3
san
6789678967896789
rokusennanahyakuhachijuukyuuchoorokusennanahyakuhachijuukyuuokurokusennanahyakuhachijuukyuumanrokusennanahyakuhachijuukyuu
-1234567898765432
mainasusennihyakusanjuuyonchoogosenroppyakunanajuuhachiokukyuusenhappyakunanajuurokumangosen'yonhyakusanjuuni
日语数字
(如果您熟悉日语,则可以跳过此部分。)
在下表中,红色条目涉及声音变化。
\ begin {array} {ll}
\ text {Number}和\ text {日语阅读} \\
1&\ text {一(ichi)} \\
2&\ text {二(ni)} \\
3&\ text {三(san)} \\
4&\ text {四(yon )} \\
5&\ text {五(go)} \\
6&\ text {六(roku)} \\
7&\ text {七(nana)} \\
8&\ text {八(hachi)} \\
9&\ text {九(kyuu)} \\
10&\ text {十(juu)} \\
20&\ text {二十(nijuu)} \\
30&\ text {三十(sanjuu)} \\
40&\ text {四十(yonjuu)} \ \
50&\ text {五十(gojuu)} \\
60&\ text {六十(rokujuu)} \\
70&\ text {七十(nanajuu)} \\
80&\ text {八十(hachijuu)} \\
90&\ text {九十(kyuujuu)} \\
100&\ text {百(hyaku)} \\
200&\文本{二百(nihyaku)} \\
300和\ text {三百} \ color {red} {\ text {(sanbyaku)}} \\
400&\ text {四百( yonhyaku)} \\
500&\ text {五百(gohyaku)} \\
600&\ text {六百} \ color {red} {\ text {(roppyaku)}}} \\
700&\ text {七百(nanahyaku)} \\
800&\ text {八百} \ color {red} {\ text {(happyaku)}} \\
900 &\ text {九百(kyuuhyaku)} \\
1000&\ text {千(sen)} \\
2000&\ text {二千(nisen)} \\
3000&\ text {三千} \ color {red} {\ text {(sanzen)}} \\
4000&\ text {四千(yonsen)} \\
5000&\ text {五千(gosen)} \\
6000&\ text {六千(rokusen)} \\
8000&\ text {八千} \ color {red} {\ text {(hassen)}}} \\
9000&\ text {九千(kyuusen)} \\
\ end {array}
大数字被认为是小数字的总和。例如:
\ begin {array} {ccccccc}
2019&=&2000&+&10&+&9 \\
\ text {二千十九( nisenjuukyuu)}&&
\ text {二千(nisen)}&&
\ text {十(juu)}&&
\ text {九(kyuu)}
\ end {array}
丢失的一百位将被忽略。
四位数字被认为是一个组,与英语不同,三位数字是一个组。组标记为:
\ begin {array} {ccc}
10 ^ 4&10 ^ 8&10 ^ {12}&10 ^ {16} \\
\ text {万(man)}&\ text {亿(oku)}&\ text {兆(choo)}&\ text {京(kee)}
\ end {array}
例如,\ $ 1,,2345 \,6789 \ $被读为一亿二千三百四十五十五六六七七八十九(ichioku nisensanbyakuyonjuugoman rokusennanahyakuhachijuukyuu)。 (空格仅是为了便于识别。)请注意,与十(juu),百(hyaku)和千(sen)不同,在这些组标记之前需要一个(ichi)。
0是ゼロ(零)。
负整数\ $-n \ $读为マイナス(mainasu),后跟绝对值\ $ n \ $。例如,-5读为マイナス五(mainasugo),因为5读为五(go)。以“ aeiouy”之一开头,然后在它们之间添加分隔符'。例如,1001是千一(sen'ichi),而1004是千四(sen'yon)。
#1 楼
push
函数可能会调用未定义的行为。当您创建新的std::string
时,我认为不能保证它会以null结尾,因为这会导致运行时成本,而C兼容性除外。仅在调用c_str()
之后才存在终止空字符。因为字符串实际上是空的,所以调用back()
会访问一个越界元素。您声称您的代码适用于\ $-2 ^ {63} \ $,但没有添加数字作为测试用例。我希望它不会起作用,因为
-(-2**63)
仍然是负数。除了这两个问题,您的代码读起来也很容易理解。对于不懂日语的每个人来说,添加额外的文档是一个好主意。
#2 楼
您的数量级应为chou
和kei
(顺便说一句,在超级计算机中)。而且,to_Japanese()
最好命名为to_romaji()
。作为练习,您也可以尝试to_Japanese(number, KANJI|HIRAGANA|ROMAJI)
。此外,它是
icchou
而不是ichichou
和ikkei
而不是ichikei
。评论
\ $ \ begingroup \ $
感谢您纠正我的日语!我是一个初学者,为错误感到抱歉。关于chou vs choo:据我了解,当おう是长的“ o”时,它被写为“ oo”而不是“ ou”。我发现不同的罗马字系统对此有所不同。而且“ ichichou”是如此愚蠢;)
\ $ \ endgroup \ $
– L. F.
19年7月29日在0:50
\ $ \ begingroup \ $
是的,有很多不同的罗马书版本-我查找了最常见的赫本,并且choo是正确的,尽管我始终将chou用作IME友好型,但是sennana的类型是sennnana。我们现在正在向日语领域进发...
\ $ \ endgroup \ $
– Ken Y-N
19年7月29日在5:59
#3 楼
我认为最好生成数字的汉字版本,因为它通常更有用。创建一个单独的类来负责罗马化(或一般来说是语音转换,以便它可以支持假名)。使用汉字还可以使琐碎的对形式数字(大字)的支持增加一些,这很酷/有用。
我还建议添加对分解romaji序列的支持,因为在romaji中读取非常大的数字而没有空格并不是一件好事。它可以是一个可选标志。添加空间的自然位置至少是在幅度之间,或者可能是在您已经组合在一起的所有各个组(一个,数十个,数百个等)之间。
使用romaji为了避免编码问题,我建议您忍耐一下,学习如何正确支持Unicode,因为这将非常有用。
评论
\ $ \ begingroup \ $
有见地。尽管因为一个汉字可以具有多个读数,所以罗马化并不是很平常的AFAIK。在罗马字之间使用空格也是一个好主意。
\ $ \ endgroup \ $
– L. F.
19年7月29日在3:25
\ $ \ begingroup \ $
@ L.F。罗马化不需要支持全部日语,因为此代码以数字为中心,并且您已经决定了所涉及的每种汉字的读数(或者,在这种情况下,只有一个正确的答案) ;这意味着片假名和平假名的语音阅读在那时就很容易。
\ $ \ endgroup \ $
–briantist
19年7月29日在3:30
#4 楼
我用英语知道,在程序打印所有合并在一起的数字的地方,每个单词都会分开。这是日语的实际功能吗?由于该代码在很多地方都有用,所以翻译代码像在类中一样好。
垂直空间的使用
通常,当一行上只有一个值时,代码更易于阅读和维护。这将适用于向量的初始化以及函数
is_vowel()
中的switch语句。出于维护原因,将行插入所需位置比将值添加到逗号分隔列表要容易得多。is_vowel函数
如果元音位于std :: map中,而不是switch语句中。这是有关堆栈溢出和软件工程的讨论。
答案的这一部分已被修改,以消除使用std :: map可能会改善性能的声明。如果map使用一个简单的索引到数组中可能是正确的,但是它不是一个简单的数组索引。AssertAssert语句通常用于调试目的并终止程序。无需调试也可以在编译代码时删除断言语句。我不希望在生产级代码中看到断言,因为这意味着该代码尚未调试。
评论
\ $ \ begingroup \ $
“如果元音使用std :: map而不是switch语句,则代码将更少,并且性能可能会更好。” -您是否有任何证据支持std :: map比原始switch语句更快的说法?因为我对此非常怀疑。
\ $ \ endgroup \ $
–TomášZato-恢复莫妮卡
19年7月29日在9:26
\ $ \ begingroup \ $
^与Tomas达成协议。提供证明。 std :: map是动态分配的,基于节点,并且绝对不缓存友好
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
19年7月29日在10:41
\ $ \ begingroup \ $
“ Assert语句通常用于调试目的并引发异常。” -不存在断言语句以测试前提条件。异常与前提条件无关。这个答案是有害的。
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
19年7月29日在10:42
\ $ \ begingroup \ $
失败的assert()不会抛出-它们会终止程序。
\ $ \ endgroup \ $
– Toby Speight
19年7月29日在11:14
\ $ \ begingroup \ $
“通常最好使用提供错误消息的if语句而不是assert语句。” -这仍然是严重错误和有害的。他们有完全不同的用例。断言->前提条件检查,合同破裂,不可恢复的错误。 if语句+错误->可恢复的错误,通常可以由人来处理。
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
19年7月29日在13:15
#5 楼
将std::vector<std::string>
用于字符串常量数组很浪费。 std::vector
和std::string
都是可以潜在分配的动态类型。更轻量的选择是constexpr std::array<const char*>
或constexpr std::array<std::string_view>
。评论
\ $ \ begingroup \ $
公平点。由于SSO,字符串在这种情况下可能不是动态的,但是向量是非常浪费的。
\ $ \ endgroup \ $
– L. F.
19年7月29日在10:42
\ $ \ begingroup \ $
尽管如此,我还是喜欢尽可能使用最少的工具来完成这项工作。即使常量适合SSO,也最好使用std :: string_view或const char *。编译器也很有可能能够进行更好的优化。
\ $ \ endgroup \ $
–罗密欧(Vittorio Romeo)
19年7月29日在10:44
评论
\ $ \ begingroup \ $
我明白了。问题在于,反向访问不存在的元素。 (终止的空字符不会对此进行更改。)
\ $ \ endgroup \ $
– L. F.
19年7月29日在5:22
\ $ \ begingroup \ $
@ L.F。是的,实际上大多数std :: string实现始终都有一个终止的0字节。实际上,C ++ 11和更高版本要求pos = size()的operator []引用终止符0。使back()UB产生的原因是en.cppreference.com/w/cpp/string/basic_string/back表示它是.empty()== true时的UB。此外,它等效于operator [](size()-1),该操作符将包装未签名的位置。
\ $ \ endgroup \ $
– Peter Cordes
19年7月29日在6:28
\ $ \ begingroup \ $
代码肯定需要检查。另外,operator [](size()-1)仅在empty()为false的情况下适用,因此我想换行是无关紧要的。
\ $ \ endgroup \ $
– L. F.
19年7月29日在6:31
\ $ \ begingroup \ $
实际上,在空std :: string上执行.back()时,带有libstdc ++的g ++ -O0(在空std :: string上执行.back()时)为0,因此它无法帮助您通过崩溃该UB来检测此错误。
\ $ \ endgroup \ $
– Peter Cordes
19年7月29日在6:31
\ $ \ begingroup \ $
@ L.F .:有趣的事实:C ++ 11中COW std :: string实现的合法性表明,C ++ 11中的某些新要求基本上使std :: string的有效COW实现根本不可能。由于.data()也需要以0结尾的字符串,因此显而易见的/预期的实现是始终保持该状态,因此.data()和.c_str()可以是一个无操作符,仅返回一个类成员vars。
\ $ \ endgroup \ $
– Peter Cordes
19年7月29日在6:36