从字符串中随机选择特定的子序列

时间:2015-02-08 18:12:52

标签: c++ string random

给定一个包含许多填充短划线的字符的字符串,例如string s = "A--TG-DF----GR--";,我希望随机选择一个破折号块(可以是大小为1,2,......,字符串中最大连续破折号) ),并将它们复制到随机选择的字符串的另一部分。

例如,A--TG-DF(---)-GR--会被移动到A--T(---)G-DF-GR-- 另一次迭代可能会将A--TG-DF----GR(--)移至A--TG-(--)DF----GR

我通过int i = rand() % (int) s.length();生成字符串的随机索引。插入发生在s.insert(rand() % (int) s.length(), substr);,其中substr是破折号。

我的主要问题在于随机连续的破折号组。我想过使用s.find("-"),但是它只返回单个破折号的第一个实例,而不是破折号集的随机位置。

4 个答案:

答案 0 :(得分:4)

我知道这个问题可能已经沉浸在XY problems中,但我发现这是一个很好的挑战,所以我想用Boost Interval Container库来实现它。

图书馆的美妙之处在于你可以忘记很多细节,而你却不会牺牲很多表现。

我冒昧地将其概括,以便它能够同时移动多个破折号(均匀随机选择)。

解决方案运行 Live On Coliru 并在约2673毫秒(我机器上的1156毫秒)内随机变化的移动块数(1..3)生成给定样本的1,000,000个随机转置):

Generator gen(test_case);

std::string result;
std::map<std::string, size_t> histo;

for(int i = 0; i < 1000000; ++i) {
    auto const mobility = gen.gen_relocations(1 + gen.random(3)); // move n blocks of dashes

    result.clear();
    gen.apply_relocations(mobility, std::back_inserter(result));

    histo[result]++;
}
  

注意:基准时间包括构建生成的唯一结果的直方图所花费的时间

