terasum / js-mdict

*.mdx/*.mdd interpreter js implements, support mdict index file

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

关于 `keyCaseSensitive` 和 `stripKey` 的预期行为

songxiaocheng opened this issue · comments

keyCaseSensitive 为例,当同时存在仅大小写不同的单词时,期望的行为是如何?

比如测试词典中的"dict-01-袖珍葡汉汉葡词典(简体版).mdx"设置的是 KeyCaseSensitive: 'No', 词典中同时存在 holanda 和 Holanda,当查询其中一个词条时,有以下几种方式:

  1. 强行大小写敏感,这是 #37 之前事实上的效果
  2. 对大小写不敏感的词典,先搜到哪个是就用哪个,这是 #37 之后的效果,造成 test 中上述测试失败
  3. 两个均都同时返回(考虑以数组或拼合的方式)
  4. 若存在大小写相同的精确匹配,则返回该条目,否则返回大小写不敏感的匹配

其实我认为该测试词典的制作是有不妥的,因为既然设置了大小写不敏感,就不应同时提供两个仅大小写不同的词条,应该将二者放在同一个词条里,或者设置大小写敏感。

第1种方式我认为应当首先排除,因为其无视了词典的设置。该库调用者若有需要可以强行覆盖设置的。

不过,若需要兼容部分词典已经发生这种现象(大小写不敏感但是大小写分不同词条)的既定事实,则需考虑其它方式。

第2种方式我认为也应当排除,因为造成使用上的不便(我们不希望搜索“Holanda”时,词典中明明存在“Holanda”,却只返回“holanda”,造成词典中的“Holanda”无法被查询到)。

方式3的问题是,若以拼合字符串返回,则相当于事实上调整了词条内容,作为 mdict reader 这样似乎也不妥;若以数组返回,则需要改动接口。

方式4的话感觉最佳?

方式3和方式4实现起来很可能都需要二次查询。

大小写的问题我们在 #28 中进行了比较详细的讨论,这个词典也是 @danjame 提供的。

针对上述建议,我的理解是这样的:

  1. 库的调用者可以强行设置大小写敏感选项,我觉得是有必要提供的,因为有些词典即使在 Header中设置了大小写敏感,其内部的实际排序也是不正确的,需要库的调用者手动判断。
  2. 这种搜到哪个返回哪个肯定不是我们想要的效果
  3. 两个都同时返回应该也不是我们想要的效果,我们应该理解为 在大小写敏感的时候 Holandaholanda 是两个词,搜索的时候也应该返回两个意思,(我猜测在葡萄牙语里面这两个词是不同的意思)
  4. 这种方式其实需要搜索两次,@danjame 实现过一遍,当时我觉得不是特别好,因为这种方式其实是遍历了一遍所有词

我原来的解决思路是通过使用不同的 CompareFn 来实现快速查找
大小写敏感词的差异主要是这样的:

KeyCaseSensitiveYesNo 实际并不影响词典中是否包含大写字母词,也就是说,即使 KeyCaseSensitive:No 词典中也会有 Holanda 这种词的,而且这种词典是通过MdxBuilder制作出来的,我相信这种词典应该数量不少。

KeyCaseSensitiveYesNo 在词典层面的差异,我通过遍历所有词发现的:

如果KeyCaseSensitive=Yes,那么词典中词的顺序是

Abc
Beer
Holanda
abc
beer
holanda

如果KeyCaseSensitive=No,那么词典中词的顺序是

Abc
abc
Beer
beer
Holanda
holanda

多谢。我问这个是因为之前我这边 debug 报 assert 失败(我上面错写为 test),不知道为什么,现在又没问题了,很奇怪,可能与脏目录有关吧。

@songxiaocheng 刚刚我 master 推了一个修复了一些问题的分支,KeyCaseSensitive 目前工作没有问题

@terasum 我知道怎么回事了,你后来的提交把 keyCaseSensitive 又改回去成原来的恒为 true 的情况了,于是上面那个问题才莫名消失。
dd0356c#r52555947

我认为这个问题有必要好好解决, keyCaseSensitive 恒为 true 并不能真正解决问题,只是掩盖了问题。

以上面举的 'Holanda' 为例,由于KeyCaseSensitive:No,字典中是这样排列的: ..., 'holanda', 'Holanda', ...

当查询 'Holanda' 时, 由于大小写不敏感,因此采用了大小写不敏感的 CompareFn 进行比较,当比较到 'holanda' 时,被 CompareFn 判断为相等,于是返回该条目。

现在的实现是两级二分查找,先用二分查找找到 block (通过 _reduceWordKeyBlock 函数),然后再用二分查找找到 record (通过 _binarySearh 函数)。这两级二分查找都是搜索到值就返回。某些情况下,大小写不同的单词甚至有可能出现在不同的 block。

我认为如果要按照我最顶上说的方式4实现,有两种做法。

第一种做法,用不同的 CompareFn 分别搜两次,先精确匹配搜,搜不到的话,再忽略大小写地搜。每次查找都要重做上述的两级二分查找。

第二种做法,在大小写不敏感的情况下,“仅大小写不同”的单词被认为是等值的,那么就意味着单词列表中有2个(或更多)等值的单词(当然这些等值的单词是连续的),那么可以把两级二分查找都改成返回一个区间,然后再从中选择一个返回。

我觉得第一种做法更好,第二种做法要魔改二分查找,可能遇到奇奇怪怪的、意料之外的问题,

最后,还有一个要注意的是,在极端情况下,“仅大小写不同”的单词可能有多条,比如3条,如果没有精确匹配的,返回哪个呢?任一个都可以吗?当然这种情况实际中可能极少,可以暂不特殊处理,找到哪个是哪个,只要保证精确匹配优先即可。

我仔细研究了一下你的观点,我觉得有几个前提还需要明确一下:

  1. KeyCaseSensitive:Yes 的情况下,用户查询 Holanda 是想得到 Holanda 这个没有争议,但是在 KeyCaseSensitive:No 的情况下,用户查询 Holanda 我理解还是想得到 Holanda 的,观点类似于你前面说的"词典制作有问题"的论述,目前已经是既定事实,不去讨论词典制作工具的问题。

  2. KeyCaseSensitive:No 的情况下,依旧同时存在 holandaHolanda 两个仅大小写字母不同的相同词条的情况下, 用户依旧希望得到 Holanda 的精确匹配,那么存在两种选择
    1)调用方强行传入 searchOption 来设定 KeyCaseSensitive = true, 若不设定,则默认为 true, 可以满足大多数需求, 这种情况下,用户搜索 Holanda 需得到精确匹配才会有结果,否则将搜索不到结果
    2) 不允许库调用方传入选项参数,有大写精确匹配则返回大写精确匹配结果,否则返回小写结果,我认为这种方式可能会误导用户,我认为这不是用户的原本意图,如果用户想查询小写结果,可以直接输入小写查询,用户特意输入大写查询是有明确用意的(因为输入大写字母更麻烦)

  3. 是否存在 holanda 存在前一个 keyBlock中,Holanda 存在在后一个 keyBlock 中的情况,我理解这种情况是不会出现的,否则在 KeyCaseSensitive:No 的情况下,一个词无法确定是在前一个 keyBlock 还是后一个keyBlock,例如

keyBlock 323: firstKey: hallo lastKey :holanda
keyBlock 324: firstKey: Holanda lastKey :hzzz

在这种情况下,即使我们精确匹配到了某个词,还需要往后搜索至少一个 keyBlock ,我认为词典格式的设计者不会考虑这种设计,实在是太麻烦了

再考虑这种情况:

Abc
Beer
Holanda
abc
beer
holanda

这种情况则更加麻烦,在匹配不到Beer之后,还需要将 Beer 转为小写 beer 进行匹配,我认为这种做法也欠妥当,在英语中可能这么转换是可以的,但是在小语种中我们无法确定其大写字母转小写字母的规则,我认为没有必要匹配两次

综上,我认为用户的意图永远都是 KeyCaseSensitive=true 的情况,站在用户角度,没有必要多非力气输入一个大写字母的,输入大写字母查询必然是想要得到大写字母词的精确结果,没有就返回无即可,这也是我改成 KeyCaseSensitive 恒 true 的原因,基于此,上面提到的几个问题:

最后,还有一个要注意的是,在极端情况下,“仅大小写不同”的单词可能有多条,比如3条,如果没有精确匹配的,返回哪个呢?任一个都可以吗?当然这种情况实际中可能极少,可以暂不特殊处理,找到哪个是哪个,只要保证精确匹配优先即可。
也自然不存在了。

@terasum 第一条我们认知是一致的。
第二条,我最新的实现允许调用者强行设置,不冲突,一会我会提个PR。
第三条,我不知道keyblock的划分是制作者设计的,还是自动生成的。

至于小语种情况,我们是用 toLowerCase 来设置的,寄希望于js能够处理,即使不能,也可以在 toLowerCase 这里扩充多语种支持。

最后,对于MDD文件来说,大小写敏感性设置非常关键,需要尊重词典设计,如果不按词典自身的设置来,资源文件很容易找不到的。

即使对于MDX来说,用户可不光是打字输入,也可能是复制粘贴,强行大小写敏感并非长久之策。

mdd 文件我今天也进行了几个用例的测试,目前采用了一个 localCompare 的函数姑且可以解决,说到 mdd 我还是坚持,如果用户查询 Holanda 就返回 Holanda 的结果,而不是返回 holanda,因为 mdd 的话是要求完全精确匹配的,我觉得mdd 和 mdx 目前如果想要统一匹配比较函数的话,还是只允许大小写敏感精确匹配比较好

image

类似这种情况,mdd 词典中的 KeyCaseSensitiveno 的,但是我理解在寻找资源的时候还是需要精确匹配的,匹配不到不应该返回小写结果的

我手头就有这种词典,他们definition里面里面是大写名字的css,但是mdd里面是小写名字,匹配失败。没有了资源文件,看到的是一团混乱。这个词典,几乎所有支持 mdict 格式的词典产品(如欧路等)都可以正常解析,但我们强行 KeyCaseSensitive 为 true 的情况下,就无法找到资源。

最关键的是,我是不能在我项目里直接 toLowerCase 的,因为不能保证 mdd 里面都是小写(即使是 case insensitive 的情况),mdd 里面的 key 和 definition里面的链接大小写都可能有。制作者可能出于某些原因刻意为之,也可能是因为疏忽,我也见过词典 mdd 里面打包了 .DS_Store 文件的。

  1. 不允许库调用方传入选项参数,有大写精确匹配则返回大写精确匹配结果,否则返回小写结果,我认为这种方式可能会误导用户,我认为这不是用户的原本意图,如果用户想查询小写结果,可以直接输入小写查询,用户特意输入大写查询是有明确用意的(因为输入大写字母更麻烦)

我认为本库作为底层 reader,不应揣摩用户意图,那是应用层的事。本库应该致力于正确呈现词典制作者想要呈现的词典的内容。制作词典的人设置的 KeyCaseSensitive 正是他用来调试自己词典的设置。作为一个 mdict reader, 只有严格按照词典设置去工作,才能尽可能和词典制作者调试的时候看到的一致。他调试的时候没问题,我们就大概率不会出问题。

有些词典有词条之间的关联 @@@LINK=,可在应用层解析以生成连接。由于这些都是脚本生成的,大小写都有可能的。举个例子 @@@LINK=Earth 但是只有词条 earth(词典制作者心想,我明明设置了 KeyCaseSensitive 为 No,因此这不是 bug 是 feature)。再或者反过来,词典只包括 England 这个 key,用户查询 england,仍然查不到任何东西。就算要强行,也只能默认 case insensitive,可是这仍然不 robust,很多时候我们又需要精确匹配。

综上,我认为最好的做法就是默认遵守词典的设置。如果本库调用方有自己的想法,给他们设置的接口就好了。多一个选项,多一种可能,调用者可以强行设置,不强行就用词典默认,只要工作正常,岂不是比一律 case sensitive 更好?

@songxiaocheng 上述中的:

我认为本库作为底层 reader,不应揣摩用户意图,那是应用层的事。本库应该致力于正确呈现词典制作者想要呈现的词典的内容。制作词典的人设置的 KeyCaseSensitive 正是他用来调试自己词典的设置。作为一个 mdict reader, 只有严格按照词典设置去工作,才能尽可能和词典制作者调试的时候看到的一致。他调试的时候没问题,我们就大概率不会出问题。

“只有严格按照词典设置去工作” 这点我是赞同的,我们再回来看这句话所代表的含义:

  1. 词典的设置 Header.KeyCaseSensitiveYes 时: 用户查询 England 应该返回 England 而不应该返回england
  2. 词典的设置 Header.KeyCaseSensitiveNo 时: 用户查询 England 应该返回 England 如果没有则返回 england
  3. 词典的设置 Header.KeyCaseSensitiveNo, 但 searchOption.KeyCaseSensitive = true 时,则:用户查询 England 应该返回 England 而不应该返回england

在现有的实现中:

  1. 仅词典的设置 Header.KeyCaseSensitiveNo 时, 即 this.saerchOption.KeyCaseSensitive = false 使用的是 wordCompare 会优先返回小写的结果(因为顺序是小写大写词条相邻,先匹配小写)

  2. 仅词典的设置 Header.KeyCaseSensitiveYes 时, 即 this.saerchOption.KeyCaseSensitive = true 使用的是 normalUpperCaseWordCompare 会优先返回大写结果(因为大写的词在词典开始的 keyblock 中)

  3. 词典的设置 Header.KeyCaseSensitiveNo, 但 searchOption.KeyCaseSensitive = true 时,结果同2)

现在的实现存在的几个问题:
第一个是 KeyCaseSensitiveNo 时,会优先返回小写(现在的实现和我的理解也有点出入),这个问题我同意你说的 “应该先返回精确匹配结果,再返回小写结果”
第二个是 KeyCaseSensitiveYes 时,只会返回大写结果,小写结果匹配不到,这个目前实现是正确的
第三个是 KeyCaseSensitiveNo 且用户设置 searchOption.KeyCaseSensitive = true 时,也只能精确匹配大写结果,这个也是符合预期的

因此其实就是第一个问题需要解决,即KeyCaseSensitiveNo 时,先返回精确匹配,再返回小写结果。

另外,我觉得 mdd 文件中,如果 definition 中是大写的引用路径,结果返回了小写的结果,比如 \US_pron.png 返回 \us_pron.png 我理解问题应该不大(从作者采用 \ 作为路径分隔,猜测应该是windows环境设计上述词典的,因此应该不会区分这部分的大小写)

我先合并你的PR看下效果好了

  1. 词典的设置 Header.KeyCaseSensitiveYes 时: 用户查询 England 应该返回 England 而不应该返回england
  2. 词典的设置 Header.KeyCaseSensitiveNo 时: 用户查询 England 应该返回 England 如果没有则返回 england
  3. 词典的设置 Header.KeyCaseSensitiveNo, 但 searchOption.KeyCaseSensitive = true 时,则:用户查询 England 应该返回 England 而不应该返回england

可见对预期行为我们是一致的。我是完全按照这个思路去实现的,实际用的 KeyCaseSensitive (通过新增的 _isKeyCaseSensitive() 方法) 来自于 this.searchOptions.keyCaseSensitive || common.isTrue(this.header.KeyCaseSensitive);

  1. 词典的设置 Header.KeyCaseSensitive 为 Yes 时,无 searchOptions.keyCaseSensitive 时,实际为 _isKeyCaseSensitive() 为 true,对应你说的第一种情况,采用”大小写敏感(精确)“的匹配
  2. 词典的设置 Header.KeyCaseSensitive 为 No 时,无 searchOptions.keyCaseSensitive 时,实际为 _isKeyCaseSensitive() 为 false,对应你说的第二种情况,先采用”大小写敏感(精确)“的匹配,有则直接返回,无则采用”大小写不敏感“的匹配。
  3. searchOptions.keyCaseSensitive 为 true 时,不管词典设置,实际为 _isKeyCaseSensitive() 为 true,对应你说的第三种情况,采用”大小写敏感(精确)“的匹配。
  4. searchOptions.keyCaseSensitive 为 false 时,不管词典设置,实际为 _isKeyCaseSensitive() 为 false,你没提到这种情况,这时,先采用”大小写敏感(精确)“的匹配,有则直接返回,无则采用”大小写不敏感“的匹配。

现在的实现存在的几个问题:
第一个是 KeyCaseSensitiveNo 时,会优先返回小写(现在的实现和我的理解也有点出入),这个问题我同意你说的 “应该先返回精确匹配结果,再返回小写结果”

你说的应该是我之前提的 PR #37 出现的情况,那时我想的比较简单,随后我也发现了问题,结果是因为词典中小写在前,大写紧随其后,可是由于大小写不敏感,搜到小写就认为匹配,直接返回了。也正是如此,我提出本 Issue 就是希望理清预期行为,从而解决大小写问题。后来你的 PR #44 通过忽略词典的设置,通过了测试,但是正如我说的,这实际上没有解决问题而只是隐藏了问题。

因而我提出了 PR #45。在此之后,优先返回”大小写敏感(精确)“的匹配,如果输入是大写,则只要存在对应”大小写一致“的key,会返回这一条的。只有当不存在大小写一致的情况,才会返回”仅大小写不一致“的匹配。由于词典设置 KeyCaseSensitive 为 No,(在没有 searchOptions.keyCaseSensitive 强行设置的情况下)这是符合预期的。

也可以再添加一些相关测试,我是意图按上述描述去实现的。

晚上我抽点时间写几个测试看下好了。

补充了几个测试,目前没有发现问题,还有一些边缘条件需要完善,后续补充。

如果KeyCaseSensitive=No,那么词典中词的顺序是

Abc
abc
Beer
beer
Holanda
holanda

这里应该写反了,根据你提到的 #28 中的讨论(#28 (comment) )以及我的实测,应该是

abc
Abc
beer
Beer
holanda
Holanda

@songxiaocheng 是的,应该是你这个实测的结果,是我记错了