如何在C++的std::string中去除重音符号和波浪符号

19

我在C++中遇到一个字符串问题,其中有几个西班牙语单词。这意味着我有很多带重音符号和波浪符号的单词。我想用它们没有重音的对应词替换它们。例如:我想将此单词“había”替换为habia。我尝试直接使用string类的replace方法进行替换,但无法使其正常工作。

我正在使用以下代码:

for (it= dictionary.begin(); it != dictionary.end(); it++)
{
    strMine=(it->first);
    found=toReplace.find_first_of(strMine);
    while (found!=std::string::npos)
    {
        strAux=(it->second);
        toReplace.erase(found,strMine.length());
        toReplace.insert(found,strAux);
        found=toReplace.find_first_of(strMine,found+1);
    }
}

其中dictionary是一个类似于下面这样的映射(有更多的条目):

dictionary.insert ( std::pair<std::string,std::string>("á","a") );
dictionary.insert ( std::pair<std::string,std::string>("é","e") );
dictionary.insert ( std::pair<std::string,std::string>("í","i") );
dictionary.insert ( std::pair<std::string,std::string>("ó","o") );
dictionary.insert ( std::pair<std::string,std::string>("ú","u") );
dictionary.insert ( std::pair<std::string,std::string>("ñ","n") );

要替换的字符串是toReplace

std::string toReplace="á-é-í-ó-ú-ñ-á-é-í-ó-ú-ñ";

我显然缺少了什么,我搞不清楚它是什么。 是否有任何库可供使用?

谢谢。


1
你应该添加你的目标平台(Windows,Linux等)和你的目标编码(UTF-8,UTF-16等)。例如,你的“á”是字形E1,在USO-8859-1字符上翻译为'á',在UTF-16 wchar_t上翻译为L'á',但在UTF-8上翻译为“á”(是的,两个字符)。 - paercebal
抱歉...当我从Unicode.org搜索回到您的帖子并验证评论时,您已经回答了... - paercebal
1
这是一个重复的问题,参考链接:<a href="https://dev59.com/_nVC5IYBdhLWcg3w7V73"> 如何将8位字符转换为7位字符?(例如,Ü转换为U) </a>。 - Jim
8个回答

34

我不同意目前“批准”的答案。当在索引文本时,问题是非常有意义的。像不区分大小写的搜索一样,不区分重音符号的搜索也是一个好主意。"naïve" 匹配 "Naïve" 匹配 "naive" 匹配 "NAİVE"(你知道土耳其语中大写的i是İ吗?这就是为什么要忽略重音符号)

现在,最好的算法被批准的答案所提示:使用NKD(分解)将带重音符号的字母分解为基础字母和单独的重音符号,然后删除所有重音符号。

不过,之后的重新组合几乎没有任何意义。你已经删除了大部分会改变的序列,其他序列从本质上讲是相同的。NKC中的æ与NKD中的æ有什么区别呢?


1
你的理论在德语中不成立。 "bär"(熊)可以与 "baer"(熊)进行比较,但不能与 "bar"(酒吧)进行比较。 - user3850
3
实际上不是这样的。将“bär”进行Unicode分解,得到的是ba"r(使用额外的码点来表示umlaut),而不是“baer”。请记住,Unicode分解是与语言环境无关的。例如,ä = ae是德语的分解方式,但不是荷兰语的分解方式。 - MSalters
我认为你指的是大写字母“İ”,而不是“Ï”。 - CB Bailey
也许您可以指出一些NKC / NKD的定义吗? - lajarre

22

首先,这是一个非常糟糕的想法:通过移除字母来破坏某人的语言。虽然像“naïve”中的额外点对于只能讲英语的人似乎是多余的,但在世界上有成千上万种写作系统,在这些系统中这种区别非常重要。编写软件来毁掉某人的语言会使您完全站在使用计算机扩大人类表达领域与成为压迫工具之间的紧张关系的错误一方。

你尝试这样做的原因是什么?是否有些东西卡在重音上?很多人都很乐意帮助您解决这个问题。

话虽如此,libicu可以为您完成这项工作。打开转换演示;复制并粘贴您的西班牙文本到“输入”框中;输入即可。

NFD; [:M:] remove; NFC

将“Compound 1”输入并点击“transform”按钮。(本内容参考自ICU中的Unicode转换幻灯片第9页,第29-30页展示了如何使用API。)


