私は 2010 年にこの問題に取り組みましたが、解決策を見つけることができませんでした。数日前、私は当時のメモをもう一度見て、解決に非常に近づいているのではないかと疑いました。数分後、私は鍵を手に入れました。
要約すると、要件は、LIFO (後入れ先出し) が最大化されるように、文字列 s の k-サブセットの厳密な最小変更順序です。以前の投稿では、これを最大化された「スワッピング」と呼んでいます。
キー要件の後に、このアルゴリズムを maxlifo (maximized LIFO) と呼びます。重複文字を含んではならない文字列 s と、s のサイズを超えない正の整数 k の 2 つのパラメータを取ります。このアルゴリズムは再帰的です。つまり、maxlifo(s, k) は maxlifo(s, k-1) の出力を k=1 まで使用します。出力はリストとして返されます。
以下に、文字列「abcdefg」とさまざまな k の値を使用して、例を挙げて非公式に説明します。続いて、Unicon プロシージャとしての実装例を示します。(私は、より一般的に使用されている言語に堪能ではありません。)
k=1 の場合は自明です — s の要素を最初から最後まで順番に返します。
k>1 の場合、次のルールを maxlifo(s, k-1) の出力に適用します。
(1) maxlifo(s, k-1) の出力の各要素について、その要素から構築された k-サブセットを s の各欠落文字を順番に並べてリストします。サブセット内の文字の順序は s と同じです。
(2) 2 番目の行から作業して、前の行に表示されるサブセットのすべての出現を空白の「プレースホルダー」に置き換えます。s の各 k-サブセットが 1 回だけ表示されるようになりました。
(3) 空白でない各行で、頭文字 ! でマークします。次の行の同じ位置にプレースホルダーがあるような各サブセット。このマークは「最初」を意味します。空白でない各行で、正確に 1 つのサブセットがそのようにマークされます。
(4) 完全に空白の行 (プレースホルダーのみを含む) をすべて削除します。
(5) 最後の行を除く残りの各行では、最後に ! を付けます。次の下の行で「最初」とマークされたサブセットに対応する位置のサブセット。このマーキングは「最後」を意味します。
これで、サブセットの maxlifo の最終的な順序を一覧表示できます。各行は上から下に並べられ、その要素がその順序で出力リストに追加されます。
(6) 各行の上から順に:
(6.1) 空白のプレースホルダーをすべて削除します。
(6.2) 出力リストに「最初」とマークされたサブセット (イニシャル !) を追加し、行から削除します。
(6.3) 行にまだサブセットが残っている場合、左端または右端のサブセットのいずれかが「最後」とマークされます (最終!)。右端のサブセットが「最後」とマークされている場合は、サブセットを左から右の順序で出力リストに追加します。それ以外の場合は、右から左の順序で追加します。
(7) すべての行を処理した後、出力リストを返します。
maxlifo("abcdefg", 2) を使用した例:
Col1 には maxlifo("abcdefg", 1) の出力が含まれます。Col2 の行には、s の残りの文字で形成されたクリークが含まれています。
Col1 Col2
---- ----------------------------
a ab ac ad ae af ag
b ab bc bd be bf bg
c ac bc cd ce cf cg
d ad bd cd de df dg
e ae be ce de ef eg
f af bf cf df ef fg
g ag bg cg dg eg fg
前の行に表示されるサブセットを空白にします。
a ab ac ad ae af ag
b bc bd be bf bg
c cd ce cf cg
d de df dg
e ef eg
f fg
g
各行の「最初の」サブセット (下に空白があるもの) をマークします。
a !ab ac ad ae af ag
b !bc bd be bf bg
c !cd ce cf cg
d !de df dg
e !ef eg
f !fg
g
完全に空白の行をすべて削除します (この場合は 1 行だけ):
a !ab ac ad ae af ag
b !bc bd be bf bg
c !cd ce cf cg
d !de df dg
e !ef eg
f !fg
各行の「最後の」サブセット (その下に「最初の」サブセットがあるもの) をマークします。
a !ab ac! ad ae af ag
b !bc bd! be bf bg
c !cd ce! cf cg
d !de df! dg
e !ef eg!
f !fg
上記の順序で各行を出力します: 「最初」、マークなし、「最後」:
Ordered rows:
a !ab ac! ad ae af ag ab ag af ae ad ac
b !bc bd! be bf bg bc bg bf be bd
c !cd ce! cf cg cd cg cf ce
d !de df! dg de dg df
e !ef eg! ef eg
f !fg fg
出力: [ab ag af ae ad ac bc bg bf be bd cd cg cf ce df dg de ef eg fg]
3 <= k <= 6 の例は、あまり詳細ではありません。手順 4 で削除された空白行はそのまま残ります。
maxlifo("abcdefg", 3):
Ordered rows:
ab !abc abd abe abf abg! abc abd abe abf abg
ag acg adg aeg! !afg afg acg adg aeg
af acf adf! !aef aef acf adf
ae ace! !ade ade ace
ad !acd! acd
ac
bc !bcd bce bcf bcg! bcd bce bcf bcg
bg bdg beg! !bfg bfg bdg beg
bf bdf! !bef bef bdf
be !bde! bde
bd
cd !cde cdf cdg! cde cdf cdg
cg ceg! !cfg cfg ceg
cf !cef! cef
ce
de !def deg! def deg
dg !dfg! dfg
df
ef !efg efg
eg
fg
出力: [abc abd abe abf abg afg acg adg aeg aef acf adf ade ace acd
bcd bce bcf bcg bfg bdg beg bef bdf bde cde cdf cdg cfg ceg cef def deg dfg efg]
maxlifo("abcdefg", 4):
Ordered rows:
abc !abcd abce! abcf abcg abcd abcg abcf abce
abd !abde abdf! abdg abde abdg abdf
abe !abef abeg! abef abeg
abf !abfg! abfg
abg
afg acfg! adfg !aefg aefg adfg acfg
acg !acdg aceg! acdg aceg
adg !adeg! adeg
aeg
aef acef! !adef adef acef
acf !acdf! acdf
adf
ade !acde! acde
ace
acd
bcd !bcde bcdf! bcdg bcde bcdg bcdf
bce !bcef bceg! bcef bceg
bcf !bcfg! bcfg
bcg
bfg bdfg! !befg befg bdfg
bdg !bdeg! bdeg
beg
bef !bdef! bdef
bdf
bde
cde !cdef cdeg! cdef cdeg
cdf !cdfg! cdfg
cdg
cfg !cefg! cefg
ceg
cef
def !defg defg
deg
dfg
efg
出力: [abcd abcg abcf abce abde abdg abdf abef abeg abfg aefg adfg acfg acdg aceg adeg adef acef acdf acde bcde bcdg bcdf bcef bceg bcfg befg bdfg bdeg bdef cdef cdeg cdfg cefg defg]
maxlifo("abcdefg", 5):
Ordered rows:
abcd !abcde abcdf abcdg! abcde abcdf abcdg
abcg abceg! !abcfg abcfg abceg
abcf !abcef! abcef
abce
abde !abdef abdeg! abdef abdeg
abdg !abdfg! abdfg
abdf
abef !abefg! abefg
abeg
abfg
aefg acefg! !adefg adefg acefg
adfg !acdfg! acdfg
acfg
acdg !acdeg! acdeg
aceg
adeg
adef !acdef! acdef
acef
acdf
acde
bcde !bcdef bcdeg! bcdef bcdeg
bcdg !bcdfg! bcdfg
bcdf
bcef !bcefg! bcefg
bceg
bcfg
befg !bdefg! bdefg
bdfg
bdeg
bdef
cdef !cdefg cdefg
cdeg
cdfg
cefg
defg
出力: [abcde abcdf abcdg abcfg abceg abcef abdef abdeg abdfg abefg adefg acefg acdfg acdeg acdef bcdef bcdeg bcdfg bcefg bdefg cdefg]
maxlifo("abcdefg", 6):
Ordered rows:
abcde !abcdef abcdeg! abcdef abcdeg
abcdf !abcdfg! abcdfg
abcdg
abcfg !abcefg! abcefg
abceg
abcef
abdef !abdefg! abdefg
abdeg
abdfg
abefg
adefg
acefg !acdefg! acdefg
acdfg
acdeg
acdef
bcdef !bcdefg bcdefg
bcdeg
bcdfg
bcefg
bdefg
cdefg
出力: [abcdef abcdeg abcdfg abcefg abdefg acdefg bcdefg]
ユニコンの実装:
procedure maxlifo(s:string, k:integer)
# A solution to my combinatorics problem from 2010.
# Return a list of the k subsets of the characters of a string s
# in a minimal change order such that last-in first-out is maximised.
# String s must not contain duplicate characters and in the present
# implementation must not contain "!", which is used as a marker.
local ch, cand, Hit, inps, i, j, K, L, Outp, R, S
# Errors
if *cset(s) ~= *s then
stop("Duplicate characters in set in maxlifo(", s, ", ", k, ")")
if find("!", s) then
stop("Illegal character in set in maxlifo(", s, ", ", k, ")")
if k > *s then
stop("Subset size larger than set size in maxlifo(", s, ", ", k, ")")
# Special cases
if k = 0 then return []
if k = *s then return [s]
Outp := []
if k = 1 then {
every put(Outp, !s)
return Outp
}
# Default case
S := set()
K := []
# Build cliques from output of maxlifo(s, k-1) with the remaining
# characters in s, substituting empty strings as placeholders for
# subsets already listed.
every inps := !maxlifo(s, k-1) do {
R := []
every ch := !s do
if not find(ch, inps) then {
cand := reorder(inps ++ ch, s)
if member(S, cand) then cand := "" else insert(S, cand)
put(R, cand)
}
put(K, R)
}
# Mark ‘first’ subset in each row with initial "!"
every i := 1 to *K - 1 do {
every j := 1 to *K[i] do
if K[i, j] ~== "" & K[i+1, j] == "" then {
K[i, j] := "!" || K[i, j]
break
}
}
# Remove rows containing only placeholders
every i := *K to 1 by -1 do {
every if !K[i] ~== "" then break next
delete(K, i)
}
# Mark ‘last’ subset in each row with final "!"
every i := 1 to *K - 1 do
every j := 1 to *K[i] do
if K[i+1, j][1] == "!" then {
K[i, j] ||:= "!"
break
}
# Build output list
every R := !K do {
# Delete placeholders from row (no longer needed and in the way)
every j := *R to 1 by -1 do if R[j] == "" then delete(R, j)
# Handle ‘first’ subset and remove from row
# N.B. ‘First’ subset will be leftmost or rightmost in row
if R[1][1] == "!" then
put(Outp, trim(get(R), '!', 0))
else put(Outp, trim(pull(R), '!', 0))
# Handle any remaining subsets, ‘last’ subset last, stripping '!' markers
# N.B. ‘Last’ subset will be leftmost or rightmost in row after removal
# of ‘first’ subset.
if R[-1][-1] == "!" then while put(Outp, trim(get(R), '!', 0)) else
while put(Outp, trim(pull(R), '!', 0))
}
return Outp
end
procedure reorder(cs:cset, s:string)
# Reorder cset cs according to string s
local r
# If no s, return set in alphabetical order
if /s then return string(cs)
r := ""
s ? while tab(upto(cs)) do r ||:= move(1)
return r
end