多くのフォルダを作成し、その中でいくつかのことをしたいと思います。フォルダ名は、for
ループ内で変数として定義された複数の化学元素の配列に基づいています。
for Element in Cr Hf Mo Nb Ta Ti V W Zr
CrHfMoNb
、、...などの文字を含むCrHfMoTa
サブフォルダを取得できるように、アルファベット順に4つの要素のすべての順列のフォルダが必要です。これを行うために4つのネストされたループを試しましたが、for
単純化のためにここでは2つのループだけを使って説明します。私が思いついたコードは次のとおりです。
for Element in Cr Hf Mo Nb Ta Ti V W Zr; do
for Elemen in Hf Mo Nb Ta Ti V W Zr; do
mkdir "$Element""$Elemen"N # the N at the end is intended
done
done
TiNbN
これにより、目的のフォルダが作成されますが、orZrVN
やlikeなどのアルファベット以外の組み合わせも取得されるため、不要なフォルダもたくさん作成されますHfHfN
。 3行目にifステートメントを追加すると、重複エントリを削除できます。
do [ "$Element" != "$Elemen" ] && mkdir "$Element""$Elemen"N
しかし、これらの重複フォルダは完全に消えませんでしたが、代わりに私のディレクトリの「ゴースト」ファイルになりました。つまり、HfHfN
ファイル拡張子なしでetcとして呼び出されました。しかし、実際の問題は残りのフォルダです。次のようなifステートメントを追加してみました。
do [ "$Element" != "$Elemen" ] && [ "$Element" > "$Elemen" ] && mkdir "$Element""$Elemen"N
許容される順列の数は減りますが、何も削除されません。また、ifステートメントを独自のforループに分割してみましたが、何も変更されませんでした。
for Element in Cr Hf Mo Nb Ta Ti V W Zr; do
[ "$Element" != "$Elemen" ] && [ "$Element" > "$Elemen" ] &&
for Elemen in Hf Mo Nb Ta Ti V W Zr; do...
>
これが正しいコマンドであるかどうかはわかりませんが、if
このリストからhttp://tldp.org/LDP/Bash-Beginners-Guide/html/sect_07_01.htmlこれが最も意味のあるようです。同様のコマンドを使用すると-ne, -lt, -le, -gt
整数が必要なため、機能しないため、文字は許可されません。結局のところ、4つのループをグループ化してみるのは少し難しくなりました。私は何を見逃していますか?
答え1
#/bin/sh
# shellcheck disable=SC2046
# ^ word-splitting by the shell is intentional in this file
elems="Cr Hf Mo Nb Ta Ti V W Zr"
for a in $elems
do
for b in $elems
do
for c in $elems
do
for d in $elems
do
# for a set of any four elements:
# string them together, separated by NUL-bytes
# sort them lexicographically ...
# ... with NUL separating the elements (-z)
# ... and eliminate duplicates (-u)
# then replace the NUL bytes with line breaks
# allow the shell to split on those line breaks
# and chuck the resulting chunks into $1, $2, etc
set -- $(printf '%s\0' "$a" "$b" "$c" "$d" | sort -z -u | tr "\0" "\n")
# only if the current selection of elements consisted of four
# different ones (remember we eliminated duplicates):
if [ $# -eq 4 ]
then
# create a directory, don't error out if it already exists (-p)
mkdir -p "$(printf '%s' "$@")"
fi
done
done
done
done
非常に効率的ではありませんが、(sort
明白な非候補者を呼び出してmkdir
同じディレクトリ名を複数回呼び出す場合でも)、内部ループは最大9 4 = 6561の反復を実行し、ワンタイムスクリプトなのでそうではないと思います。最適化に時間を費やす価値があります。
編集:
Xeon E3-1231v3のベンチマーク、いいえmkdir
:
./elemdirs.sh > /dev/null 11.66s user 1.73s system 173% cpu 7.725 total
そしてそれと一緒に:
./elemdirs.sh > /dev/null 13.80s user 2.16s system 156% cpu 10.215 total
予想数である126個のディレクトリを作成します。コンビネーションここでk = 4、n = 9です。
答え2
PerlとAlgorithm::Combinatorics
モジュールの使用:
perl -MAlgorithm::Combinatorics=combinations -e '$"=""; map { mkdir "@{$_}N" } combinations([qw(Cr Hf Mo Nb Ta Ti V W Zr)], 4)'
これにより、含まれる4つの単語のすべての組み合わせから取得できる126のカテゴリが作成されます。各ディレクトリはN
名前の末尾に1つあります。コード配列の初期順序のため、個々の単語は常にアルファベット順にディレクトリ名に表示されます。
正しいPerlスクリプト:
#!/usr/bin/perl
use strict;
use warnings;
use English;
use Algorithm::Combinatorics qw(combinations);
# When interpolating a list in a string (@{$ARG} below), don't use a delimiter
local $LIST_SEPARATOR = "";
# Get all combinations, and create a directory for each combination
map { mkdir "@{$ARG}N" } combinations( [qw(Cr Hf Mo Nb Ta Ti V W Zr)], 4 );
これはほぼすぐに実行され、より多くの単語や結合された長さを含めるように簡単に拡張できます。
おそらくPythonでも非常に似たようなことができます。
再帰シェルの実装(再帰シェル機能は楽しみのために非常に効率的なケースがほとんどありません):
#!/bin/sh
build_combinations () {
set_size=$1
shift
if [ "$set_size" -eq 0 ]; then
printf 'N'
else
for token do
shift
for reminder in $(build_combinations "$(( set_size - 1 ))" "$@")
do
printf '%s%s\n' "$token" "$reminder"
done
done
fi
}
build_combinations 4 Cr Hf Mo Nb Ta Ti V W Zr | xargs mkdir
読んだ考えStudogの答えそしてあらゆる面からインスピレーションを得るStackOverflow質問への回答。
このソリューションの利点は、ディレクトリ名が常に終了することですN
。再帰的停止分岐は空のN
文字列の代わりに出力されるため、すべてが機能します。これがない場合(空の文字列または改行文字の印刷)、コマンド置換を含むループにはループする項目がなく、出力もありません(変数のデフォルト値のためIFS
)。
答え3
要素が最初からソートされているという事実を活用して、@n.stの答えを改善しました。私はこれがもう少し明確だと思います。
#!/bin/bash
elements=(Cr Hf Mo Nb Ta Ti V W Zr)
len=${#elements[@]}
(( a_end = len - 3 ))
(( b_end = len - 2 ))
(( c_end = len - 1 ))
(( d_end = len - 0 ))
(( a = 0 ))
while (( a < a_end )); do
(( b = a + 1 ))
while (( b < b_end )); do
(( c = b + 1 ))
while (( c < c_end )); do
(( d = c + 1 ))
while (( d < d_end )); do
mkdir "${elements[$a]}${elements[$b]}${elements[$c]}${elements[$d]}"
(( d++ ))
done
(( c++ ))
done
(( b++ ))
done
(( a++ ))
done
各内部ループのしきい値セクションは、囲むループの次の要素インデックスから始まります。これは、アイテムリストのすべての組み合わせを生成するのに非常に一般的なパターンです。
走る:
user@host:~/so$ time ./do.sh
real 0m0.140s
user 0m0.085s
sys 0m0.044s
そして
user@host:~/so$ ls -1d Cr* Hf* Mo* Nb* Ta* Ti* V* W* Zr* | wc -l
ls: cannot access 'V*': No such file or directory
ls: cannot access 'W*': No such file or directory
ls: cannot access 'Zr*': No such file or directory
126
答え4
冗長性をスキップするには、いくつかの手順を実行します。全体のプロセス速度が速くなります。
declare -a lst=( Cr Hf Mo Nb Ta Ti V W Zr ) # make an array
for a in ${lst[@]} # for each element
do for b in ${lst[@]:1} # for each but the 1st
do [[ "$b" > "$a" ]] || continue # keep them alphabetical and skip wasted work
for c in ${lst[@]:2} # for each but the first 2
do [[ "$c" > "$b" ]] || continue # keep them alphabetical and skip wasted work
for d in ${lst[@]:3} # for each but the first 3
do [[ "$d" > "$c" ]] || continue # keep them alphabetical and skip wasted work
mkdir "$a$b$c$d" && echo "Made: $a$b$c$d" || echo "Fail: $a$b$c$d"
done
done
done
done
重複スキップは、後続のループの開始時に適用されます。たとえば、外部ループは要素4にありますが、2番目のループはまだ要素3または4にあります。モノグラムではないのでスキップします。これはまた、重複が発生しないことを保証します。これにより、私のラップトップのgit bashに126の異なるディレクトリが作成されましたmkdir
。