将数字从1到999,999,999的单词排序为字符串

时间:2009-09-29 21:40:15

标签: algorithm

有趣的programming puzzle

  

如果整数从1到999,999,999   被写成单词,排序   按字母顺序,连接,什么   是第510亿封信?

     

准确地说:如果整数来自1   以999,999,999表示   (省略空格,'和',和   标点符号 - 请参阅下面的注释格式),并排序   按字母顺序排列前六位   整数是

     
      
  • 8
  •   
  • 18
  •   
  • eighteenmillion
  •   
  • eighteenmillioneight
  •   
  • eighteenmillioneighteen
  •   
  • eighteenmillioneighteenthousand
  •   
     

,最后一个是

     
      
  • twothousandtwohundredtwo
  •   
     

然后从上到下阅读,向左   对,第28封信完成了   拼写整数   “eighteenmillion”。

     

第51亿封信也完成了   整数的拼写。哪一个,   什么是所有的总和   整数到那个点?

     

注意:例如,911,610,034是   书面   “ninehundredelevenmillionsixhundredtenthousandthirtyfour”;   写了500,000,000   “fivehundredmillion”;编写了1,709   “onethousandsevenhundrednine”。

我在编程博客'Occasionally Sane'上偶然发现了这一点,并且无法想到这样做的简洁方法,relevant post的作者说他最初的尝试是通过1.5GB的内存来进行的。 10分钟,他只赚了20,000,000(“二十亿”)。

任何人都能想到 想出 与小组分享一个新颖/聪明的方法吗?

20 个答案:

答案 0 :(得分:22)

修改:已解决!

您可以创建一个按排序顺序输出数字的生成器。有一些规则用于比较连接字符串,我认为我们大多数人都隐含地知道:

  • a< a + b,其中b为非null。
  • a + b&lt; a + c,其中b <角
  • a + b&lt; c + d,其中a < c,a不是c的子集。

如果您从前1000个数字的排序列表开始,您可以通过附加“千”或“百万”并连接另一组1000来轻松生成其余数据。

以下是Python中的完整代码:

import heapq

first_thousand=[('', 0), ('one', 1), ('two', 2), ('three', 3), ('four', 4),
                ('five', 5), ('six', 6), ('seven', 7), ('eight', 8),
                ('nine', 9), ('ten', 10), ('eleven', 11), ('twelve', 12),
                ('thirteen', 13), ('fourteen', 14), ('fifteen', 15),
                ('sixteen', 16), ('seventeen', 17), ('eighteen', 18),
                ('nineteen', 19)]
tens_name = (None, 'ten', 'twenty', 'thirty', 'forty', 'fifty', 'sixty',
             'seventy','eighty','ninety')
for number in range(20, 100):
    name = tens_name[number/10] + first_thousand[number%10][0]
    first_thousand.append((name, number))
for number in range(100, 1000):
    name = first_thousand[number/100][0] + 'hundred' + first_thousand[number%100][0]
    first_thousand.append((name, number))

first_thousand.sort()

def make_sequence(base_generator, suffix, multiplier):
    prefix_list = [(name+suffix, number*multiplier)
                   for name, number in first_thousand[1:]]
    prefix_list.sort()
    for prefix_name, base_number in prefix_list:
        for name, number in base_generator():
            yield prefix_name + name, base_number + number
    return

def thousand_sequence():
    for name, number in first_thousand:
        yield name, number
    return

def million_sequence():
    return heapq.merge(first_thousand,
                       make_sequence(thousand_sequence, 'thousand', 1000))

def billion_sequence():
    return heapq.merge(million_sequence(),
                       make_sequence(million_sequence, 'million', 1000000))

def solve(stopping_size = 51000000000):
    total_chars = 0
    total_sum = 0
    for name, number in billion_sequence():
        total_chars += len(name)
        total_sum += number
        if total_chars >= stopping_size:
            break
    return total_chars, total_sum, name, number

