【问题标题】:How would you write a program to find the shortest pangram in a list of words?你将如何编写一个程序来在单词列表中找到最短的 pangram?
【发布时间】:2010-04-25 19:00:22
【问题描述】:

给定一个包含至少一次 a-z 字母的单词列表,你将如何编写一个程序来找到最短的 pangram(按字符数(不包括空格)计算)作为单词的组合?

由于我不确定是否存在简短的答案,所以这不是代码高尔夫,而只是讨论您将如何解决这个问题。但是,如果您认为自己可以编写一个短程序来完成此操作,那么请继续,这可能会变成代码高尔夫 :)

【问题讨论】:

  • 不是所有的pangrams都具有相同数量的字符(不包括空格)?此外,讨论需要是社区 wiki
  • @SilentGhost:不,pangram 不一定每个字母只有一次。
  • 我会给任何可以从头开始生成语法正确的 pangram 句子的人 +1...你有字典或语言模型吗?
  • +1 打高尔夫球是个阳光明媚的好日子
  • @SilenGhost,这个社区维基是什么东西?我是这个社区的新手,所以我不知道。 @Stephen,我的意思是给您一个单词列表,然后您尝试从该单词列表中生成最短的pangram。它不必在语法上是正确的,因为那太难了哈哈。

标签: algorithm pangram


【解决方案1】:

我会通过证明问题是 NP-hard 来解决这个问题,并通过启发式检查看起来相似的 NP-hard 问题。

我们可以将Set Cover problem 简化为我们的。 Set Cover 的不同之处在于,不是使用的字母数量被最小化,而是使用的单词数量被最小化。假设我们要解决 Set Cover 问题,给定 N 个单词,每个单词的长度都小于 M。让我们通过克隆给定的集合来构建另一组单词,但是将 N*M 个非英文字母连接到每个单词上,比如 Ж。如果我们可以构建一个需要最少符号的 pangram(在 a,b,c...x,y,z,ж 字母表上),如果我们删除所有 Ж 字母,那将是一个包含最少单词的 pangram。

这证明了原始问题是 NP-hard,但是不幸的是,我们需要将一些 NP-hard 问题简化,以重用其(希望已经知道)启发式。 Set-Cover 具有对数近似的贪婪启发式算法,但我认为它不适用于原始问题(Set-Cover 问题的性质要求采用字母丰富的长词;这不是解决我们问题的方法)。

所以我会搜索相关的 NP 难题列表,并检查是否有感兴趣的内容。我就是这样处理这个的。

【讨论】:

  • 在提出这个问题之前,我对 Set Cover 问题一无所知。但无论如何,即使它是 NP 难的,因为宇宙只有 26 个字母,如果我们在字典中的单词数量上加上一个上限,就有可能解决这个问题。当然,最简单的方法是蛮力贪婪递归,但肯定有其他选择,比如计算一个字母重复的总次数并使用它或其他东西。
  • 26 个字母将您限制为“仅”6700 万字。 :-) 这几乎不意味着您可以忽略 NP 完整性。但这放弃了对启发式的要求:它们可能比最快的要慢。很抱歉,我现在不能提出一些建议;也许,其他人会。
  • 我从来没有说过它不会是非 NP 完全的,但是绝对可以在这些限制下制定算法(其中包括对所用字典中单词数量的实际上限)
  • 现实的限制是100-20万字,英文字典的大小。顺便说一句,您可以尝试以下启发式方法:迭代选择比率最高的单词(number of unique letters still not persisting in a pangram)/(length of the word)
  • 除了启发式之外,最好根据每个字母在输入单词集中出现的频率为每个字母分配一个值,以决定选择哪些单词而不是其他单词。但是,是的,这几乎决定了这没有决定性的解决方案,所以我将接受这个答案。
【解决方案2】:

这是set cover problem(又名hitting set problem)的变体:

作为输入,您将获得几组。它们可能有一些共同点。您必须选择最少数量的这些集合,以便您选择的集合包含输入中任何集合中包含的所有元素。 [...] 在 1972 年被证明是 NP 完全的 [,] 并且集合覆盖的优化版本是 NP 难的。

