按字典顺序从范围中获取下一个数字(不包含所有字符串的容器)

时间:2013-12-20 12:52:38

标签: c++ algorithm sorting language-agnostic iterator

  

你是如何设计一个函数,在每个调用中返回字符串表示的字典顺序中指定数值范围中的下一个值 ...

示例:范围8..203 - > 10,100..109,11,110..119,12,120..129,13,130..139,...,19,190..199,20,200..203,30..99

约束:索引0 ..~INT_MAX,固定空间,O(范围长度)性能,最好是“懒惰”,所以如果你停止迭代中途你没有浪费处理工作。请不要发布暴力“解决方案”以数字方式迭代,同时生成随后排序的字符串。

实用程序:如果您生成的数据最终需要按字典顺序显示或处理,则词典系列承诺根据需要生成延迟,减少内存需求并消除排序。

背景:今天回答this question时,我的解决方案按数字顺序(即8,9,10,11,12)输出了输出,而不是字典顺序(10,11,12) ,8,8)如问题所示。我想象编写或找到解决方案会很容易,但是我的Google-foo让我失望了,它比我想象的要复杂,所以我想我会在这里收集/贡献....

(标记为C ++,因为它是我的主要语言,我个人对C ++解决方案特别感兴趣,但欢迎任何事情)

有人投票决定关闭这个,因为我要么没有证明对要解决的问题的最小理解(嗯!?!; - P),要么尝试解决方案。 我的解决方案是作为答案发布的,因为我很高兴在Stack Overflow智慧的残酷风中对其进行评论和监管.... O_o

4 个答案:

答案 0 :(得分:3)

这实际上非常简单。首先观察一下:

定理:如果两个数字xy使得x < y在系列中并且这些数字具有相同的位数,那么{{ 1}}出现在x之前。

证明:让我们将y的数字视为x,将xn..x0的数字视为y。让我们取这两个不同的最左边的数字,假设是在索引yn...y0。因此,我们有:

i

因为y = yn...yiy(i-1)...y0 x = yn...yix(i-1)...x0 n的所有数字在两个数字中都相同。如果i,则以数学方式:

x < y

按字典顺序,如果数字x(i-1) < y(i-1) 小于数字x(i-1),则y(i-1)会出现在x之前。


这个定理意味着在您指定的y范围内,您的数字位数不同,但具有相同位数的数字按其数学顺序排列。