运行需要一段时间,大约一个小时。第五十五个字是最后一个字符,六百七十六百七十万五千五十五十五,并且这一点的整数之和为413,540,008,163,475,743。

答案 1 :(得分:15)

我会对前20个整数的名称和数十,数百和数千的名称进行排序,计算出每个数字的起始数,并从那里开始。

例如,前几个是[ eight, eighteen, eighthundred, eightmillion, eightthousand, eighty, eleven, ...

以“8”开头的数字是8.使用“eighthundred”,800-899,800,000-899,999,800,000,000-899,999,999。等等。

可以找到并总计0(由空字符串表示)到99的单词串联中的字母数;这可以乘以“千”= 8或“百万”= 7加入更高的范围。 800-899的值将是“eighthundred”长度的100倍加上0-99的长度。等等。

答案 2 :(得分:10)

This guy解决了Haskell编写的难题。显然Michael Borgwardt使用Trie找到解决方案是正确的。

答案 3 :(得分:9)

这些字符串将有很多很多共同的前缀 - trie的完美用例,这将大大减少内存使用量,也可能还有运行时间。

答案 4 :(得分:3)

这是我的python解决方案,可在几分之一秒内打印出正确的答案。我一般不是python程序员,所以对任何令人发指的代码样式错误表示道歉。

#!/usr/bin/env python

import sys

ONES=[
    "",         "one",      "two",      "three",        "four", 
    "five",     "six",      "seven",    "eight",        "nine",
    "ten",      "eleven",   "twelve",   "thirteen",     "fourteen",
    "fifteen",  "sixteen",  "seventeen","eighteen",     "nineteen",
]
TENS=[
    "zero",     "ten",      "twenty",   "thirty",       "forty",
    "fifty",    "sixty",    "seventy",  "eighty",       "ninety",
]

def to_s_h(i):
    if(i<20):
        return(ONES[i])
    return(TENS[i/10] + ONES[i%10]) 

def to_s_t(i):
    if(i<100):
        return(to_s_h(i))
    return(ONES[i/100] + "hundred" + to_s_h(i%100))

def to_s_m(i):
    if(i<1000):
        return(to_s_t(i))
    return(to_s_t(i/1000) + "thousand" + to_s_t(i%1000))

def to_s_b(i):
    if(i<1000000):
        return(to_s_m(i))
    return(to_s_m(i/1000000) + "million" + to_s_m(i%1000000))


def try_string(s,t):
    global letters_to_go,word_sum
    l=len(s)
    letters_to_go -= l
    word_sum += t
    if(letters_to_go == 0):
        print "solved: " + s
        print "sum is: " + str(word_sum)
        sys.exit(0)
    elif(letters_to_go < 0):
        print "failed: " + s + " " + str(letters_to_go)
        sys.exit(-1)

def solve(depth,prefix,prefix_num):
    global millions,thousands,ones,letters_to_go,onelen,thousandlen,word_sum
    src=[ millions,thousands,ones ][depth]
    for x in src:
        num=prefix + x[2]
        nn=prefix_num+x[1]
        try_string(num,nn)
        if(x[0] == 0):
            continue
        if(x[0] == 1):
            stl=(len(num) * 999) + onelen
            ss=(nn*999) + onesum
        else:
            stl=(len(num) * 999999) + thousandlen + onelen*999
            ss=(nn*999999) + thousandsum
        if(stl < letters_to_go):
            letters_to_go -= stl
            word_sum += ss
        else:
            solve(depth+1,num,nn)


ones=[]
thousands=[]
millions=[]
onelen=0
thousandlen=0
onesum=(999*1000)/2
thousandsum=(999999*1000000)/2



for x in range(1,1000):
    s=to_s_b(x)
    l=len(s)
    ones.append( (0,x,s) )
    onelen += l
    thousands.append( (0,x,s) )
    thousands.append( (1,x*1000,s + "thousand") )
    thousandlen += l + (l+len("thousand"))*1000
    millions.append( (0,x,s) )
    millions.append( (1,x*1000,s + "thousand") )
    millions.append( (2,x*1000000,s + "million") )

ones.sort(key=lambda x: x[2])
thousands.sort(key=lambda x: x[2])
millions.sort(key=lambda x: x[2])

letters_to_go=51000000000
word_sum=0

solve(0,"",0)

它的工作原理是预先计算1..999和1..999999中数字的长度,这样它就可以跳过整个子树,除非它知道答案位于其中的某个位置。

答案 5 :(得分:3)

(对此的第一次尝试是错误的,但我会放弃它,因为在解决问题的方式上看错会更有用,而不仅仅是最终答案。)

我首先生成0到999之间的字符串,然后将它们存储到名为thousandsStrings的数组中。 0元素为“”,“”表示下面列表中的空白。

thousandsString设置使用以下内容:

Units: "" one two three ... nine

Teens: ten eleven twelve ... nineteen

Tens: "" "" twenty thirty forty ... ninety

千万字符串设置是这样的:

thousandsString[0] = ""

for (i in 1..10)
   thousandsString[i] = Units[i]
end

for (i in 10..19)
   thousandsString[i] = Teens[i]
end

for (i in 20..99)
   thousandsString[i] = Tens[i/10] + Units[i%10]
end

for (i in 100..999)
   thousandsString[i] = Units[i/100] + "hundred" + thousandsString[i%100]
end

然后,我会按字母顺序对该数组进行排序。

然后,假设t1 t2 t3是从thousandsString中取出的字符串,所有字符串都具有

形式

T1

OR

t1 +百万+ t2 +千+ t3

OR

t1 +千+ t2

要以正确的顺序输出它们,我会处理单个字符串,然后是数百万个字符串,后跟字符串+数千个字符串。

foreach (t1 in thousandsStrings)

   if (t1 == "")
     continue;

   process(t1)

   foreach (t2 in thousandsStrings)
      foreach (t3 in thousandsStrings)
         process (t1 + "million" + t2 + "thousand" + t3)
      end
   end

   foreach (t2 in thousandsStrings)
       process (t1 + "thousand" + t2)
   end
end

其中,过程表示存储前一个总和长度,然后将新的字符串长度添加到总和中,如果新总和> =您的目标总和,则吐出结果,并可能返回或中断循环,无论什么让你开心

=============================================== ======================

第二次尝试,其他答案是正确的,你需要使用3k字符串而不是1k字符串作为基础。

从上面开始使用thousandsString,但是将空白“”删除为零。这留下了999个元素,并将其称为uStr(单位字符串)。

再创建两组:

tStr = the set of all uStr + "thousand"

mStr = the set of all uStr + "million"

现在再创建两个设置联合:

mtuStr = mStr union tStr union uStr

tuStr = tStr union uStr

订购uStr,tuStr,mtuStr

现在这里的循环和逻辑与以前有点不同。

foreach (s1 in mtuStr)
   process(s1)

   // If this is a millions or thousands string, add the extra strings that can
   // go after the millions or thousands parts.

   if (s1.contains("million"))
      foreach (s2 in tuStr)
         process (s1+s2)          

         if (s2.contains("thousand"))
            foreach (s3 in uStr)
               process (s1+s2+s3)
            end
         end
      end
   end

   if (s1.contains("thousand"))
      foreach (s2 in uStr)
         process (s1+s2)
      end
   end
end

答案 6 :(得分:2)

我做了什么: 1)迭代1 - 999并为每个单词生成单词。 正如我们生成: 2)创建3个数据结构,其中每个节点都有一个指向子节点的指针,每个节点都有一个字符值,以及一个指向兄弟姐妹的指针。 (事实上​​,这是一棵二叉树,但是我们不想这么想它 - 对我而言,更容易将其概念化为一个兄弟姐妹的列表,其中有一些儿童名单悬而未决,但是如果你想到这一点{画一张照片你会意识到它实际上是一棵二叉树)。 这3个数据结构并行创建如下:   a)第一个生成单词(即1-999按字母顺序排序)   b)第一个+所有值中的所有值,附加“千”(即1-999和1,000 - 999,000(步骤1000)(1998年总值)   c)B +中的所有值以及附加百万的所有值(总共2997个值) 3)对于(b)中的每个叶节点,将一个Child添加为(a)。对于(c)中的每个叶节点,将子节点添加为(b)。 4)遍历树,计算我们通过的字符数和停止在51亿字。