这是一个变体,因为我们正在寻找最少的字母数,而不是最少的单词数。但我认为它仍然是 NP-hard,这意味着你将无法比蛮力做得更好。

【讨论】:

    【解决方案3】:

    这是一个O(n) 算法当你有一个字符串而不是一个单词列表作为输入时的不同问题。。这是我的疏忽,但将解决方案留在这里,因为我不想删除它:)

    由于我们只对字符感兴趣,这让问题变得容易多了。维护每个字符 [a-z] 到其在字符串中的位置的映射。仅此地图就足以确定我们是否有一个 pangram 以及它的长度。

    1. Initialize a map of all alphabets to null
    2. Initialize shortest_pangram to { length: ∞, value: undefined }
    3. Loop through each "character" in given string
      3.1 Update the value of map[character] to current string index
      3.2 If we have a pangram, and its the shortest so far, record its length/value
    4. shortest_pangram should have our result
    

    我们创建的地图足以确定我们是否有一个 pangram - 如果我们的地图中的所有值都不为空,我们就有一个 pangram。

    要找到当前 pangram 的长度,请从我们地图中的最小值中减去最大值。请记住,在找到长度之前,我们必须检查它是否是一个 pangram。

    这是一个简单的 Ruby 中未优化的实现:

    class Pangram
      def initialize(string)
        @input = string.downcase.split('')
        @map = {}
        ('a'..'z').each { |c| @map[c] = nil }
        infinity = 1.0/0.0
        @best = { :length => infinity, :string => nil }
      end
    
      def shortest
        @input.each_with_index do |c, index|
          @map[c] = index if @map.key?(c)
          if pangram? and length < @best[:length]
            @best[:length] = length
            @best[:string] = value
          end
        end
        @best
      end
    
      def pangram?
        @map.values.all? { |value| !value.nil? }
      end
    
      def length
        @map.values.max - @map.values.min
      end
    
      def value
        @input[@map.values.min..@map.values.max].join('')
      end
    end
    

    要使用,实例化类并将整个字符串传递给它。调用 .shortest 查找最短 pangram 的长度和匹配的子串。

    pangram = Pangram.new("..")
    print pangram.shortest
    

    【讨论】:

    • 感谢您的尝试,即使您完全误解了这个问题:)
    【解决方案4】:

    这是一个老问题,所以您可能已经找到了一些您已经喜欢的启发式方法。我在探索生成完美 pangrams 的方法时遇到了这个问题,这将是最少的字符(因为它们只允许使用字母表中的每个字母一次)。无论如何,对于像我这样的未来发现者:

    我编写了一个一些成功的程序。我将这个问题更像是图形搜索而不是设置覆盖,并使用 A* 作为算法的起点。你可以探索the code on github

    帮助最大的事情是:

    压缩状态空间

    我拿了一本字典,将所有单词转换成它们的排序字母集。例如,这种方式“BAD”和“DAB”都存储为“ABD”。我使用的压缩字典将大约 250,000 个单词缩减为大约 31,000 个独特的字母组合,这是一个巨大的胜利。

    启发式

    正如其他地方所提到的,这是 NP 很难,所以我开始使用启发式算法。我目前使用的三个是:

    元音比例

    当我在选择一个单词后检查剩余的字母时,我计算#vowels / #unusedLetters。这样做的动机很简单 - 剩余的元音越多,我就越有可能使用这些字母选择单词。

    字母共性

    当我阅读初始单词集时,我为字母表中的每个字母创建一个字典,并计算每个字母在所有单词中出现的次数。我使用这本字典来选择其余字母具有更常见字母的节点。 (我相信 OP 在其中一个 cmets 中提到了这个)

    共享 3 字母组合

    这类似于字母共性启发式。同样,在处理初始单词集时,我创建了一个字典,其中包含可以用该单词组成的所有 3 个字母组合。例如,字母集 ABC 只有一个有效的组合,但 ABCD 有 [ABC, ABD, BCD]。请记住,我只关心压缩初始单词集后的排序字母集。

    所以最后,一定要喜欢字母共性度量,我有一个字典,映射所有 26 个选择 3 个可能的字母集,映射到这些组合在我的单词集中出现的次数。然后我使用它来更喜欢搜索剩余字母具有更多有效 3 字母组合的节点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多