在此基础上,这是一个简单的算法。首先,假设[a, b]a个数字,mb个数字(n

n >= m

注意:

  • 在步骤2中,您将插入每个具有相同位数的数字序列的起始编号。
  • 要更改为在每次调用时返回数字的函数,应更改步骤3.1以存储算法的状态并在下次调用时继续。非常标准。
  • 步骤3.2是利用上述定理的部分,只保留堆中数学顺序的下一个数字。

假设1. create a heap with lexicographical order 2. initially, insert `a` and `10^i` for i in [n + 1, m] 3. while the heap is not exhausted 3.1. remove and yield the top of the heap (`next`) as next result 3.2. if `next + 1` is still in range `[a, b]` (and doesn't increase in digits), insert it in heap ,此算法使用的额外空间为N = b - a,时间复杂度为O(log N)

答案 1 :(得分:2)

这是我在Python中的尝试:

import math

#iterates through all numbers between start and end, that start with `cur`'s digits
def lex(start, end, cur=0):
    if cur > end:
        return
    if cur >= start:
        yield cur
    for i in range(0,10):
        #add 0-9 to the right of the current number
        next_cur = cur * 10 + i
        if next_cur == 0: 
            #we already yielded 0, no need to do it again
            continue
        for ret in lex(start, end, next_cur):
            yield ret

print list(lex(8, 203))

结果:

[10, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 11, 110, 111, 112, 113, 
114, 115, 116, 117, 118, 119, 12, 120, 121, 122, 123, 124, 125, 126, 127, 128, 
129, 13, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 14, 140, 141, 142, 
143, 144, 145, 146, 147, 148, 149, 15, 150, 151, 152, 153, 154, 155, 156, 157, 
158, 159, 16, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 17, 170, 171, 
172, 173, 174, 175, 176, 177, 178, 179, 18, 180, 181, 182, 183, 184, 185, 186, 
187, 188, 189, 19, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 20, 200, 
201, 202, 203, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 
37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 
57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 
77, 78, 79, 8, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 9, 90, 91, 92, 93, 94, 95, 
96, 97, 98, 99]

这使用O(log(end))堆栈空间,它由INT_MAX限制,因此它不会比典型的16位int的五次调用更深入。它在O(结束)时间运行,因为它必须迭代小于start的数字才能开始产生有效数字。如果startend很大并且靠近在一起,这可能比O(end-start)差得多。

在我的机器上迭代lex(0, 1000000)需要大约六秒钟,因此它似乎比Tony的方法慢,但比Shahbaz的速度快。当然,由于我使用的是另一种语言,因此进行直接比较具有挑战性。

答案 2 :(得分:1)

这有点乱,所以我很想知道别人怎么解决它。在增量运算符中明确处理了很多边缘情况!

范围lowhigh

  • 0之后是1
  • 短于high的数字后面跟着0个附加版本(例如12-> 120)
  • 以{0}结尾的数字以0-8结尾后跟下一个整数
  • high的位数与low一样多时,您将在high之后完成(返回哨兵high
    • 否则,您使用的数字high + 1只比999...
    • 少一位
  • 以9(s)结尾的其他数字在尾随9s之前的部分增加,但如果这导致尾随0,则会删除它们,前提是该数字仍然超过high

low

演出编号

在标准的英特尔机器/单线程,MS编译器-O2下,我可以在一秒钟内从0到10亿。

同样的机器/线束运行我对Shahbaz解决方案的尝试 - 下面 - 需要3.5秒才能达到100,000。也许template <typename T> std::string str(const T& t) { std::ostringstream oss; oss << t; return oss.str(); } template <typename T> class Lex_Counter { public: typedef T value_type; Lex_Counter(T from, T to, T first = -1) : from_(from), to_(to), min_size_(str(from).size()), max_size_(str(to).size()), n_(first != -1 ? first : get_first()), max_unit_(pow(10, max_size_ - 1)), min_unit_(pow(10, min_size_ - 1)) { } operator T() { return n_; } T& operator++() { if (n_ == 0) return n_ = 1; if (n_ < max_unit_ && n_ * 10 <= to_) return n_ = n_ * 10; // e.g. 10 -> 100, 89 -> 890 if (n_ % 10 < 9 && n_ + 1 <= to_) return ++n_; // e.g. 108 -> 109 if (min_size_ == max_size_ ? n_ == to_ : (n_ == max_unit_ - 1 && to_ < 10 * max_unit_ - 10 || // 99/989 n_ == to_ && to_ >= 10 * max_unit_ - 10)) // eg. 993 return n_ = to_ + 1; // increment the right-most non-9 digit // note: all-9s case handled above (n_ == max_unit_ - 1 etc.) // e.g. 109 -> 11, 19 -> 2, 239999->24, 2999->3 // comments below explain 230099 -> 230100 // search from the right until we have exactly non-9 digit for (int k = 100; ; k *= 10) if (n_ % k != k - 1) { int l = k / 10; // n_ 230099, k 1000, l 100 int r = ((n_ / l) + 1) * l; // 230100 if (r > to_ && r / 10 < from_) return n_ = from_; // e.g. from_ 8, r 20... while (r / 10 >= from_ && r % 10 == 0) r /= 10; // e.g. 230100 -> 2301 return n_ = r <= from_ ? from_ : r; } assert(false); } private: T get_first() const { if (min_size_ == max_size_ || from_ / min_unit_ < 2 && from_ % min_unit_ == 0) return from_; // can "fall" from e.g. 321 to 1000 return min_unit_ * 10; } T pow(T n, int exp) { return exp == 0 ? 1 : exp == 1 ? n : 10 * pow(n, exp - 1); } T from_, to_; size_t min_size_, max_size_; T n_; T max_unit_, min_unit_; }; 不是一个好的堆/堆替代品,或者有更好的方法来使用它?任何优化建议都欢迎。

std::set

Perf代码供参考......

template <typename T>
struct Shahbaz
{
    std::set<std::string> s;
    Shahbaz(T from, T to)
      : to_(to)
    {
        s.insert(str(from));
        for (int n = 10; n < to_; n *= 10)
            if (n > from) s.insert(str(n));
        n_ = atoi(s.begin()->c_str());
    }

    operator T() const { return n_; }

    Shahbaz& operator++()
    {
        if (s.empty())
            n_ = to_ + 1;
        else
        {
            s.erase(s.begin());
            if (n_ + 1 <= to_)
            {
                s.insert(str(n_ + 1));
                n_ = atoi(s.begin()->c_str());
            }
        }
        return *this;
    }

  private:
    T n_, to_;
};

答案 3 :(得分:1)

一些Java代码(从中导出C ++代码应该是微不足道的),与Kevin的Python解决方案非常相似:

public static void generateLexicographical(int lower, int upper)
{
   for (int i = 1; i < 10; i++)
      generateLexicographical(lower, upper, i);
}

private static void generateLexicographical(int lower, int upper, int current)
{
   if (lower <= current && current <= upper)
      System.out.println(current);

   if (current > upper)
      return;

   for (int i = 0; i < 10; i++)
      generateLexicographical(lower, upper, 10*current + i);
}

public static void main(String[] args)
{
   generateLexicographical(11, 1001);
}

if语句的顺序并不重要,一个可以成为另一个的else,但是以任何方式奇怪地改变它们会使它花费大约20%的时间。

这只是从1到10的每个数字开始,然后递归地将每个可能的数字从0到10附加到该数字,直到我们得到一个大于上限的数字。

它同样使用O(log upper)空格(每个数字需要一个堆栈框架)和O(upper)时间(我们从1转到upper)。

I / O显然是这里最耗时的部分。如果将其删除并仅通过递增变量来替换,generateLexicographical(0, 100_000_000);大约需要4秒,但绝不会从适当的基准测试中获取。