让我们在这里做一个代码演练来解释一些事情:

  1. 我尝试使用“可读”类型:

    namespace icl = boost::icl;
    
    using Position = size_t;
    using Map      = icl::interval_set<Position>;
    using Region   = Map::value_type;
    

    E.g。构建破折号Map的函数只是:

    template <typename It> Map region_map(It f, It l) {
        Map dashes;
    
        for (Position pos = 0; f!=l; ++f, ++pos)
            if ('-' == *f)
                dashes += pos;
    
        return dashes;
    }
    
      

    注意我怎么没有特别优化这个。我让interval_set结合相邻的破折号。我们可能会使用提示插入,或者将连续短划线添加为块的解析器。我在这里选择了KISS。

  2. 稍后,我们会生成重定位,将Region映射到文本其余部分的非移动Position

    using Relocs   = boost::container::flat_multimap<Position, Region>;
    

    通过使用平面多图,调用者的条目已按升序插入点排序。因为我们使用reserve() - ed前面的平面多图,所以我们在这里避免了基于节点的map实现的开销。

  3. 我们首先选择要移动的破折号块:

    Map pick_dashes(int n) {
        Map map;
        if (!_dashes.empty())
            for (int i = 0; i < n; ++i)
                map += *std::next(_dashes.begin(), _select(_rng));
        return map;
    }
    

    随机分布在构造时已经确定了尺寸,例如:

      _dashes(region_map(_input.begin(), _input.end())),
      _rng(std::random_device {}()),
      _select (0, _dashes.iterative_size() - 1),
      _randpos(0, _input.size() - 1),
    
  4. 接下来,我们为每个分配插入位置。诀窍是在源的非移动(惰性)区域内分配位置。

    • 这包括留在其位置的其他破折号
    • 有一个退化的情况,一切都是破折号,我们在构造函数中检测到了这一点:

        _is_degenerate(cardinality(_dashes) == _input.size())
      

    所以代码如下:

    Relocs gen_relocations(int n=1) {
        Map const moving = pick_dashes(n);
    
        Relocs relocs;
        relocs.reserve(moving.iterative_size());
    
        if (_is_degenerate)
        {
            // degenerate case (everything is a dash); no non-moving positions
            // exist, just pick 0
            for(auto& region : moving)
                relocs.emplace(0, region);
        } else {
            auto inertia = [&] {
                Position inert_point;
                while (contains(moving, inert_point = _randpos(_rng)))
                    ; // discard insertion points that are moving
                return inert_point;
            };
    
            for(auto& region : moving)
                relocs.emplace(inertia(), region);
        }
    
        return relocs;
    }
    

    现在我们需要做的就是应用重定位。

  5. 这个的一般实现非常简单。同样,特别优化以保持简单(KISS):

    template <typename F>
        void do_apply_relocations(Relocs const& mobility, F const& apply) const {
            icl::split_interval_set<Position> steps {{0, _input.size()}};
    
            for (auto& m : mobility) {
                steps += m.first; // force a split on insertion point
                steps -= m.second; // remove the source of the move
                //std::cout << m.second << " moving to #" << m.first << ": " << steps << "\n";
            }
    
            auto next_mover = mobility.begin();
    
            for(auto& step : steps) {
                while (next_mover!=mobility.end() && contains(step, next_mover->first))
                    apply((next_mover++)->second, true);
    
                apply(step, false);
            }
        }
    
      

    注意这里的诀窍是我们“滥用”split_interval_set合并策略,将处理分解为随机生成的插入点“停止”的子范围:这些人工区域是我们生成循环中的“步骤”。

  6. 我们实现了apply功能以获得我们想要的功能。在我们的例子中,我们需要一个像A--TG-DFGR(----)--这样的字符串,所以我们编写一个使用任何输出迭代器附加到容器(例如std::string)的重载:

    template <typename Out>
        Out apply_relocations(Relocs const& mobility, Out out) const {
            if (_is_degenerate)
                return std::copy(_input.begin(), _input.end(), out);
    
            auto to_iter = [this](Position pos) { return _input.begin() + pos; };
    
            do_apply_relocations(mobility, [&](Region const& r, bool relocated) {
                if (relocated) *out++ = '(';
                out = std::copy(
                    to_iter(first(r)),
                    to_iter(last(r)+1),
                    out
                );
                if (relocated) *out++ = ')';
            });
    
            return out;
        }
    
      

    注意这里的“复杂”部分是将Position映射到输入迭代器(to_iter)和代码,以便可选地在移动的块周围添加()

  7. 有了这个,我们已经看到了所有的代码。

    完整列表

    #include <boost/container/flat_map.hpp>
    #include <boost/icl/interval_set.hpp>
    #include <boost/icl/split_interval_set.hpp>
    #include <boost/icl/separate_interval_set.hpp>
    #include <boost/lexical_cast.hpp>
    #include <boost/range/algorithm.hpp>
    #include <iomanip>
    #include <iostream>
    #include <random>
    #include <map>
    #include <chrono>
    
    namespace icl = boost::icl;
    
    using Position = size_t;
    using Map      = icl::interval_set<Position>;
    using Region   = Map::value_type;
    using Relocs   = boost::container::flat_multimap<Position, Region>;
    
    struct Generator {
        Generator(std::string const& input) 
            : _input(input),
              _dashes(region_map(_input.begin(), _input.end())),
              _rng(std::random_device {}()),
              _select (0, _dashes.iterative_size() - 1),
              _randpos(0, _input.size() - 1),
              _is_degenerate(cardinality(_dashes) == _input.size())
        {
        }
    
        unsigned random(unsigned below) {
            return _rng() % below; // q&d, only here to make the tests deterministic for a fixed seed
        }
    
        Map full() const { 
            return Map { { 0, _input.size() } };
        }
    
        Relocs gen_relocations(int n=1) {
            Map const moving = pick_dashes(n);
    
            Relocs relocs;
            relocs.reserve(moving.iterative_size());
    
            if (_is_degenerate)
            {
                // degenerate case (everything is a dash); no non-moving positions
                // exist, just pick 0
                for(auto& region : moving)
                    relocs.emplace(0, region);
            } else {
                auto inertia = [&] {
                    Position inert_point;
                    while (contains(moving, inert_point = _randpos(_rng)))
                        ; // discard insertion points that are moving
                    return inert_point;
                };
    
                for(auto& region : moving)
                    relocs.emplace(inertia(), region);
            }
    
            return relocs;
        }
    
        template <typename Out>
            Out apply_relocations(Relocs const& mobility, Out out) const {
                if (_is_degenerate)
                    return std::copy(_input.begin(), _input.end(), out);
    
                auto to_iter = [this](Position pos) { return _input.begin() + pos; };
    
                do_apply_relocations(mobility, [&](Region const& r, bool relocated) {
                    if (relocated) *out++ = '(';
                    out = std::copy(
                        to_iter(first(r)),
                        to_iter(last(r)+1),
                        out
                    );
                    if (relocated) *out++ = ')';
                });
    
                return out;
            }
    
        template <typename F>
            void do_apply_relocations(Relocs const& mobility, F const& apply) const {
                icl::split_interval_set<Position> steps {{0, _input.size()}};
    
                for (auto& m : mobility) {
                    steps += m.first; // force a split on insertion point
                    steps -= m.second; // remove the source of the move
                    //std::cout << m.second << " moving to #" << m.first << ": " << steps << "\n";
                }
    
                auto next_mover = mobility.begin();
    
                for(auto& step : steps) {
                    while (next_mover!=mobility.end() && contains(step, next_mover->first))
                        apply((next_mover++)->second, true);
    
                    apply(step, false);
                }
            }
    
      private:
        std::string                             _input;
        Map                                     _dashes;
        std::mt19937                            _rng;
        std::uniform_int_distribution<Position> _select;
        std::uniform_int_distribution<Position> _randpos;
        bool                                    _is_degenerate;
    
        Map pick_dashes(int n) {
            Map map;
            if (!_dashes.empty())
                for (int i = 0; i < n; ++i)
                    map += *std::next(_dashes.begin(), _select(_rng));
            return map;
        }
    
        template <typename It> Map region_map(It f, It l) {
            Map dashes;
    
            for (Position pos = 0; f!=l; ++f, ++pos)
                if ('-' == *f)
                    dashes += pos;
    
            return dashes;
        }
    };
    
    int main() {
    
        for (std::string test_case : {
                "----",
                "A--TG-DF----GR--",
                "",
                "ATGDFGR",
            })
        {
            auto start = std::chrono::high_resolution_clock::now();
            Generator gen(test_case);
    
            std::string result;
            std::map<std::string, size_t> histo;
    
            for(int i = 0; i < 1000000; ++i) {
                auto const mobility = gen.gen_relocations(1 + gen.random(3)); // move n blocks of dashes
    
                result.clear();
                gen.apply_relocations(mobility, std::back_inserter(result));
    
                histo[result]++;
            }
            std::cout << histo.size() << " unique results for '" << test_case << "'"
                      << " in " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()-start).count() << "ms\n";
    
            std::multimap<size_t, std::string, std::greater<size_t> > ranked;
            for (auto& entry : histo)
                ranked.emplace(entry.second, entry.first);
    
            int topN = 10;
            for (auto& rank : ranked)
            {
                std::cout << std::setw(8) << std::right << rank.first << ": " << rank.second << "\n";
                if (0 == --topN)
                    break;
            }
        }
    }
    

    打印例如。

    1 unique results for '----' in 186ms
     1000000: ----
    3430 unique results for 'A--TG-DF----GR--' in 1156ms
        9251: A(----)--TG-DFGR--
        9226: (----)A--TG-DFGR--
        9191: A--T(----)G-DFGR--
        9150: A--TG-DFGR-(----)-
        9132: A--(----)TG-DFGR--
        9128: A--TG(----)-DFGR--
        9109: A--TG-D(----)FGR--
        9098: A--TG-DFG(----)R--
        9079: A--TG-DFGR(----)--
        9047: A-(----)-TG-DFGR--
    1 unique results for '' in 25ms
     1000000: 
    1 unique results for 'ATGDFGR' in 77ms
     1000000: ATGDFGR
    

