これは、最もエレガントなJavaScript、Ruby、または比較的些細な問題に対する他の解決策を考え出すことへの挑戦です。
この問題は 最も長い一般的な部分文字列の問題 のより具体的なケースです。配列内で最も長い共通starting部分文字列のみを見つける必要があります。これにより、問題が大幅に簡素化されます。
たとえば、[interspecies, interstelar, interstate]
の最も長い部分文字列は「inters」です。ただし、[specifics, terrific]
で「ific」を見つける必要はありません。
シェルのようなタブの補完に関する回答 ( テストページはこちら )の一部として、JavaScriptでソリューションをすばやくコーディングすることで問題を解決しました。これが少し調整されたソリューションです:
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)
}
これは コードはこのGistで入手可能 とRubyでの同様のソリューションです。 Gistをgitリポジトリとして複製して試すことができます。
$ git clone git://Gist.github.com/257891.git substring-challenge
私はそれらの解決策にあまり満足していません。私はそれらがよりエレガントでより少ない実行の複雑さで解決されるかもしれないと感じています-それが私がこの挑戦を投稿している理由です。
私は、最もエレガントで簡潔な解決策を答えとして受け入れます。たとえば、私が思いついたクレイジーなRubyハック—文字列での&
演算子の定義:
# 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
JavaScriptまたはRuby=での解決策が推奨されますが、何が起こっているのかを説明している限り、他の言語で巧妙な解決策を披露できます。標準ライブラリのコードのみを使用してください。
私は JavaScript sorting solution by kennebec を「答え」として選択しました。それは、予期しないものと天才の両方として私を驚かせたからです。実際の並べ替えの複雑さを無視すると(言語の実装によって無限に最適化されると想像してみてください)、ソリューションの複雑さは2つの文字列を比較するだけです。
その他の優れたソリューション:
commonprefix
in Python — Roberto Bonvalletは、ファイルシステムパスを処理してこの問題を解決するために作成された機能を使用しました参加してくれてありがとう!コメントからわかるように、Rubyについても多くのことを学びました。
それは好みの問題ですが、これは単純な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);
}
[〜#〜]デモ[〜#〜]
sharedStart(['interspecies', 'interstelar', 'interstate']) //=> 'inters'
sharedStart(['throne', 'throne']) //=> 'throne'
sharedStart(['throne', 'dungeon']) //=> ''
sharedStart(['cheese']) //=> 'cheese'
sharedStart([]) //=> ''
sharedStart(['prefix', 'suffix']) //=> ''
Pythonの場合:
>>> from os.path import commonprefix
>>> commonprefix('interspecies interstelar interstate'.split())
'inters'
ルビーワンライナー:
l=strings.inject{|l,s| l=l.chop while l!=s[0...l.length];l}
すべての文字列が異なるまでトラバースし、サブストリングをこの時点まで取得するだけです。
疑似コード:
loop for i upfrom 0
while all strings[i] are equal
finally return substring[0..i]
一般的な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))))
私のHaskellワンライナー:
import Data.List
commonPre :: [String] -> String
commonPre = map head . takeWhile (\(x:xs)-> all (==x) xs) . transpose
編集:barkmadleyは以下のコードの良い説明をしました。また、haskellは遅延評価を使用しているので、transpose
の使用について遅延することもできます。共通接頭辞の終わりを見つけるために必要な範囲でリストを転置するだけです。
それを行うもう1つの方法:正規表現の貪欲を使用します。
words = %w(interspecies interstelar interstate)
j = '='
str = ['', *words].join(j)
re = "[^#{j}]*"
str =~ /\A
(?: #{j} ( #{re} ) #{re} )
(?: #{j} \1 #{re} )*
\z/x
p $1
そしてミスライナーの礼儀(50文字):
p ARGV.join(' ').match(/^(\w*)\w*(?: \1\w*)*$/)[1]
Pythonでは、別の回答で示した既存のcommonprefix
関数以外は何も使用しませんが、ホイール_:P
_を再発明することはできませんでした。これは私のイテレータベースのアプローチです:
_>>> a = 'interspecies interstelar interstate'.split()
>>>
>>> from itertools import takewhile, chain, izip as Zip, imap as map
>>> ''.join(chain(*takewhile(lambda s: len(s) == 1, map(set, Zip(*a)))))
'inters'
_
編集:これがどのように機能するかの説明。
Zip
は、一度にa
の各項目の1つを取る要素のタプルを生成します。
_In [6]: list(Zip(*a)) # here I use list() to expand the iterator
Out[6]:
[('i', 'i', 'i'),
('n', 'n', 'n'),
('t', 't', 't'),
('e', 'e', 'e'),
('r', 'r', 'r'),
('s', 's', 's'),
('p', 't', 't'),
('e', 'e', 'a'),
('c', 'l', 't'),
('i', 'a', 'e')]
_
これらのアイテムにset
をマッピングすると、一連の一意の文字が得られます。
_In [7]: list(map(set, _)) # _ means the result of the last statement above
Out[7]:
[set(['i']),
set(['n']),
set(['t']),
set(['e']),
set(['r']),
set(['s']),
set(['p', 't']),
set(['a', 'e']),
set(['c', 'l', 't']),
set(['a', 'e', 'i'])]
_
takewhile(predicate, items)
は、述語がTrueのときにthisから要素を取得します。この特定のケースでは、set
sに1つの要素がある場合、つまり、すべての単語のその位置に同じ文字がある場合:
_In [8]: list(takewhile(lambda s: len(s) == 1, _))
Out[8]:
[set(['i']),
set(['n']),
set(['t']),
set(['e']),
set(['r']),
set(['s'])]
_
この時点で、反復可能なセットがあり、それぞれに、探していた接頭辞の1文字が含まれています。文字列を作成するには、それらを単一の反復可能オブジェクトにchain
して、そこからjoin
への文字を取得して最終的な文字列にします。
イテレータを使用する魔法は、すべてのアイテムがオンデマンドで生成されることです。そのため、takewhile
がアイテムの要求を停止すると、その時点で圧縮が停止し、不要な作業は行われません。私のワンライナーの各関数呼び出しには、暗黙のfor
と暗黙のbreak
があります。
これはおそらく最も簡潔な解決策ではありません(すでにこのライブラリが存在するかどうかによって異なります)が、エレガントな方法の1つは、トライを使用することです。私は、Schemeインタープリターでタブ補完を実装するためにトライを使用します。
http://github.com/jcoglan/heist/blob/master/lib/trie.rb
例えば:
tree = Trie.new
%w[interspecies interstelar interstate].each { |s| tree[s] = true }
tree.longest_prefix('')
#=> "inters"
また、バイユープロトコルのチャネル名とワイルドカードを照合するためにも使用します。これらを参照してください:
http://github.com/jcoglan/faye/blob/master/client/channel.js
http://github.com/jcoglan/faye/blob/master/lib/faye/channel.rb
それを楽しむために、ここに(SWI-)PROLOGで書かれたバージョンがあります:
common_pre([[C|Cs]|Ss], [C|Res]) :-
maplist(head_tail(C), [[C|Cs]|Ss], RemSs), !,
common_pre(RemSs, Res).
common_pre(_, []).
head_tail(H, [H|T], T).
ランニング:
?- S=["interspecies", "interstelar", "interstate"], common_pre(S, CP), string_to_list(CPString, CP).
与える:
CP = [105, 110, 116, 101, 114, 115],
CPString = "inters".
説明:
(SWI-)PROLOGは、文字列を文字コード(数値)のリストとして扱います。すべての述語common_pre/2
doは、最初のコード(C
)を最初のリスト(文字列、[C|Cs]
)すべてのリストのリスト(すべての文字列、[[C|Cs]|Ss]
)、一致するコードC
を結果に追加しますiffすべてのリスト(文字列)のすべての(残りの)ヘッドに共通です。それ以外の場合は終了します。
素敵で、清潔で、シンプルで効率的... :)
@ Svanteのアルゴリズム に基づくJavaScriptバージョン:
function commonSubstring(words){
var iChar, iWord,
refWord = words[0],
lRefWord = refWord.length,
lWords = words.length;
for (iChar = 0; iChar < lRefWord; iChar += 1) {
for (iWord = 1; iWord < lWords; iWord += 1) {
if (refWord[iChar] !== words[iWord][iChar]) {
return refWord.substring(0, iChar);
}
}
}
return refWord;
}
kennebec、Florian Fおよびjberrymanで回答を組み合わせると、次のHaskellワンライナーが生成されます。
commonPrefix l = map fst . takeWhile (uncurry (==)) $ Zip (minimum l) (maximum l)
Control.Arrow
ポイントフリーのフォームを取得できます:
commonPrefix = map fst . takeWhile (uncurry (==)) . uncurry Zip . (minimum &&& maximum)
これは、Rubyを除いて、Roberto Bonvalletのソリューションと非常によく似ています。
chars = %w[interspecies interstelar interstate].map {|w| w.split('') }
chars[0].Zip(*chars[1..-1]).map { |c| c.uniq }.take_while { |c| c.size == 1 }.join
最初の行は、各Wordを文字の配列で置き換えます。次に、Zip
を使用してこのデータ構造を作成します。
[["i", "i", "i"], ["n", "n", "n"], ["t", "t", "t"], ...
map
およびuniq
これを[["i"],["n"],["t"], ...
に削減
take_while
は、サイズが1ではない(すべての文字が同じであるとは限らない)ものが見つかるまで、配列から文字を引き出します。最後に、私はそれらをjoin
一緒に戻します。
受け入れられるソリューション は壊れています(たとえば、_['a', 'ba']
_のような文字列に対してはa
を返します)。修正は非常に簡単で、文字どおり3文字(indexOf(tem1) == -1
からindexOf(tem1) != 0
)を変更するだけで、関数は期待どおりに機能します。
残念ながら、入力ミスを修正するために回答を編集しようとしたとき、SOは「編集は少なくとも6文字でなければならない」と私に言った。私はできたネーミングと読みやすさを改善することにより、これらの3文字よりも多くを変更しますが、それは少し多すぎるように感じます。
だから、以下はケネベックの解決策の修正された(少なくとも私の観点から)バージョンです:
_function commonPrefix(words) {
max_Word = words.reduce(function(a, b) { return a > b ? a : b });
prefix = words.reduce(function(a, b) { return a > b ? b : a }); // min Word
while(max_Word.indexOf(prefix) != 0) {
prefix = prefix.slice(0, -1);
}
return prefix;
}
_
(オン jsFiddle )
配列をソートしてからその最初と最後の要素をフェッチする代わりに、英数字の最大/最小を見つけるために reduce メソッド(JavaScript 1.8)を使用することに注意してください。
究極のパフォーマンスをあまり気にしないのであれば、それほど複雑ではないようです。
def common_substring(data)
data.inject { |m, s| s[0,(0..m.length).find { |i| m[i] != s[i] }.to_i] }
end
インジェクトの便利な機能の1つは、配列の最初の要素が挿入される前にシードする機能です。これにより、メモのチェックが不要になります。
puts common_substring(%w[ interspecies interstelar interstate ]).inspect
# => "inters"
puts common_substring(%w[ feet feel feeble ]).inspect
# => "fee"
puts common_substring(%w[ fine firkin fail ]).inspect
# => "f"
puts common_substring(%w[ alpha bravo charlie ]).inspect
# => ""
puts common_substring(%w[ fork ]).inspect
# => "fork"
puts common_substring(%w[ fork forks ]).inspect
# => "fork"
更新:ここでゴルフがゲームの場合、67文字:
def f(d)d.inject{|m,s|s[0,(0..m.size).find{|i|m[i]!=s[i]}.to_i]}end
これらの回答をすべての豪華な関数型プログラミング、並べ替え、正規表現などで読んでいるとき、私はただ考えました。Cの少し問題は何ですか?だから、これは間抜けな小さなプログラムです。
#include <stdio.h>
int main (int argc, char *argv[])
{
int i = -1, j, c;
if (argc < 2)
return 1;
while (c = argv[1][++i])
for (j = 2; j < argc; j++)
if (argv[j][i] != c)
goto out;
out:
printf("Longest common prefix: %.*s\n", i, argv[1]);
}
それをコンパイルし、文字列のリストをコマンドライン引数として実行してから、goto
!
_Python 2.6 (r26:66714, Oct 4 2008, 02:48:43)
>>> a = ['interspecies', 'interstelar', 'interstate']
>>> print a[0][:max(
[i for i in range(min(map(len, a)))
if len(set(map(lambda e: e[i], a))) == 1]
) + 1]
inters
_
i for i in range(min(map(len, a)))
、最大ルックアップ数は、最短の文字列の長さを超えることはできません。この例では、これは_[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
_に評価されます
len(set(map(lambda e: e[i], a)))
、1)リスト内の各文字列の_i-th
_ characterの配列を作成します。 2)それからセットを作る。 3)セットのサイズを決定する
[i for i in range(min(map(len, a))) if len(set(map(lambda e: e[i], a))) == 1]
、セットのサイズが1である文字のみを含めます(その位置にあるすべての文字は同じでした。)。ここでは_[0, 1, 2, 3, 4, 5]
_と評価されます
最後にmax
を受け取り、1つ追加して、部分文字列を取得します...
注:上記は_a = ['intersyate', 'intersxate', 'interstate', 'intersrate']
_では機能しませんが、次のようになります。
_ >>> index = len(
filter(lambda l: l[0] == l[1],
[ x for x in enumerate(
[i for i in range(min(map(len, a)))
if len(set(map(lambda e: e[i], a))) == 1]
)]))
>>> a[0][:index]
inters
_
楽しみのためのゴルフJSソリューション:
w=["hello", "hell", "helen"];
c=w.reduce(function(p,c){
for(r="",i=0;p[i]==c[i];r+=p[i],i++){}
return r;
});
Rubyでの効率的なソリューションを次に示します。私は、最長の接頭辞に繰り返しゼロを当てるhi/lo推測ゲームの戦略のアイデアに基づいています。
誰かが私を間違っていると訂正してくれますが、複雑さはO(n log n)だと思います。ここで、nは最短の文字列の長さであり、文字列の数は定数と見なされます。
def common(strings)
lo = 0
hi = strings.map(&:length).min - 1
return '' if hi < lo
guess, last_guess = lo, hi
while guess != last_guess
last_guess = guess
guess = lo + ((hi - lo) / 2.0).ceil
if strings.map { |s| s[0..guess] }.uniq.length == 1
lo = guess
else
hi = guess
end
end
strings.map { |s| s[0...guess] }.uniq.length == 1 ? strings.first[0...guess] : ''
end
そしていくつかはそれが機能することを確認します:
>> common %w{ interspecies interstelar interstate }
=> "inters"
>> common %w{ dog dalmation }
=> "d"
>> common %w{ asdf qwerty }
=> ""
>> common ['', 'asdf']
=> ""
ソートする代わりに、文字列の最小値と最大値を取得することもできます。
私にとって、コンピュータープログラムの優雅さはスピードとシンプルさのバランスです。それは不必要な計算をするべきではなく、その正確さを明らかにするのに十分単純でなければなりません。
私はソーティングソリューションを「賢い」と呼ぶことができましたが、「エレガント」ではありませんでした。
楽しい代替手段Rubyソリューション:
_def common_prefix(*strings)
chars = strings.map(&:chars)
length = chars.first.Zip( *chars[1..-1] ).index{ |a| a.uniq.length>1 }
strings.first[0,length]
end
p common_prefix( 'foon', 'foost', 'forlorn' ) #=> "fo"
p common_prefix( 'foost', 'foobar', 'foon' ) #=> "foo"
p common_prefix( 'a','b' ) #=> ""
_
最初の文字列が短いほど、Zip
によって作成される配列が短くなるため、chars = strings.sort_by(&:length).map(&:chars)
を使用した場合、速度が向上する可能性があります。ただし、速度を重視する場合は、おそらくこのソリューションを使用すべきではありません。 :)
Javaでの私の解決策:
public static String compute(Collection<String> strings) {
if(strings.isEmpty()) return "";
Set<Character> v = new HashSet<Character>();
int i = 0;
try {
while(true) {
for(String s : strings) v.add(s.charAt(i));
if(v.size() > 1) break;
v.clear();
i++;
}
} catch(StringIndexOutOfBoundsException ex) {}
return strings.iterator().next().substring(0, i);
}
私は次のようにします:
次にJavaScriptの実装を示します。
var array = ["interspecies", "interstelar", "interstate"],
prefix = array[0],
len = prefix.length;
for (i=1; i<array.length; i++) {
for (j=0, len=Math.min(len,array[j].length); j<len; j++) {
if (prefix[j] != array[i][j]) {
len = j;
prefix = prefix.substr(0, len);
break;
}
}
}
Rubyで正規表現を使用するソリューションは次のとおりです。
def build_regex(string)
arr = []
arr << string.dup while string.chop!
Regexp.new("^(#{arr.join("|")})")
end
def substring(first, *strings)
strings.inject(first) do |accum, string|
build_regex(accum).match(string)[0]
end
end
多くの場合、独自に開発するのではなく、成熟したオープンソースライブラリを使用する方がエレガントです。次に、それが完全にニーズに合わない場合は、拡張または変更して改善し、コミュニティーがライブラリーに属しているかどうかをコミュニティーに判断させることができます。
diff-lcs は、よくあるRuby最も一般的なサブストリングのgemです。
私Javascriptソリューション:
IMOP、並べ替えを使用するのは難しいです。私の解決策は、配列をループして文字ごとに比較することです。文字が加工されていない場合は文字列を返します。
これは私の解決策です:
var longestCommonPrefix = function(strs){
if(strs.length < 1){
return '';
}
var p = 0, i = 0, c = strs[0][0];
while(p < strs[i].length && strs[i][p] === c){
i++;
if(i === strs.length){
i = 0;
p++;
c = strs[0][p];
}
}
return strs[0].substr(0, p);
};
ルビー
require 'abbrev'
ar = ["interspecies", "interstelar", "interstate"]
ar.abbrev.keys.min_by(&:size).chop # => "inters"
文字列のセットが与えられた場合、abbrev
はそれらの文字列の明確な略語のセットを計算し、キーがすべての可能な省略形であるハッシュを返します(値は完全な文字列です)。 charが共通のプレフィックスになります。
これは決してエレガントではありませんが、簡潔にしたい場合:
def f(a)b=a[0];b[0,(0..b.size).find{|n|a.any?{|i|i[0,n]!=b[0,n]}}-1]end
展開したい場合は、次のようになります。
def f(words)
first_Word = words[0];
first_Word[0, (0..(first_Word.size)).find { |num_chars|
words.any? { |Word| Word[0, num_chars] != first_Word[0, num_chars] }
} - 1]
end
これが コードゴルフ のマッチに変わるリスクを認識している(またはその意図は?)、ここに 私の答え からコピーされたsed
を使用した私の解決策があります別のSOの質問になり、36文字に短縮されます(そのうち30文字は実際のsed
式です)。文字列(それぞれ別の行)が標準で提供されることを期待しています追加の引数として渡される入力またはファイル。
sed 'N;s/^\(.*\).*\n\1.*$/\1\n\1/;D'
シバン行でsedを使用したスクリプトの重量は45文字です。
#!/bin/sed -f
N;s/^\(.*\).*\n\1.*$/\1\n\1/;D
スクリプトのテスト実行(名前はlongestprefix
)で、「ヒアドキュメント」として文字列が指定されています。
$ ./longestprefix <<EOF
> interspecies
> interstelar
> interstate
> EOF
inters
$
それはゴルフのコードではありませんが、ややエレガントなものを求めたので、再帰は楽しいと思う傾向があります。 Java。
/** Recursively find the common prefix. */
public String findCommonPrefix(String[] strings) {
int minLength = findMinLength(strings);
if (isFirstCharacterSame(strings)) {
return strings[0].charAt(0) + findCommonPrefix(removeFirstCharacter(strings));
} else {
return "";
}
}
/** Get the minimum length of a string in strings[]. */
private int findMinLength(final String[] strings) {
int length = strings[0].size();
for (String string : strings) {
if (string.size() < length) {
length = string.size();
}
}
return length;
}
/** Compare the first character of all strings. */
private boolean isFirstCharacterSame(String[] strings) {
char c = string[0].charAt(0);
for (String string : strings) {
if (c != string.charAt(0)) return false;
}
return true;
}
/** Remove the first character of each string in the array,
and return a new array with the results. */
private String[] removeFirstCharacter(String[] source) {
String[] result = new String[source.length];
for (int i=0; i<result.length; i++) {
result[i] = source[i].substring(1);
}
return result;
}
A Ruby @Svanteのアルゴリズムに基づくバージョン。最初のバージョンの3倍の速度で実行されます。
def common_prefix set
i=0
rest=set[1..-1]
set[0].each_byte{|c|
rest.each{|e|return set[0][0...i] if e[i]!=c}
i+=1
}
set
end
AShelly のJavascriptクローンは素晴らしい答えです。
Array#reduce
が必要です。これは、Firefoxでのみサポートされています。
var strings = ["interspecies", "intermediate", "interrogation"]
var sub = strings.reduce(function(l,r) {
while(l!=r.slice(0,l.length)) {
l = l.slice(0, -1);
}
return l;
});