空白で区切られた単語のリストから、互いに一定の数の値が離れているいくつかの値を繰り返す最も効率的な方法を見つけようとしています(配列を使用したくありません)。例えば、
list="1 ant bat 5 cat dingo 6 emu fish 9 gecko hare 15 i j"
そのため、リストを繰り返して1、5、6、9、15にのみアクセスできるようにしたいと思います。
編集する:リストから取得する値がリストの残りの部分と異なる形式である必要はないことを明確にする必要があります。これはリスト内の場所(この場合は場所1、4、7...)でのみ特別です。したがって、リストにすることができますが、1 2 3 5 9 8 6 90 84 9 3 2 15 75 55
まだ同じ数が欲しいです。そして、私はリストの長さがわからないと仮定し、これを行うことができるようにしたいと思います。
今まで私が考えることができる方法は次のとおりです。
方法1
set $list
found=false
find=9
count=1
while [ $count -lt $# ]; do
if [ "${@:count:1}" -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
done
方法2
set list
found=false
find=9
while [ $# ne 0 ]; do
if [ $1 -eq $find ]; then
found=true
break
fi
shift 3
done
方法3 私は配管がこれを最悪の選択にすると確信していますが、好奇心が強いので、セットを使用せずにこれを行う方法を見つけようとしています。
found=false
find=9
count=1
num=`echo $list | cut -d ' ' -f$count`
while [ -n "$num" ]; do
if [ $num -eq $find ]; then
found=true
break
fi
count=`expr $count + 3`
num=`echo $list | cut -d ' ' -f$count`
done
それでは、最も効率的な方法は何ですか?それとももっと簡単な方法を見逃していますか?
答え1
ソフトウェア最適化の最初の規則:いいえ。
問題があることを知るまで、プログラムの速度について考える必要はありません。リストの長さがこの程度であるか、項目が100〜1000個程度の場合は、時間がどれくらいかかるかさえ気付かないでしょう。違いよりも最適化について考えるのにより多くの時間を費やすことができます。
2番目のルール:測定する。
これは確かな方法であり、システムに答えを提供する方法です。特に貝殻は種類が異なり、すべて同じではありません。 1つのシェルに固有の回答があなたのシェルに適用されない場合があります。
大規模なプログラムでは、ここでも分析も行われます。最も遅い部分は、あなたが想像しているものと異なる場合があります。
3. シェルスクリプト最適化の最初の規則:シェルを使用しないでください。
はい、本当にそうです。多くのシェルはすぐに設計されておらず(外部プログラムを起動する必要がないため)、毎回ソースラインを再解析することもできます。
代わりにawkやPerlなどを使用してください。私が行った単純なマイクロベンチマークでは、
awk
I / Oなしで単純なループを実行するのが一般的なシェルよりも数十倍速かったです。ただし、シェルを使用する場合は、外部コマンドではなくシェルの組み込み機能を使用してください。ここでの使用は、
expr
私のシステムで見つけたどのシェルにも組み込まれていませんが、標準算術拡張で置き換えることができます。たとえばi=$((i+1))
、。前の例で使用したものを標準パラメータ拡張で置き換えることもできます。i=$(expr $i + 1)
i
cut
問題に手順1と2を適用する必要があります。
答え2
とても簡単ですawk
。これにより、すべての長さ入力の4つのフィールドごとに値が提供されます。
$ awk -F' ' '{for( i=1;i<=NF;i+=3) { printf( "%s%s", $i, OFS ) }; printf( "\n" ) }' <<< $list
1 5 6 9 15
これは、(レコード内のフィールド数)awk
などの組み込み変数を利用し、いくつかの簡単なループを実行してフィールドを繰り返すことで、フィールド数を事前に知らなくても必要なフィールドを提供できます。NF
for
または、例で指定された特定のフィールドのみが必要な場合:
$ awk -F' ' '{ print $1, $4, $7, $10, $13 }' <<< $list
1 5 6 9 15
効率に関する質問の場合、最も簡単な方法はこの方法または他のすべての方法をテストし、それを使用してView System Call Flowなどのツールをtime
使用することもできます。strace
使用法はtime
次のとおりです。
$ time ./script.sh
real 0m0.025s
user 0m0.004s
sys 0m0.008s
さまざまな方法間の結果を比較して、時間の点で最も効率的な方法を確認できます。他の効率指標には異なるツールを使用できます。
答え3
この回答では、ベンチマークではなく一般的なアドバイスだけを提供します。ベンチマークは、パフォーマンスに関する質問に確実に答えることができる唯一の方法です。ところで、あなたが言わないから。どのくらいあなたが作業しているデータとどのくらいの頻度これにより、便利なベンチマークができなくなります。一般に、10のプロジェクトでより効率的になることと1,000,000のプロジェクトでより効率的になることとの間には違いがあります。
通常、純粋なシェルコードにループが含まれていない限り、外部コマンドを呼び出すと、純粋なシェル構成を使用して操作を実行するよりもコストがかかります。一方、大きな文字列または多数の文字列を繰り返すシェルループは、特別なツールを呼び出すよりも遅くなる可能性があります。たとえば、ループ呼び出しはcut
実際にはかなり遅くなる可能性がありますが、1回の呼び出しで完全な操作を実行する方法を見つけると、cut
シェルで文字列操作を使用して同じ操作を実行するよりも高速になります。
カットオフポイントはシステムごとに大きく異なる場合があります。これは、カーネル、カーネルスケジューラの設定方法、外部実行可能ファイルを含むファイルシステム、現在のCPUとメモリ圧力、およびその他の多くの要因によって異なります。
expr
パフォーマンスに全く興味がある場合は、算術を実行するために電話しないでください。実際にexpr
算術を実行するために呼び出しはまったく行われません。シェルにexpr
。
shには存在しないbash構成を使用しているので、bashを使用しているようです。では、とにかく配列を使用しないのはなぜですか?配列は最も自然な解決策であり、おそらく最速の解決策です。配列インデックスは0から始まります。
list=(1 2 3 5 9 8 6 90 84 9 3 2 15 75 55)
for ((count = 0; count += 3; count < ${#list[@]})); do
echo "${list[$count]}"
done
sh
sh を使用している場合、システムが bash の代わりに dash または ksh を使用すると、スクリプトが高速になる可能性があります。 shを使用すると、名前付き配列を取得できませんが、を使用して設定できる位置引数配列の1つを取得できますset
。実行時まで不明な場所の要素にアクセスするには、を使用する必要がありますeval
(正しい引用に注意してください)。
# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
count=1
while [ $count -le $# ]; do
eval "value=\${$count}"
echo "$value"
count=$((count+1))
done
配列に一度だけ左から右へ(特定の値をスキップして)アクセスしたい場合は、shift
代わりに変数インデックスを使用できます。
# List elements must not contain whitespace or ?*\[
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
set $list
while [ $# -ge 1 ]; do
echo "$1"
shift && shift && shift
done
どの方法が速いかは、シェルと要素の数によって異なります。
別の可能性は、文字列処理を使用することです。位置パラメータを使用しないという利点があり、他の目的に使用できます。データ量が多いと速度が遅くなりますが、量が少ないと顕著な違いは発生しません。
# List elements must be separated by a single space (not arbitrary whitespace)
list='1 2 3 5 9 8 6 90 84 9 3 2 15 75 55'
while [ -n "$list" ]; do
echo "${list% *}"
case "$list" in *\ *\ *\ *) :;; *) break;; esac
list="${list#* * * }"
done
答え4
おそらくこれ?
cut -d' ' -f1,4,7,10,13 <<<$list
1 5 6 9 15