找出两个字符串是否相似

3

规则: 两个字符串a和b,它们都由ASCII字符和非ASCII字符(例如,使用gbk编码的中文字符)组成。

If the non-ASCII chars contained in b also show up in a and no less than the times they appear in b, then we say b is similar with a.

例如:

a = "ab中ef日jkl中本"  //non-ASCII chars:'中'(twice), '日'(once), '本'(once)
b = "bej中中日"  //non-ASCII chars:'中'(twice), '日'(once)
c = 'lk日日日'   //non-ASCII chars:'日'(3 times, more than twice in a)

根据规则,b与a相似,但c不相似。 我的问题是: 我们不知道a和b中有多少个非ASCII字符,可能很多。 因此,为了找出a和b中非ASCII字符出现的次数,我需要使用哈希表来存储它们的出现次数吗? 以字符串a为例:

[non-ASCII's hash-value]:[times]
     中's hash-val      : 2
     日's hash-val      : 1
     本's hash-val      : 1

检查字符串b,如果在b中遇到一个非ASCII字符,则对其进行哈希处理,并检查a的哈希表,如果该字符存在于a的哈希表中,则出现次数减1。 如果出现次数小于0(-1),则我们认为b与a不相似。

还有更好的方法吗?

PS: 我逐字节读取字符串a,如果字节小于128,则将其视为ASCII字符,否则将其视为非ASCII字符(多字节)的一部分。 这就是我找出非ASCII字符的方法。 这样做对吗?


2
除非您知道编码方式,否则无法逐字节读取字符串。如果字符串以实际字符的形式呈现给您,则已为您完成编码。您的意思是“逐个读取字符串中的字符,如果字符的代码点小于128,则为ASCII码”。 - Ray Toal
你的例子假设使用 >=,但规则只是说“更多”,即 > - jfs
@Ray Toal,我正在做的是这样的:for(int i=0; i < strlen(a); i++) { char tmp = a[i]; ...},这样对吗? - Alcott
@Ray,现在我标记为c语言。我需要知道编码吗?知道或不知道编码有什么区别吗? - Alcott
我已经编写了一个演示不同策略将字符串分段为字符的脚本:按字节、按代码点、按NFC形式的代码点、按字形簇。'bytes'方法在utf-16编码的问题示例上失败;代码点方法在带有组合符号的示例(例如a=u'\xea',b=u'e\u0302',两者都是'ê'字符)上中断;规范化代码点不能通过基于天城文kshi('क्षि')的测试。作为参考,我使用了实现http://www.unicode.org/reports/tr29/的`/\X/`正则表达式。 - jfs
显示剩余3条评论
1个回答

7

你问了两个问题:

  1. 我们能使用哈希表计算非ASCII字符吗?答案:可以。当你读取字符(不是字节)时,检查代码点。对于任何代码点大于127的字符,将其放入计数哈希表中。即对于字符c,如果c不在表中,则添加(c,1),如果c已经在表中,则将(c,x)更新为(c,x + 1)。

  2. 有没有比你递增a并在遍历b时递减更好的解决方法?如果你的哈希表实现几乎具有O(1)访问权限,那么我认为没有。你正好一次查看字符串中的每个字符,并且对于每个字符,你要执行哈希表插入或查找,加法或减法以及针对0的检查。对于未排序的字符串,你必须无论如何查看两个字符串中的所有字符,因此,我认为你已经给出了最佳解决方案。

面试官可能会希望你说出像这样的话:“嗯,如果这些字符串实际上是无法适应内存的大型文件,我该怎么办?”或者让你问:“那么这些字符串是否已排序?因为如果已排序,我可以更快地完成...”。
但现在假设这些字符串是巨大的。你在内存中存储的唯一东西是哈希表。Unicode只有大约100万个代码点,你为每个代码点存储一个整数计数,因此即使从千兆字节大小的文件获取数据,你只需要大约4MB左右的哈希表(或这个值的小倍数,因为会有开销)。
在没有其他条件的情况下,你的算法很好。预先对字符串进行排序不好;它会占用更多的内存,并且不是线性时间操作。
补充说明:
由于您最初的评论提到了类型char而不是wchar_t,我想展示使用宽字符串的示例。请参见http://codepad.org/B3MXOgqc
希望能帮到您。
第二个补充:

好的,这里有一个C程序,可以准确地演示如何逐个字符遍历宽字符串:

http://codepad.org/QVX3QPat

这是一个非常简短的程序,因此我也会在这里粘贴它:

#include <stdio.h>
#include <string.h>
#include <wchar.h>

char *s1 = "abd中日";
wchar_t *s2 = L"abd中日";

int main() {
    int i, n;
    printf("length of s1 is %d\n", strlen(s1));
    printf("length of s2 using wcslen is %d\n", wcslen(s2));
    printf("The codepoints of the characters of s2 are\n");
    for (i = 0, n = wcslen(s2); i < n; i++) {
        printf("%02x\n", s2[i]);
    } 
    return 0;
}

输出:

length of s1 is 9
length of s2 using wcslen is 5
The codepoints of the characters of s2 are
61
62 
64
4e2d
65e5

我们可以从中学到什么?以下是几点:
  1. 如果您在 CJK 字符中使用普通的 char,则字符串长度将会错误
  2. 要在 C 中使用 Unicode 字符,请使用 wchar_t
  3. 字符串字面量在宽字符串前有一个前导 L
在此示例中,我使用了带有 CJK 字符的字符串,并使用了 wchar_t 和带有 wcslen 的 for 循环。请注意,我正在处理实际字符,而不是字节,因此我得到了正确的字符计数,即 5。现在,我打印出每个代码点。在您的面试问题中,您将查看代码点是否为 >=128。我用十六进制显示它们,这是文化习惯,所以您可以寻找 > 0x7F。 :-) 补充 3

