【问题标题】:Is there a way to use the erase-remove idiom in concert with other looping conditions?有没有办法将擦除删除习语与其他循环条件一起使用?
【发布时间】:2019-05-26 14:55:37
【问题描述】:

我有一个函数,给定一个文本块,应该删除所有标点符号,并使所有字母小写,最后,应该根据单字母密码将它们转换。下面的代码有效:

class Cipher { 
  public:

  string keyword; 

  string decipheredText;

  deque<string> encipheredAlphabet;

    static bool is_punctuation (char c) {
      return c ==  '.' || c == ',' || c == '!' || c == '\''|| c == '?' || c 
      == ' ';
    }

  string encipher(string text) { 
    Alphabet a;
    encipheredAlphabet = a.cipherLetters(keyword);


    text.erase( remove_if(text.begin(), text.end(), is_punctuation), 
    text.end() );

    string::iterator it;
    for (it = text.begin(); it != text.end(); it++) { 
      *it = tolower(*it); 
      // encipher text according to shift
    }

    return text;
  }

};

问题是,它目前对字符串进行了两次遍历,一次删除标点符号,一次执行所有其他操作。这似乎效率低下,因为似乎所有转换都可以以某种方式通过字符串一次完成。有没有一种干净的方法可以将擦除删除习语与其他循环条件结合起来?

【问题讨论】:

  • 当然。只需编写您自己的算法:remove_if_tolowercase_otherwise。看看std::remove_if 是如何实现的并进行调整。
  • 这似乎效率低下 -- 分析代码。遍历一个容器n 次一次执行一次操作,或者一次执行n 次操作,理论上复杂度是一样的。
  • 是的,你是对的。我的意思是复杂性是相同的,但现实世界的效率可能不同。但这就是为什么在试图使循环“太优雅”以至于人们无法理解它在做什么之前,应该对代码进行分析。
  • 代码可读性很重要,两个循环的可读性很强,因为它们执行两个不同的任务。您可以将 for 循环替换为 std::transform。
  • 第二遍看起来不像for(auto &amp;c : text)那样冗余和漂亮。

标签: c++ erase-remove-idiom


【解决方案1】:

使用range-v3,您可以创建(惰性)视图:

return text | ranges::view::filter([](char c){ return !is_punctuation(c); })
            | ranges::view::transform([](char c) -> char { return to_lower(c); });

【讨论】:

    【解决方案2】:

    您可以使用std::accumulate 和一个迭代器作为插入到输出std::string 的初始值来完成此操作

    auto filter = [](auto pred) {
        return [=](auto map) {
            auto accumulator = [=](auto it, auto c) {
                if (pred(c)) {
                    *it = map(c);
                }
                return ++it;
            };
            return accumulator;
        };
    };
    
    auto accumulator = filter(std::not_fn(is_punctuation))
    ([](auto c) {
        return std::tolower(c);
    });
    
    std::string in = "insIsjs.|s!js";
    std::string out;
    std::accumulate(std::begin(in), std::end(in), std::back_inserter(out), accumulator);
    

    见demo

    【讨论】:

    • 我不能说我理解这个解决方案。名称不透明的三重嵌套 lambda 让我很难理解它在做什么。使用std::accumulate 来做一些真的不像积累东西的事情也很奇怪,特别是因为你在一个感觉对accumulate 无效的位置传递了一个迭代器。与其经历这个,我更喜欢某种transform_if 或transform_remove_if
    • @Justin 代码模型 transform_if 和 init 值是一个迭代器这一事实并没有删除作为累积的 fact ,就像在这种情况下一样,你正在累积到一个 iterator 中,但我会解开 lambdas 以便它更清晰
    • 没错。它确实做了一个transform_if,但以一种令人困惑的迂回方式。 init 值是一个迭代器这一事实令人困惑,因为认为 accumulate 有一个输出迭代器参数似乎是一个错误。
    • 有很多std算法接收一个输出迭代器,有些遵循与上面相同的习语,这里需要的是一种不同的方式来考虑累积的初始值,你可能找到一些数据流库,它们也将集合视为累积的初始值。
    【解决方案3】:

    复制和/或修改字符,然后截断字符串:

    string encipher(string text)
    {
        auto it = text.begin(),
             jt = it;
        for (; it != text.end(); it++)
        {
            if (!is_punctuation(*it))
            {
                *jt = tolower(*it);
                ++jt;
            }
        }
        text.erase(jt, it);
        return text;
    }
    

    【讨论】:

      【解决方案4】:

      如果你不想做两个循环,因为你已经测量并发现它更慢,编写一个自定义算法:

      template <typename Iter, typename OutIter>
      OutIter lowercased_without_punctuation(Iter begin, Iter end, OutIter out) {
          while (begin != end) {
              // Ignoring things like std::move_iterator for brevity.
              if (!is_punctuation(*begin)) {
                  *out = tolower(*begin);
                  ++out;
              }
      
              // Use `++iter` rather than `iter++` when possible
              ++begin;
          }
      
          return out;
      }
      
      // ...
      
      string encipher(string text) {
          Alphabet a;
          encipheredAlphabet = a.cipherLetters(keyword);
      
          text.erase(
              lowercased_without_punctuation(text.begin(), text.end(), text.begin()),
              text.end());
      
          return text;
      }
      

      如果你再想一想,lowercased_without_punctuation 实际上是一个更通用的算法的特例,它可能被称为transform_if (relevant Q&A):

      template <typename Iter, typename OutIter, typename Pred, typename Transf>
      OutIter transform_if(Iter begin, Iter end, OutIter out, Pred p, Transf t) {
          while (begin != end) {
              if (p(*begin)) {
                  *out = t(*begin);
                  ++out;
              }
      
              ++begin;
          }
      
          return out;
      }
      
      // ...
      
      string encipher(string text) {
          Alphabet a;
          encipheredAlphabet = a.cipherLetters(keyword);
      
          text.erase(
              transform_if(text.begin(), text.end(), text.begin(),
                  [](char c) { return !is_punctuation(c); },
                  [](char c) { return tolower(c); }),
              text.end());
      
          return text;
      }
      

      【讨论】:

      • 谢谢,这看起来很容易理解。
      • 我们教擦除删除而不是循环擦除是有原因的。代码中的每个erase 都需要移动字符串的整个剩余部分。
      • @T.C. - 确切地。这段代码只有一个显式循环,但复杂度是O(n^2),而原始版本是O(n)。
      • @T.C.我忙于思考正确性,以至于我完全忘记了时间复杂度。如果我可以删除或否决这个答案,我会
      • @Justin:您可以通过使用“输入”和“输出”迭代器来修复您的答案,这样您就不会调用擦除,而只是跳过标点符号。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-17
      • 2020-01-15
      相关资源
      最近更新 更多