タスクの本質が実に単純であることを忘れてはなりません。Haskellのチュートリアルに記載されているように(これは、このタスクのソリューションの作業手順を中心に書かれており、徐々に改良されています)
ここで、プログラムがどのように動作し、それを疑似コードで表現するかについて少し考えてみましょう。
main = Read list of directories and their sizes.
Decide how to fit them on CD-Rs.
Print solution.
合理的に聞こえますか?私はそうだと思いました。
私たちの生活を少し単純化して、今のところ、プログラムの外のどこかで(たとえば、 " du -sb *
"を使用して)ディレクトリサイズを計算し、この情報をstdinから読み取ると仮定します。
(HitchhikersガイドからHaskellへ、第1章)
(さらに、あなたの質問では、結果のディスクレイアウトを微調整(編集)し、ツールを使用してそれらを書き込むことができます。)
ファイルコレクションを分割するためのHaskellチュートリアルのプログラムの単純なバリアントを再利用(適応および再利用)できます。
残念ながら、ここで別の回答で述べたdistribute
ツールでは、重要な分割タスクのシンプルさは、ユーザーインターフェースの複雑さと肥大さには対応していませんdistribute
(複数のタスクを組み合わせるように記述されているため、段階的に実行されますが、しかし、私が今考えることができる最もきれいな方法ではまだ結合されていません)。
そのコードの使用を支援するために、ファイルのコレクションを分割するこの「重要な」タスクを実行するのに役立つdistribute
(380行目)のbashコードからの抜粋を次に示します。
# Splitting:
function splitMirrorDir() {
if [[ ! -d "$THIS_BASES_DIR/$BASE/$type" ]]; then
echo $"No base fixed for $type" >&2
exit 1
fi
# Getting the list of all suitable files:
local -a allFiles
let 'no = 0' ||:
allFiles=()
# no points to the next free position in allFiles
# allFiles contains the constructed list
for p in "$THIS_BASES_DIR/$BASE/$type"/*.rpm; do
if [[ ! -e "$p" ]]; then
# fail on non-existent files
echo $"Package file doesn't exist: " "$p" >&2
return 1
fi
if [[ "$ONLY_REAL_FILES" == "yes" && ! -f "$p" ]]; then
continue
fi
if [[ "$DIFF_TO_BASE" ]]; then
older_copy="$DIFF_TO_BASE/$type/${p##*/}" # using shell param expansion instead of `basename' to speed up
if [[ -h "$older_copy" || -a "$older_copy" ]]; then
continue
fi
fi
allFiles[$(( no++ ))]="$p"
done
readonly -a allFiles
# Splitting the list of all files into future disks:
#
local -a filesToEat allSizes
let 'no = 0' ||:
filesToEat=()
allSizes=($(getSize "${allFiles[@]}"))
readonly -a allSizes
# allSizes contains the sizes corrsponding to allFiles
# filesToEat hold the constructed list of files to put on the current disk
# no points to the next free position in filesToEat
# totalSize should hold the sum of the sizes
# of the files already put into filesToEat;
# it is set and reset externally.
for p in "${allFiles[@]}"; do
if (( totalsize + ${allSizes[$(( no ))]} > CDVOLUME )); then
eatFiles "${filesToEat[@]}"
filesToEat=()
finishCD
startTypedCD
fi
let "totalsize += ${allSizes[$(( no ))]}" ||:
filesToEat[$(( no++ ))]="$p"
done
eatFiles "${filesToEat[@]}"
}
function eatFiles() {
#{ oldIFS="$IFS"; IFS=$'\n'; echo "$FUNCNAME: args: " "$*" | head >&2; IFS="$oldIFS"; }
zeroDelimited "$@" | xargs -0 --no-run-if-empty \
cp -s \
--target-dir="$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$type$DOT_SUFFIX"/ \
--
}
function startTypedCD() {
# set -x
mkdir -p "$THIS_LAYOUTS_DIR/cd$(( cdN ))/$PREFIX/$type$DOT_SUFFIX"
start_action $" %s with %s" "$(( cdN ))" "$type"
# set +x
}
function finishCD() {
(454行目以降を参照)
このeatFiles
関数は、将来のディスクのレイアウトを、リーフが実際のファイルへのシンボリックリンクであるツリーとして準備することに注意してください。したがって、書き込み前にレイアウトを編集できる必要があるという要件を満たしています。mkisofs
ユーティリティは、実際のコードで使用されるシンボリックリンクをたどるためのオプション、持っている私のmkiso
機能を。
提示されたスクリプト(もちろん、必要に応じて書き換えることができます!)は、最も単純なアイデアに従っdistribute
ています。再配置は行いません。
"Hitchhikers guide to Haskell"は最適化の問題をより真剣に受け止め、ファイルをより適切に再配置してディスクに収まるように(そして必要なディスクが少なくなるように)プログラムのバリエーションを提案します。
すでに十分な予備知識。いくつかのCDをパックしましょう。
すでにご存じかもしれませんが、私たちの問題は古典的な問題です。これは「ナップサックの問題」と呼ばれます
(それが何であるかまだわからない場合は、グーグルで確認してください。100000以上のリンクがあります)。
貪欲な解決策から始めましょう...
(第3章でさらに詳しく読んでください。)
その他のスマートツール
Debianはdistribute
パッケージのwrtコレクションよりもスマートなディストリビューションCDを作成するためのツールを使用することも聞かれました。パッケージ間の依存関係を考慮し、取得するパッケージのコレクションを作成しようとするため、結果はより良いです最初のディスクは依存関係の下で閉じられました。つまり、最初のディスクからのパッケージは別のディスクからのパッケージを必要としません(または、少なくとも、そのような依存関係の数を最小限に抑える必要があります)。