注意:这不会对这些值求和(我最初做的时候没有读过那个位),并且运行时间超过3分钟(通常使用c ++约为192秒)。 注2 :(如果不是很明显),只存储了5,994个值,但它们的存储方式是树中有十亿个路径

我在大约一年或两年前做过这件事,当我偶然发现它时,已经意识到有很多优化(最耗时的一点是穿越树 - 通过一条长路)。我认为有一些优化可以显着改善这种方法,但除了稍微优化树中的冗余节点之外,我永远不会费心去做,所以它们存储字符串而不是字符

我看到人们在网上声称他们已经在不到5秒的时间内解决了它......

答案 7 :(得分:1)

是的,我再次,但是一种完全不同的方法。

简单地说,不是存储“onethousandeleventyseven”字样,而是在比较时编写要使用的排序。

Crude java POC:

public class BillionsBillions implements Comparator {
    public int compare(Object a, Object b) {
        String s1 = (String)a; // "1234";
        String s2 = (String)b; // "1235";

        int n1 = Integer.valueOf(s1);
        int n2 = Integer.valueOf(s2);

        String w1 = numberToWords(n1);
        String w2 = numberToWords(n2);

        return w1.compare(w2);
    }

    public static void main(String args[]) {
        long numbers[] = new long[1000000000]; // Bring your 64 bit JVMs

        for(int i = 0; i < 1000000000; i++) {
            numbers[i] = i;
        }

        Arrays.sort(numbers, 0, numbers.length, new BillionsBillions());

        long total = 0;

        for(int i : numbers) {
            String l = numberToWords(i);
            long offset = total + l - 51000000000;

            if (offset >= 0) {
                String c = l.substring(l - offset, l - offset + 1);
                System.out.println(c);
                break;
            }
         }
    }
}
“numberToWords”留给读者练习。

