你会怎么写一个程序来找到一个单词列表中最短的pangram?

时间:2010-04-25 19:00:23

标签: algorithm pangram

给定一个包含字母az至少一次的单词列表,你会如何编写一个程序来找到由字符数(不计算空格)计算的最短pangram作为单词的组合? / p>

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

4 个答案:

答案 0 :(得分:7)

我会通过证明问题是NP难的,并通过检查启发式方法来解决类似的NP难问题。

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

这证明原始问题是NP难的,但不幸的是,我们需要减少某些NP难问题才能重用它(希望已知的)启发式算法。 Set-Cover有一个带对数近似的贪心启发式算法,但我不认为它适用于原始问题(Set-Cover问题的本质需要采用字母丰富,冗长的单词;它不是解决问题的方法)。

所以我会搜索相关的NP难题列表,并检查是否有兴趣。这就是我接近这个的方法。

答案 1 :(得分:2)

这是set cover problem(a.k.a。hitting set problem)的变体:

  

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

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

答案 2 :(得分:2)

这是一个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

答案 3 :(得分:1)

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

我写了一个一些成功的程序。我把这个问题更像是图搜索,而不是设置封面,并使用A *作为算法的起点。您可以浏览the code on github

最有帮助的事情是:

压缩状态空间

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

启发式

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

元音比率

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

Letter Commonality

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

共享3个字母组合

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

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