嗯,我来自西班牙语国家阿根廷,所以在第一部分方面我已经很熟悉了。让我在下面的回答中提供更多细节。 - Alejo
3
没错!重音符号和波浪线不是为了可爱而存在的;去掉它们会改变文本的含义。 "Habia" 不是一个单词,但 "había" 是。"Carácter" 是“个性”;“caracter”是印刷符号。“Cana” 是白头发;“Caña” 是一根手杖。“Peso”是一个名词。“Pesó”是一个动词。 - Euro Micelli
顺便说一下,我找到了这个页面,它解释了如何使用ICU Transliterator:http://www.markcmusic.com/blog/2008/08/28/using-the-icu4c-transliterator/ - Alejo
3
虽然从理论上说是正确的,但在实际应用中,许多西班牙语使用者并不费心使用重音符号或者只是使用不正确(我想到IM)。这种情况下,意思仍然很明确。就像英语中的its/it's, they're/their等。错误使用它们显示出一定的疏忽,但很少引起误解。 - Juan Pablo Califano
在荷兰语中,重音用于强调。"een"是荷兰语中的"an"或"one",例如"een appel"。但是,如果您想要强调"precisely one",则需要添加重音:"één appel"。 - MSalters
这是一个相当不寻常的观点。去除重音可以在实施搜索功能时非常有用,因为可能有人不知道如何输入重音符号,但却需要在带有重音的内容中进行搜索。去除重音也可以在处理古老的程序时非常有用,这些程序没有人愿意“帮助”。Whitaker's words,一本拉丁词典,不支持带有长音符的字母,因为它是一个非常古老的程序,而且长音符对于阅读拉丁文并不是必需的,直到1645年才出现。 - undefined

2
我认为你应该深入研究问题的根源,即寻找一种解决方案,使您能够支持Unicode编码或用户的语言环境。
话虽如此,你的问题是你正在处理多字符字符串。有std :: wstring,但我不确定我会使用它。首先,宽字符并不适用于处理可变宽度编码。这个问题很深,所以我就说到这里吧。
现在,至于你代码的其余部分,它容易出错,因为你将循环逻辑和翻译逻辑混合在一起。因此,至少会出现两种类型的错误:翻译错误和循环错误。请使用STL,它可以帮助你处理循环部分。
以下是替换字符串中字符的粗略解决方案。
main.cpp:
#include <iostream>
#include <string>
#include <iterator>
#include <algorithm>
#include "translate_characters.h"

using namespace std;

int main()
{
    string text;
    cin.unsetf(ios::skipws);
    transform(istream_iterator<char>(cin), istream_iterator<char>(),
              inserter(text, text.end()), translate_characters());
    cout << text << endl;
    return 0;
}

translate_characters.h:

#ifndef TRANSLATE_CHARACTERS_H
#define TRANSLATE_CHARACTERS_H

#include <functional>
#include <map>

class translate_characters : public std::unary_function<const char,char> {
public:
    translate_characters();
    char operator()(const char c);

private:
    std::map<char, char> characters_map;
};

#endif // TRANSLATE_CHARACTERS_H

translate_characters.cpp:

#include "translate_characters.h"

using namespace std;

translate_characters::translate_characters()
{
    characters_map.insert(make_pair('e', 'a'));
}

char translate_characters::operator()(const char c)
{
    map<char, char>::const_iterator translation_pos(characters_map.find(c));
    if( translation_pos == characters_map.end() )
        return c;
    return translation_pos->second;
}

你正在映射<char,char>。但是utf-8中的“ñ”(例如)不是一个char(实际上是一个2字节的东西)。这是一种很好的即时技术,但我想它比那更复杂。 - lajarre

0
你可能想要查看一下Boost库(http://www.boost.org/)。
它有一个正则表达式库,你可以使用它。 此外,它还具有一个专门的库,其中包含一些用于字符串操作(link)的函数,包括替换。

0

我无法链接ICU库,但我仍然认为这是最好的解决方案。由于我需要尽快使该程序正常运行,因此我编写了一个小程序(我需要改进它),并将使用它。感谢大家的建议和答案。

这是我要使用的代码:

for (it= dictionary.begin(); it != dictionary.end(); it++)
{
    strMine=(it->first);
    found=toReplace.find(strMine);
    while (found != std::string::npos)
    {
        strAux=(it->second);
        toReplace.erase(found,2);
        toReplace.insert(found,strAux);
        found=toReplace.find(strMine,found+1);
    }
} 

我会在下次需要提交程序进行更正时(大约6周后)进行更改。


-1
尝试使用std::wstring而不是std::string。UTF-16应该可以工作(而不是ASCII)。

-1
如果您使用的是Unix系统,我建议使用tr工具来完成此操作:它是专门为此目的定制的。请记住,没有代码==没有错误的代码。 :-)
编辑:抱歉,您是正确的,tr似乎不起作用。那么sed怎么样?这是我编写的一个相当愚蠢的脚本,但对我来说它有效。
#!/bin/sed -f
s/á/a/g;
s/é/e/g;
s/í/i/g;
s/ó/o/g;
s/ú/u/g;
s/ñ/n/g;