答案 8 :(得分:1)

奇怪但有趣的想法。

建立一个稀疏的数字长度列表,从0到9,然后是10-90乘几十,然后是100,1000等,到十亿,索引是存储长度的整数部分的值。 / p>

使用表格编写一个函数来计算数字作为字符串长度。 (将数字分成它的部分,并查看aprts的长度,从不动作创建字符串。)

然后你只是在遍历数字时进行数学计算,从中计算长度 表后来总结你的总和。

用和,以及最终整数的值,计算出拼写的整数,以及volia,你已经完成了。

答案 9 :(得分:0)

代码获胜......

#!/bin/bash
n=0
while [ $n -lt 1000000000 ]; do
   number -l $n | sed -e 's/[^a-z]//g'
   let n=n+1
done | sort > /tmp/bignumbers
awk '
BEGIN {
    total = 0;
}
{
    l = length($0); 
    offset = total + l - 51000000000;
    print total " " offset
    if (offset >= 0) {
        c = substr($0, l - offset, 1);
        print c;
        exit;
    }
    total = total + l;
}' /tmp/bignumbers

测试范围小得多;-)。需要大量的磁盘空间,压缩的文件系统,嗯,有价值,但没有那么多内存。

Sort也有压缩工作文件的选项,你可以投入gzip来直接压缩数据。

不是最简单的解决方案。

但确实有效。

答案 10 :(得分:0)

你有10亿个数字和510亿个字符 - 很有可能这是一个技巧问题,因为每个数字平均有51个字符。总结所有数字的转换,看看它是否总计达到510亿。

编辑:它最多可添加70,305,000,000个字符,因此这是错误的答案。

答案 11 :(得分:0)

计算1-999的长度,并将0的长度包括为0。