答案 1 :(得分:1)

您可以预处理字符串以获取迭代器列表,该列表指向字符串中连续短划线的开头,然后从该列表中统一选择随机元素。

我将在此示例中使用以下标准库头文件(如果您连接以下代码块,则完整且有效):

#include <cstddef>
#include <iostream>
#include <random>
#include <stdexcept>
#include <string>
#include <vector>

首先,我们定义一个函数,它找到我们所说的迭代器列表。为此,我们使用std::string::find_first_ofstd::string::find_first_not_of来查找下一个序列之后的第一个字符的第一个字符的索引。这两个函数都使用索引而不是迭代器,因此我们必须将它们添加到cbegin()。该功能适用​​于任何角色,而不仅仅是破折号。

std::vector<std::string::const_iterator>
find_conscutive_sequences(const std::string& text, const char c)
{
  std::vector<std::string::const_iterator> positions {};
  std::size_t idx = 0UL;
  while (idx != std::string::npos && idx < text.length())
    {
      const auto first = text.find_first_of(c, idx);
      if (first == std::string::npos)
        break;
      positions.push_back(text.cbegin() + first);
      idx = text.find_first_not_of(c, first);
    }
  return positions;
}

接下来,我们定义一个函数,该函数使用上述函数的结果将迭代器返回到随机选择的破折号序列的开头。