-2
    /// <summary>
    /// 
    /// Replace any accent and foreign character by their ASCII equivalent.
    /// In other words, convert a string to an ASCII-complient string.
    /// 
    /// This also get rid of special hidden character, like EOF, NUL, TAB and other '\0', except \n\r
    /// 
    /// Tests with accents and foreign characters:
    /// Before: "äæǽaeöœoeüueÄAeÜUeÖOeÀÁÂÃÄÅǺĀĂĄǍΑΆẢẠẦẪẨẬẰẮẴẲẶАAàáâãåǻāăąǎªαάảạầấẫẩậằắẵẳặаaБBбbÇĆĈĊČCçćĉċčcДDдdÐĎĐΔDjðďđδdjÈÉÊËĒĔĖĘĚΕΈẼẺẸỀẾỄỂỆЕЭEèéêëēĕėęěέεẽẻẹềếễểệеэeФFфfĜĞĠĢΓГҐGĝğġģγгґgĤĦHĥħhÌÍÎÏĨĪĬǏĮİΗΉΊΙΪỈỊИЫIìíîïĩīĭǐįıηήίιϊỉịиыїiĴJĵjĶΚКKķκкkĹĻĽĿŁΛЛLĺļľŀłλлlМMмmÑŃŅŇΝНNñńņňʼnνнnÒÓÔÕŌŎǑŐƠØǾΟΌΩΏỎỌỒỐỖỔỘỜỚỠỞỢОOòóôõōŏǒőơøǿºοόωώỏọồốỗổộờớỡởợоoПPпpŔŖŘΡРRŕŗřρрrŚŜŞȘŠΣСSśŝşșšſσςсsȚŢŤŦτТTțţťŧтtÙÚÛŨŪŬŮŰŲƯǓǕǗǙǛŨỦỤỪỨỮỬỰУUùúûũūŭůűųưǔǖǘǚǜυύϋủụừứữửựуuÝŸŶΥΎΫỲỸỶỴЙYýÿŷỳỹỷỵйyВVвvŴWŵwŹŻŽΖЗZźżžζзzÆǼAEßssIJIJijijŒOEƒf'ξksπpβvμmψpsЁYoёyoЄYeєyeЇYiЖZhжzhХKhхkhЦTsцtsЧChчchШShшshЩShchщshchЪъЬьЮYuюyuЯYaяya"
    /// After:  "aaeooeuueAAeUUeOOeAAAAAAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaaaaaaaBbCCCCCCccccccDdDDjddjEEEEEEEEEEEEEEEEEEeeeeeeeeeeeeeeeeeeFfGGGGGgggggHHhhIIIIIIIIIIIIIiiiiiiiiiiiiJJjjKKkkLLLLllllMmNNNNNnnnnnOOOOOOOOOOOOOOOOOOOOOOooooooooooooooooooooooPpRRRRrrrrSSSSSSssssssTTTTttttUUUUUUUUUUUUUUUUUUUUUUUUuuuuuuuuuuuuuuuuuuuuuuuYYYYYYYYyyyyyyyyVvWWwwZZZZzzzzAEssIJijOEf'kspvmpsYoyoYeyeYiZhzhKhkhTstsChchShshShchshchYuyuYaya"
    /// 
    /// Tests with invalid 'special hidden characters':
    /// Before: "\0\0\000\0000Bj��rk�\'\"\\\0\a\b\f\n\r\t\v\u0020���oacu\'\\\'te�"
    /// After:  "00000Bjrk'\"\\\n\r oacu'\\'te"
    /// 
    /// </summary>
    private string Normalize(string StringToClean)
    {
        string normalizedString = StringToClean.Normalize(NormalizationForm.FormD);
        StringBuilder Buffer = new StringBuilder(StringToClean.Length);

        for (int i = 0; i < normalizedString.Length; i++)
        {
            if (CharUnicodeInfo.GetUnicodeCategory(normalizedString[i]) != UnicodeCategory.NonSpacingMark)
            {
                Buffer.Append(normalizedString[i]);
            }
        }

        string PreAsciiCompliant = Buffer.ToString().Normalize(NormalizationForm.FormC);
        StringBuilder AsciiComplient = new StringBuilder(PreAsciiCompliant.Length);

        foreach (char character in PreAsciiCompliant)
        {
            //Reject all special characters except \n\r (Carriage-Return and Line-Feed). 
            //Get rid of special hidden character, like EOF, NUL, TAB and other '\0'
            if (((int)character >= 32 && (int)character < 127) || ((int)character == 10 || (int)character == 13)) 
            {
                AsciiComplient.Append(character);
            }
        }
        return AsciiComplient.ToString().Trim(); // Remove spaces at start and end of string if any
    }

1
这是来自Java,如果我没记错的话,为什么你要包含它,如果它说“C”? - Asfo

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