值得一读的是http://tldp.org/HOWTO/Unicode-HOWTO-6.html中的一些注释。字符处理比上面的简单示例还要复杂得多。在下面的评论中,J.F. Sebastian提供了许多其他重要的链接。

需要解决的少数问题之一是规范化。例如,当给出两个字符串时,一个仅包含Ç,另一个包含C后跟组合标记CEDILLA BELOW,您的面试官是否关心它们是否相同?它们表示相同的字符,但一个使用一个代码点,另一个使用两个代码点。


1
如果你还没有看过的话,@Alcott可以参考一下http://www.joelonsoftware.com/articles/Unicode.html。如果你已经看过了,那我很抱歉,但我不确定“一个字符是一个字节”的评论是指C语言还是编码一般情况下,所以我想提供这个参考链接。 - Ray Toal
我的意思是,你需要知道a的编码方式,才能确定你的字节数组中有多少个字符。你的面试问题中是否提到了编码、C语言、Unicode、多字节字符串、wchar_t或其他相关内容?由于你使用了CJK字符,我认为最简单的方法是使用wchar_t而不是C语言,并让C语言为你完成编码工作。 :-) - Ray Toal
1
我认为在使用wchar_t字符串时不应该使用strlen函数。 - Alcott
1
你不能总是只存储代码点。同样的用户可感知字符可以使用不同的代码点序列来表示,例如:http://ideone.com/8qSe9 。Unicode规范化形式至少应该被提及。字符的枚举也不是显而易见的,例如:http://ideone.com/bl5vp。请参阅[Unicode文本分段](http://www.unicode.org/reports/tr29/)。 - jfs
@Ray Toal,先生,这可能有点尴尬,今天我发布了一个由面试官提出的问题,希望能得到一些帮助,但是即使被浏览了30多次,也没有人回答。我真的希望您能抽出一点时间来帮助我解决这个问题,这是链接:https://dev59.com/Oms05IYBdhLWcg3wIehj。非常感谢。 - Alcott
显示剩余12条评论

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