所以现在你有一个0-999的数组,即uint32 sizes999 [1000]; (不打算详细介绍这个) 还需要一个最后一千个字母的数组last_letters [1000] (再次没有深入了解产生这一点的细节,因为它甚至更容易甚至数百甚至数十年,除了10,其他n个循环虽然最后一个 e 通过nin e 零是无关紧要的)

uint32 sizes999[1000];

uint64 totallen = 0;
strlen_million = strlen("million");
strlen_thousand = strlen("thousand");
for (uint32 i = 0; i<1000;++i){
    for (uint32 j = 0; j<1000;++j){
        for (uint32 j = 0; j<1000;++j){ 
           total_len += sizes999[i]+strlen_million + 
                        sizes999[j]+strlen_thousand +
                        sizes999[k];
           if totallen == 51000000000 goto done;
           ASSERT(totallen <51000000000);//he claimed  51000000000 was not intermediate
        }
    }
}
done:

//现在使用i j k通过last_letters999获取最后一个字母

//将i,j,k视为数字基数1000

//如果k = 0&amp; j == 0那么字母是n millio n

//如果只有k = 0那么这封信就是d thousan d

//其他方面使用last_letters数组,因为

//单位数字基数1000,即k,不为零

//对于数字i的总和,i,j,k是数字基数1000的数字,所以

n = i*1000000 + j*1000 + k;

//代表数字并使用

sum = n*(n+1)/2;

如果您需要为51000000000以外的数字执行此操作,那么还要计算sums_sizes999并以自然方式使用它。

总记忆:0(1000);

总时间:0(n)其中n是数字

答案 12 :(得分:0)

这就是我要做的事情:

  1. 创建一个包含2,997个字符串的数组:“one”到“ninehundredninetynine”,“onethousand”到“ninehundredninetyninethousand”,“onemillion”到“ninehundredninetyninemillion”。
  2. 关于每个字符串存储以下内容:length(当然可以计算),字符串表示的整数值,以及表示“是”,“数千”还是“数百万”的枚举。
  3. 按字母顺序排序2,997个字符串。
  4. 创建此数组后,可以根据以下观察结果按字母顺序查找所有999,999,999个字符串:

    1. 没有什么可以遵循“一些”字符串
    2. 无论是什么,或“一个”字符串都可以跟随“千”字符串
    3. 无论是什么,“一”字符串,“千”字符串,或“千”字符串,然后是“一”字符串,都可以跟随“百万”字符串。
    4. 构造单词主要涉及基于这些2,997个标记创建一到三个字母的“单词”,确保标记的顺序根据上述规则生成有效数字。给定一个特定的“单词”,下一个“单词”就像这样找到:

      1. 如果可能的话,首先按字母顺序添加令牌,以延长“字”。
      2. 如果无法做到这一点,请尽可能按字母顺序将最右边的标记前进到下一个标记。
      3. 如果无法做到这一点,则删除最右边的令牌,并在可能的情况下按字母顺序将第二个最右边的令牌推进到下一个令牌。
      4. 如果这也不可能,那就完成了。
      5. 在每一步中,您只需保留两个运行总计即可计算字符串的总长度和数字的总和。

答案 13 :(得分:0)

重要的是要注意,如果迭代所有1000亿个可能的数字,会有很多重叠和重复计算。重要的是要认识到以“8”开头的字符串数量与以“nin”或“7”或“6”等开头的数字相同......

对我而言,这需要一个动态编程解决方案,其中计算数十,数百,数千等字符串的数量并存储在某种类型的查找表中。当然,对于一对十一,二对十二等特殊情况

如果我能获得快速运行的解决方案,我会更新。

答案 14 :(得分:0)

WRONG !!!!!!!!!我读错了。我认为这意味着“字母顺序最后一个数字的最后一个字母是什么”

出了什么问题:

