在一组字符串中查找最长的公共起始子字符串

mis*_*lav 39 javascript ruby python haskell longest-prefix

对于一个相对微不足道的问题,提出最优雅的JavaScript,Ruby或其他解决方案是一项挑战.

此问题是最长公共子字符串问题的更具体情况.我只需要在数组中找到最长的公共起始子字符串.这大大简化了问题.

例如,最长的子串[interspecies, interstelar, interstate]是"inters".但是,我不需要找到"ific" [specifics, terrific].

我已经通过快速编写JavaScript解决方案来解决这个问题,作为我关于类似shell的选项卡完成的答案的一部分(这里是测试页面).这是解决方案,略有调整:

function common_substring(data) {
  var i, ch, memo, idx = 0
  do {
    memo = null
    for (i=0; i < data.length; i++) {
      ch = data[i].charAt(idx)
      if (!ch) break
      if (!memo) memo = ch
      else if (ch != memo) break
    }
  } while (i == data.length && idx < data.length && ++idx)

  return (data[0] || '').slice(0, idx)
}
Run Code Online (Sandbox Code Playgroud)

代码在此Gist中提供,以及Ruby中的类似解决方案.您可以将gist克隆为git repo来试用它:

$ git clone git://gist.github.com/257891.git substring-challenge
Run Code Online (Sandbox Code Playgroud)

我对这些解决方案不太满意.我觉得他们可能会以更优雅和更少的执行复杂性来解决 - 这就是我发布这个挑战的原因.

我将接受作为答案的解决方案,我发现最优雅或简洁.这是一个疯狂的Ruby hack我想出来 - &在String上定义运算符:

# works with Ruby 1.8.7 and above
class String
  def &(other)
    difference = other.to_str.each_char.with_index.find { |ch, idx|
      self[idx].nil? or ch != self[idx].chr
    }
    difference ? self[0, difference.last] : self
  end
end

class Array
  def common_substring
    self.inject(nil) { |memo, str| memo.nil? ? str : memo & str }.to_s
  end
end
Run Code Online (Sandbox Code Playgroud)

JavaScript或Ruby中的解决方案是首选,但只要您解释正在发生的事情,您就可以在其他语言中展示出聪明的解决方案.请只提供标准库中的代码.

更新:我最喜欢的解决方案

我选择了JavaScript的分拣解决方案通过肯纳贝克为"答案",因为它让我觉得既意外和天才.如果我们忽略实际排序的复杂性(让我们想象它是由语言实现无限优化的),解决方案的复杂性只是比较两个字符串.

其他好方案:

感谢您的参与!从评论中可以看出,我学到了很多东西(甚至是Ruby).

ken*_*bec 55

这是一个品味问题,但这是一个简单的javascript版本:它对数组进行排序,然后只查看第一个和最后一个项目.

//数组中最长的公共起始子字符串

function sharedStart(array){
    var A= array.concat().sort(), 
    a1= A[0], a2= A[A.length-1], L= a1.length, i= 0;
    while(i<L && a1.charAt(i)=== a2.charAt(i)) i++;
    return a1.substring(0, i);
}
Run Code Online (Sandbox Code Playgroud)

DEMOS

sharedStart(['interspecies', 'interstelar', 'interstate'])  //=> 'inters'
sharedStart(['throne', 'throne'])                           //=> 'throne'
sharedStart(['throne', 'dungeon'])                          //=> ''
sharedStart(['cheese'])                                     //=> 'cheese'
sharedStart([])                                             //=> ''
sharedStart(['prefix', 'suffix'])                           //=> ''
Run Code Online (Sandbox Code Playgroud)

  • 你不必对它进行排序.对?找到最小的和最大的一个并比较两者. (5认同)
  • 请注意,排序是在O(n·log n)中完成的. (2认同)
  • 如果数组有一个项目或全部重复,则不起作用. (2认同)

Rob*_*let 38

在Python中:

>>> from os.path import commonprefix
>>> commonprefix('interspecies interstelar interstate'.split())
'inters'
Run Code Online (Sandbox Code Playgroud)

  • 不敢相信.Python在stdlib中有这个吗? (2认同)

ASh*_*lly 9

Ruby单行:

l=strings.inject{|l,s| l=l.chop while l!=s[0...l.length];l}
Run Code Online (Sandbox Code Playgroud)

  • 它可能需要比其他解决方案更多的迭代,但我喜欢它.这是我认为优雅的. (3认同)
  • 对于不在结果中的每个字符,这会遍历每个字符串一次.不是很有效率. (2认同)

Sva*_*nte 9

您只需要遍历所有字符串直到它们不同,然后将子字符串提升到这一点.

伪代码:

loop for i upfrom 0
     while all strings[i] are equal
     finally return substring[0..i]
Run Code Online (Sandbox Code Playgroud)

Common Lisp:

(defun longest-common-starting-substring (&rest strings)
  (loop for i from 0 below (apply #'min (mapcar #'length strings))
     while (apply #'char=
                  (mapcar (lambda (string) (aref string i))
                          strings))
     finally (return (subseq (first strings) 0 i))))
Run Code Online (Sandbox Code Playgroud)


jbe*_*man 9

我的Haskell单行:

import Data.List

commonPre :: [String] -> String
commonPre = map head . takeWhile (\(x:xs)-> all (==x) xs) . transpose
Run Code Online (Sandbox Code Playgroud)

编辑:barkmadley对下面的代码做了很好的解释.我还补充一点,haskell使用懒惰评估,所以我们可以懒得使用transpose; 它只会在必要时转置我们的列表以找到公共前缀的结尾.

  • 可爱的代码,但不幸的是,当字符串是另一个的前缀时,这会失败,例如`commonPre ["foo","foobar","foobarbaz"] =="foobarbaz"`. (3认同)

FMc*_*FMc 6

还有另一种方法:使用正则表达式贪婪.

words = %w(interspecies interstelar interstate)
j = '='
str = ['', *words].join(j)
re = "[^#{j}]*"

str =~ /\A
    (?: #{j} ( #{re} ) #{re} )
    (?: #{j}    \1     #{re} )*
\z/x

p $1
Run Code Online (Sandbox Code Playgroud)

单行,礼貌的mislav(50个字符):

p ARGV.join(' ').match(/^(\w*)\w*(?: \1\w*)*$/)[1]
Run Code Online (Sandbox Code Playgroud)