我们将随机引擎作为参数传递,因此可以将其播种一次并反复使用。 C ++ 11中引入的random standard library非常强大,应尽可能优先于遗留rand函数。

如果给出positions的空向量,我们必须失败,因为没有我们可能选择的序列。

std::string::const_iterator
get_random_consecutive_sequence(const std::vector<std::string::const_iterator>& positions,
                                std::default_random_engine& prng)
{
  if (positions.empty())
    throw std::invalid_argument {"string does not contain any sequence"};
  std::uniform_int_distribution<std::size_t> rnddist {0UL, positions.size() - 1UL};
  const auto idx = rnddist(prng);
  return positions.at(idx);
}

最后,我定义了这个小辅助函数来标记所选序列。你的代码会在这里进行复制/移动/转移。

std::string
mark_sequence(const std::string& text,
              const std::string::const_iterator start)
{
  const auto c = *start;
  const std::size_t first = start - text.cbegin();
  std::size_t last = text.find_first_not_of(c, first);
  if (last == std::string::npos)
    last = text.length();
  std::string marked {};
  marked.reserve(text.length() + 2UL);
  marked += text.substr(0UL, first);
  marked += '(';
  marked += text.substr(first, last - first);
  marked += ')';
  marked += text.substr(last, text.length() - last);
  return marked;
}

可以像这样使用。

int
main()
{
  const std::string example {"--A--B-CD----E-F---"};
  std::random_device rnddev {};
  std::default_random_engine rndengine {rnddev()};
  const auto positions = find_conscutive_sequences(example, '-');
  for (int i = 0; i < 10; ++i)
    {
      const auto pos = get_random_consecutive_sequence(positions, rndengine);
      std::cout << mark_sequence(example, pos) << std::endl;
    }
}

可能的输出:

--A--B-CD(----)E-F---
--A--B(-)CD----E-F---
--A(--)B-CD----E-F---
--A(--)B-CD----E-F---
--A--B-CD(----)E-F---
--A--B-CD----E-F(---)
--A--B-CD----E-F(---)
(--)A--B-CD----E-F---
--A--B(-)CD----E-F---
(--)A--B-CD----E-F---

答案 2 :(得分:0)

string::find()optional second parameter:开始搜索的位置。因此,像s.find("-", rand() % L)这样的东西可以帮助你,L是(最后一个破折号的位置+ 1)。

答案 3 :(得分:0)

据我所知,所有的破折号块应该具有相同的被选中概率。因此,我们必须首先找到所有这些块开始的位置,然后在Random中选择其中一个位置。

如果我允许将Smalltalk用于伪代码,那么我首先会找到每个破折号块开始的索引:

dashPositionsOn: aString
    | indexes i n |
    indexes := OrderedCollection new.
    i := 1.
    n := aString size.
    [i <= n] whileTrue: [| char |
        char := aString at: i.
        char = $-
            ifTrue: [
                indexes add: i.
                [
                    i := i + 1.
                    i <= n and: [
                        char := aString at: i.
                        char = $-]] whileTrue]
            ifFalse: [i := i + 1]].
    ^indexes

现在我们可以随机选择其中一个索引:indexes atRandom

请注意,有很多(更好)方法可以在Smalltalk(以及其他语言)中实现此算法。