public class Nums {

// if overflows happen, switch to an BigDecimal or something
// with arbitrary precision
public static void main(String[] args) {
    System.out.println("last letter: " + lastLetter(1L, 51000000L);
    System.out.println("sum: " + sum(1L, 51000000L);
}    

static char lastLetter(long start, long end) {
   String last = toWord(start);
   for(long i = start; i < end; i++)
      String current = toWord(i);
      if(current.compareTo(last) > 1)
         last = current;

   return last.charAt(last.length()-1);
}

static String toWord(long num) {
   // should be relatively easy, but for now ...
   return "one";
}

static long sum(long first, long n) {
   return (n * first + n*n) / 2;
}
}

实际上没有尝试过:/ LOL

答案 15 :(得分:0)

老实说,我会让像SQL Server或Oracle这样的RDBMS为我工作。

  1. 将十亿字符串插入索引表。
  2. 计算字符串长度列。
  3. 一次用SUM开始拉出前X条记录,直到我达到510亿。
  4. 可能需要花费一段时间才能打开服务器,因为它需要执行大量的磁盘IO,但总的来说,我认为我能找到一个比编写程序的人更快的答案。

    有时只需完成它就是客户真正想要的东西,并且可以更少关注您使用的花哨的设计模式或数据结构。

答案 16 :(得分:0)

问题是有效的数据存储而非字符串操作。创建一个枚举来表示单词。单词应该按排序顺序出现,以便在排序时,它是一个简单的比较。现在生成列表并排序。使用这样一个事实,即您知道每个单词与枚举结合多长时间,以便加起来所需的字符。

答案 17 :(得分:0)

所有字符串都将从一个,十个,两个,二十,三个,三十个,四个等开始,所以我首先要弄清楚每个桶中有多少个字符串。那你至少应该知道你需要仔细观察哪个桶。

然后我会根据可能的前缀进一步查看细分桶。例如,在九百之内,您将拥有所有相同的桶,只需要从900开始的数字。

答案 18 :(得分:0)

我在2008年的某个时候用Java解决了这个问题,作为在ITA Software工作的应用程序的一部分。

代码很长,现在已经三年了,我看起来有点恐怖......所以我不会发布它。

但是我会在应用程序中包含的一些注释中发布引用。

  

这个难题的问题当然是大小。天真的方法是按字数顺序对列表进行排序,然后迭代计算字符和求和的排序列表。对于大小为999,999,999的列表,这当然需要相当长的时间,并且可能无法在内存中进行排序。

     

但是在排序中有自然模式允许快捷方式。

     

在以“百万”结尾的任何条目(比如数字为X)之后,将立即以相同文本开头的999,999个条目,表示来自X +1的所有数字   到X + 10 ^ 6 -1。

     

所有这些数字的总和可以通过经典公式(“算术系列”)计算,并且字符计数可以通过基于前缀(上面的X)和一次计算的字符的类似简单公式来计算计算从1到999,999的数字。两者都只依赖于范围底部数字的“数百万”部分。因此,如果整个范围的字符数将使整个计数保持在搜索目标之下,则无需遍历各个条目。

     

类似的快捷方式适用于“千”,实际上可以应用于“百”或“十亿”,虽然我没有在数百级别的快捷方式,数十亿水平超出这个问题的范围。

     

为了应用这些快捷方式,我的代码会创建并排序表示数字的2997个对象的列表:

     

1到999步进1     1000到999000步进1000     1000000到999000000步进1000000

     

代码遍历此列表,累积总和和字符数,根据需要递归创建,排序和遍历类似但更小的列表。

     

只需在结束时进行显式计数和添加。

我没有得到这份工作,但后来将这些代码用作另一份工作的“代码示例”,我确实得到了。

使用这些技术的Java代码可以在大约8秒内跳过大部分显式计数和添加运行。

答案 19 :(得分:0)

您需要将整个字符串保存在内存中吗?

如果没有,只需保存到目前为止已添加的字符数。对于每次迭代,您检查下一个数字的文本表示的长度。如果它超过了您要查找的第n个字母,则该字母必须在该字符串中,因此请通过索引提取,打印并停止执行。否则,将字符串长度添加到字符数,然后移动到下一个数字。