Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于 keyCaseSensitivestripKey 的预期行为 #41

Closed
songxiaocheng opened this issue Jun 23, 2021 · 15 comments
Closed

关于 keyCaseSensitivestripKey 的预期行为 #41

songxiaocheng opened this issue Jun 23, 2021 · 15 comments

Comments

@songxiaocheng
Copy link
Contributor

songxiaocheng commented Jun 23, 2021

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

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

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

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

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

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

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

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

方式4的话感觉最佳?

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

@terasum
Copy link
Owner

terasum commented Jun 23, 2021

大小写的问题我们在 #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

@songxiaocheng
Copy link
Contributor Author

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

@terasum
Copy link
Owner

terasum commented Jun 23, 2021

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

@songxiaocheng songxiaocheng reopened this Jun 23, 2021
@songxiaocheng
Copy link
Contributor Author

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

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

@songxiaocheng
Copy link
Contributor Author

songxiaocheng commented Jun 23, 2021

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

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

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

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

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

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

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

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

@terasum
Copy link
Owner

terasum commented Jun 23, 2021

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

  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条,如果没有精确匹配的,返回哪个呢?任一个都可以吗?当然这种情况实际中可能极少,可以暂不特殊处理,找到哪个是哪个,只要保证精确匹配优先即可。
也自然不存在了。

@songxiaocheng
Copy link
Contributor Author

songxiaocheng commented Jun 23, 2021

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

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

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

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

@terasum
Copy link
Owner

terasum commented Jun 23, 2021

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

@terasum
Copy link
Owner

terasum commented Jun 23, 2021

image

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

@songxiaocheng
Copy link
Contributor Author

songxiaocheng commented Jun 23, 2021

我手头就有这种词典,他们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 更好?

@terasum
Copy link
Owner

terasum commented Jun 24, 2021

@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看下效果好了

@songxiaocheng
Copy link
Contributor Author

  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 强行设置的情况下)这是符合预期的。

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

@terasum
Copy link
Owner

terasum commented Jun 24, 2021

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

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

@terasum terasum closed this as completed Jun 25, 2021
@songxiaocheng
Copy link
Contributor Author

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

Abc
abc
Beer
beer
Holanda
holanda

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

abc
Abc
beer
Beer
holanda
Holanda

@terasum
Copy link
Owner

terasum commented Jun 28, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants