【问题标题】:How to remove elements by substrings from a STL container如何通过 STL 容器中的子字符串删除元素
【发布时间】:2012-06-14 09:11:59
【问题描述】:

我有一个对象向量(对象是术语节点,在其他字段中包含一个带有术语字符串的字符串字段)

class TermNode {
private:
    std::wstring term;
    double weight;
    ...
public:
    ...
};

经过一些处理和计算分数后,这些对象最终存储在一个 TermNode 指针向量中,例如

std::vector<TermNode *> termlist;

此向量的结果列表(最多包含 400 个条目)如下所示:

DEBUG: 'knowledge' term weight=13.5921
DEBUG: 'discovery' term weight=12.3437
DEBUG: 'applications' term weight=11.9476
DEBUG: 'process' term weight=11.4553
DEBUG: 'knowledge discovery' term weight=11.4509
DEBUG: 'information' term weight=10.952
DEBUG: 'techniques' term weight=10.4139
DEBUG: 'web' term weight=10.3733
...

我尝试做的是清理也包含在术语列表中的短语中的 子字符串 的最终列表。例如,查看上面的列表 sn-p,有短语 'knowledge discovery',因此我想删除单个术语 'knowledge' >'discovery',因为它们也在列表中并且在此上下文中是多余的。我想保留包含单个术语的短语。我也在考虑删除所有等于或少于 3 个字符的字符串。但这只是暂时的想法。

对于这个清理过程,我想使用 remove_if / find_if 编写一个类(使用新的 C++ lambdas),如果把该代码放在一个紧凑的类中会很好。

我不太确定如何解决这个问题。问题是我首先必须通过可能将标志设置为删除标记来确定要删除的字符串。这意味着我必须预处理该列表。我必须找到单个术语和包含这些单个术语之一的短语。我认为这不是一件容易的事,需要一些先进的算法。使用后缀树来识别子串?

向量上的另一个循环以及相同向量的副本可能会进行清理。我正在寻找时间上最有效的东西。

我一直在玩std::list erase incompatible iterator 中显示的想法或方向,使用 remove_if / find_if 和 Erasing multiple objects from a std::vector? 中使用的想法。

所以问题基本上是有一种聪明的方法可以做到这一点并避免多个循环,我如何识别要删除的单个术语?也许我真的错过了一些东西,但可能有人在那里给我一个很好的提示。

感谢您的意见!

更新

我按照 Scrubbins 推荐的方式实现了删除冗余的单个术语,如下所示:

/**
 * Functor gets the term of each TermNode object, looks if term string
 * contains spaces (ie. term is a phrase), splits phrase by spaces and finally
 * stores thes term tokens into a set. Only term higher than a score of 
 * 'skipAtWeight" are taken tinto account.
 */
struct findPhrasesAndSplitIntoTokens {
private:
    set<wstring> tokens;
    double skipAtWeight;

public:
    findPhrasesAndSplitIntoTokens(const double skipAtWeight)
    : skipAtWeight(skipAtWeight) {
    }

    /**
     * Implements operator()
     */
    void operator()(const TermNode * tn) {
        // --- skip all terms lower skipAtWeight
        if (tn->getWeight() < skipAtWeight)
            return;

        // --- get term
        wstring term = tn->getTerm();
        // --- iterate over term, check for spaces (if this term is a phrase)
        for (unsigned int i = 0; i < term.length(); i++) {
            if (isspace(term.at(i))) {
if (0) {
                wcout << "input term=" << term << endl;
}
                // --- simply tokenze term by space and store tokens into 
                // --- the tokens set
                // --- TODO: check if this really is UTF-8 aware, esp. for
                // --- strings containing umlauts, etc  !!
                wistringstream iss(term);
                copy(istream_iterator<wstring,
                        wchar_t, std::char_traits<wchar_t> >(iss),
                    istream_iterator<wstring,
                        wchar_t, std::char_traits<wchar_t> >(),
                    inserter(tokens, tokens.begin()));
if (0) {
                wcout << "size of token set=" << tokens.size() << endl;
                for_each(tokens.begin(), tokens.end(), printSingleToken());
}
            }
        }
    }

    /**
     * return set of extracted tokens
     */
    set<wstring> getTokens() const {
        return tokens;
    }
};

/**
 * Functor to find terms in tokens set
 */
class removeTermIfInPhraseTokensSet {
private:
    set<wstring> tokens;

public:
    removeTermIfInPhraseTokensSet(const set<wstring>& termTokens)
    : tokens(termTokens) {
    }

    /**
     * Implements operator()
     */
    bool operator()(const TermNode * tn) const {
        if (tokens.find(tn->getTerm()) != tokens.end()) {
            return true;
        }
        return false;
    }
};

...

findPhrasesAndSplitIntoTokens objPhraseTokens(6.5);
objPhraseTokens = std::for_each(
    termList.begin(), termList.end(), objPhraseTokens);
set<wstring> tokens = objPhraseTokens.getTokens();
wcout << "size of tokens set=" << tokens.size() << endl;
for_each(tokens.begin(), tokens.end(), printSingleToken());

// --- remove all extracted single tokens from the final terms list
// --- of similar search terms 
removeTermIfInPhraseTokensSet removeTermIfFound(tokens);
termList.erase(
    remove_if(
        termList.begin(), termList.end(), removeTermIfFound),
    termList.end()
);

for (vector<TermNode *>::const_iterator tl_iter = termList.begin();
      tl_iter != termList.end(); tl_iter++) {
    wcout << "DEBUG: '" << (*tl_iter)->getTerm() << "' term weight=" << (*tl_iter)->getNormalizedWeight() << endl;
    if ((*tl_iter)->getNormalizedWeight() <= 6.5) break;
}

...

我不能使用 C++11 lambda 语法,因为在我的 ubuntu 服务器上当前安装了 g++ 4.4.1。无论如何。它现在完成了这项工作。 可行的方法是检查生成的加权术语与其他搜索结果集的质量,看看我如何提高质量,并找到一种方法来结合原始查询术语来提升更相关的术语。这可能不是一件容易的事,我希望有一些“简单的启发式”。 但这可能是另一个新问题,如果再进一步的话:-)

因此,感谢大家提供如此丰富的思想贡献!

【问题讨论】:

  • 似乎您的问题标题有点误导...这不是矢量问题,这是文本处理问题。一旦确定了子字符串,从向量中清除它们就很简单了,尤其是在只有 400 个条目的情况下。
  • 为什么不使用vector 的智能指针指向TermNode 对象?这样,您就可以省去 deletefind 操作之后返回的指针?
  • 我很想按术语长度的相反顺序对向量进行排序。遍历向量,将每个术语拆分为单独的单词并将这些单词放入std::set。如果集合中已经存在这些词,则将该词标记为需要删除,然后担心清除向量。
  • 哦,如果您使用std::list 而不是向量,您将获得恒定时间的插入和删除,并且您可以在不使迭代器失效的情况下删除项目。您真的需要随机访问向量中的项目,还是只需要能够遍历它?
  • @Rook:从容器中高效便捷地移除物品很难衡量。由于内存缓存行为,vector 的性能通常优于list,即使对于“中间”操作也是如此,小到几百或几千个项目。大 O 复杂度的问题在于,它使理论上的复杂度达到了极限,并且幸福地忽略了常数因素以及许多人一开始只处理小集合的事实。

标签: c++ algorithm stl


【解决方案1】:

您需要做的是首先遍历列表并将所有多词值拆分为单个词。如果您允许使用 Unicode,这意味着您将需要类似于 ICU 的 BreakIterators 的东西,否则您可以使用简单的标点符号/空格分割。当每个字符串被分成它的组成词时,然后使用哈希映射来保存所有当前词的列表。当你达到一个多词值时,你可以检查它的词是否已经被找到。这应该是识别重复项的最简单方法。

【讨论】:

  • 这是一个有趣的开始,但是当您考虑子集方面时它会变得复杂。具体来说,我猜应该删除“Hello World”以支持“Hello World I Love You”。
  • @Matthieu:OP 没有在这些问题上给出明确的规范,所以我不能真正发表评论。另外,我想说默认情况下应该使用双端队列。
  • +1 表示注意到(因为我没有注意到)尽管提问者 “子字符串”,但查看完整的单词可能更合适。一个测试用例是在存在"applications" 的情况下是否应该删除"application"。此外,Unicode 难以标记化这一点对于某些用途至关重要。
  • 听起来越来越像解决方案是使用一些专门构建的语言修饰库。在 Java 中,有各种与 Lucene 相关的东西,但文本处理并不是 C++ 真正的强项。
  • 哦,还有,“当你达到一个多词值时,你可以检查它的词是否已经被找到”是危险的。 “爱丽丝讨厌鲍勃”可能不想被视为与“鲍勃讨厌爱丽丝”相同。
【解决方案2】:

我可以建议你以这种方式使用"erase-remove" 成语:

struct YourConditionFunctor {
    bool operator()(TermNode* term) {
        if (/* you have to remove term */) {
           delete term;
           return true;
        }
        return false;
    }
};

然后写:

termlist.erase(
    remove_if(
        termlist.begin(),
        termlist.end(), 
        YourConditionFunctor()
    ), 
    termlist.end()
);

【讨论】:

  • 不幸的是,我担心这是相当容易的部分。问题的核心(与标题所暗示的不同)是确定要删除的术语。此外,我发现实际上delete 谓词中的项目是一种非常糟糕的做法。这违背了谓词的所有常规用法,这些谓词通常不会修改它们被调用的对象,并且听起来像是灾难的秘诀。
  • 是的,必须同意马蒂厄,如何做到这一点在我在问题文本中陈述的问题中显示。我认为 stackoverflow 上的一堆答案显示了 如何 使用它。我的问题更多是如何清理术语,最后可能选择可以使用上述成语的正确容器。
猜你喜欢
  • 1970-01-01
  • 2013-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-24
  • 2020-04-05
  • 2010-09-27
相关资源
最近更新 更多