更新OpenCC.NET
这两天有点空,把OpenCC.NET重构和优化了一下,版本号也正式来到了1.0。本来是想加入异步API,结果测试发现可能因为本来转换就是查字典速度很快,加上异步的话还需要Task
的创建开销,导致性能还不如同步版本。然后尝试把原来用StringBuilder
改成栈上的Span<char>
,结果性能也没啥区别。不过原来的内部转换的实现复用性不强而且乱,还是决定重写成OpenCC原本那种链式处理。简单来说就是分词完后得到IEnumerable<string>
格式的词组,给IEnumerable<string>
写一个扩展方法:
private static IEnumerable<string> ConvertBy(this IEnumerable<string> phrases,
params IDictionary<string, string>[] dictionaries)
{
return phrases.Select(phrase =>
{
... // 根据字典进行转换
}
}
一批词组先用一些字典进行转换,得到中间结果,然后可以再用其他字典继续转换,直到得到结果。这样就可以等效出原OpenCC中的转换链。比如原OpenCC中的s2twp.json
(简=>繁台+词汇):
{
"name": "Simplified Chinese to Traditional Chinese (Taiwan standard, with phrases)",
"segmentation": {
"type": "mmseg",
"dict": {
"type": "ocd2",
"file": "STPhrases.ocd2"
}
},
"conversion_chain": [{
"dict": {
"type": "group",
"dicts": [{
"type": "ocd2",
"file": "STPhrases.ocd2"
}, {
"type": "ocd2",
"file": "STCharacters.ocd2"
}]
}
}, {
"dict": {
"type": "ocd2",
"file": "TWPhrases.ocd2"
}
}, {
"dict": {
"type": "ocd2",
"file": "TWVariants.ocd2"
}
}]
}
根据conversion_chain
部分,利用ConvertBy()
可以等效出:
public static string HansToTWWithPhrase(string text)
{
// 分词
var phrases = ZhSegment.Segment(text);
// 链式转换
return phrases.ConvertBy(ZhDictionary.STPhrases, ZhDictionary.STCharacters)
.ConvertBy(ZhDictionary.TWPhrases)
.ConvertBy(ZhDictionary.TWVariants)
// Join()是我写的扩展方法,其实就是调用string.Join()把词组重新合并成句子
.Join()
}
于是我就把原来的API全部重写成这种转换链的形式了,另外还加了日语汉字新旧字体转换的API(因为原OpenCC带了相应的字典,干脆也就实现了),虽然感觉也就图一乐用。
顺带做了一下性能测试。程序的第一次转换,就算是转换空文本都要差不多花费500ms的时间,后续就不用了,可能是因为要加载Jieba分词的模型。然后测试了一本15.5M大小的网络小说,共14万行,同步的话大概要总共花费9s;改用异步方式,每行都Task.Run()
调用转换方法然后await
,可以缩短到4.5s左右。
另外还尝试了一下GitHub上的Action,用了别人写好的脚本,可以自动把最新的工程构建和发布到Nuget上,很是方便。
Nuget打包的一个坑
先说结论,我在Visual Studio 2022 17.1.0上设置C#项目自动生成Nuget包时,如果在ItemGroup
的Content
上配置了PackageCopyToOutput
属性,且同时又配置了PackagePath
属性,那么PackageCopyToOutput
会被无效。
OpenCC.NET是我第一次打Nuget包,所以啥也不懂。其实打包都是自动化的,只要在工程配置文件里设置一下库的信息比如名字版本号啥的就好。但问题是这个库需要用到OpenCC字典和Jieba.NET的资源文件,所以按理说应该也要打进Nuget包里,然后其他程序如果引用了这个包,编译的时候会自动把这些文件复制到输出目录。
一开始我实在是不知道咋操作,所以只能在readme上注明需要手动下载这两个文件夹放到程序目录。
一开始我的配置类似:
<ItemGroup>
<Content Include="Dictionary\*.txt">
<Pack>True</Pack>
<PackagePath>content\Dictionary\</PackagePath>
</Content>
<Content Include="JiebaResource\*.*">
<Pack>True</Pack>
<PackagePath>content\JiebaResource\</PackagePath>
</Content>
</ItemGroup>
文件确实是打进包里了,可是还是无法输出。困扰了我很久,我后面试过什么CopyToOutputDirectory
属性之类的,都无效,只能放弃,然后在readme上写需要手工将两个文件夹下载放入程序目录下。
这次我又重新经过一番搜索,发现是我想要的这个功能原来是靠PackageCopyToOutput
属性实现的,于是我尝试:
<ItemGroup>
<Content Include="Dictionary\*.txt">
<Pack>True</Pack>
<PackageCopyToOutput>True</PackageCopyToOutput>
</Content>
<Content Include="JiebaResource\*.*">
<Pack>True</Pack>
<PackageCopyToOutput>True</PackageCopyToOutput>
</Content>
</ItemGroup>
发现终于能输出了!但是生成的.nupkg却大了一倍,打开包结构一看,文件夹给我放进去一模一样的两份。
一顿搜索之后发现官方文档上确实是介绍了这一点
默认情况下,所有内容都添加到包中content
和contentFiles\any\<target_framework>
文件夹根目录,并保留相对文件夹结构,除非指定包路径
但这样不是白白浪费了空间,于是我就在上面的基础上,像最开始一样添加PackagePath
属性:
<ItemGroup>
<Content Include="Dictionary\*.txt">
<Pack>True</Pack>
<PackageCopyToOutput>True</PackageCopyToOutput>
<PackagePath>content\Dictionary\</PackagePath>
</Content>
<Content Include="JiebaResource\*.*">
<Pack>True</Pack>
<PackageCopyToOutput>True</PackageCopyToOutput>
<PackagePath>content\JiebaResource\</PackagePath>
</Content>
</ItemGroup>
这次确实只打包一份资源文件了。
但搞笑的是又不能自动把两个文件夹复制到程序输出目录了。所以最后我还是只能选择去掉指定路径,让它自动给我打包两份资源文件上去。虽然大小成了两倍,但至少能用不是嘛。只能说属实是有点坑了。