if条件を使用してforループでアルファベット順を強制します。

if条件を使用してforループでアルファベット順を強制します。

多くのフォルダを作成し、その中でいくつかのことをしたいと思います。フォルダ名は、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

関連情報