연관 배열이 있다고 가정해 보겠습니다 bash
.
declare -A hash
hash=(
["foo"]=aa
["bar"]=bb
["baz"]=aa
["quux"]=bb
["wibble"]=cc
["wobble"]=aa
)
키와 값이 모두 나에게 알려지지 않은 경우(실제 데이터는 외부 소스에서 읽음)
모든 고유 값에 대해 루프에서 수행할 수 있도록 동일한 값에 해당하는 키 배열을 어떻게 만들 수 있습니까?
printf 'Value "%s" is present with the following keys: %s\n' "$value" "${keys[*]}"
출력을 얻습니다(반드시 이 순서대로일 필요는 없음).
Value "aa" is present with the following keys: foo baz wobble
Value "bb" is present with the following keys: bar quux
Value "cc" is present with the following keys: wibble
중요한 점은 키가 keys
배열에 별도의 요소로 저장되므로 텍스트 문자열에서 구문 분석할 필요가 없다는 것입니다.
나는 다음과 같은 일을 할 수 있습니다
declare -A seen
seen=()
for value in "${hash[@]}"; do
if [ -n "${seen[$value]}" ]; then
continue
fi
keys=()
for key in "${!hash[@]}"; do
if [ "${hash[$key]}" = "$value" ]; then
keys+=( "$key" )
fi
done
printf 'Value "%s" is present with the following keys: %s\n' \
"$value" "${keys[*]}"
seen[$value]=1
done
하지만 이중 루프의 효율성은 다소 낮은 것 같습니다.
배열 구문이 누락되었나요 bash
?
예를 들어 이렇게 하면 zsh
더 강력한 배열 조작 도구가 제공됩니까?
Perl에서는 할 것입니다
my %hash = (
'foo' => 'aa',
'bar' => 'bb',
'baz' => 'aa',
'quux' => 'bb',
'wibble' => 'cc',
'wobble' => 'aa'
);
my %keys;
while ( my ( $key, $value ) = each(%hash) ) {
push( @{ $keys{$value} }, $key );
}
foreach my $value ( keys(%keys) ) {
printf( "Value \"%s\" is present with the following keys: %s\n",
$value, join( " ", @{ $keys{$value} } ) );
}
하지만 bash
연관 배열은 배열을 담을 수 없습니다...
나는 또한 어떤 형태의 간접 인덱싱(위에서 말한 값을 읽으면서 인덱스 배열을 구축하는 것)을 사용할 수 있는 구식 솔루션에도 관심이 있습니다 hash
. 선형 시간 내에 이를 수행할 수 있는 방법이 있어야 할 것 같습니다.
답변1
다루기 힘든
키<=> 값 반전
에서 zsh
해시를 정의하는 주요 구문은 다음과 같습니다 hash=(k1 v1 k2 v2...)
( perl
최신 버전에서는 키 참조 시 변경 사항이 있지만 호환성을 위해 어색한 ksh93/bash 구문도 지원합니다).
keys=( "${(@k)hash}" )
values=( "${(@v)hash}" )
typeset -A reversed
reversed=( "${(@)values:^keys}" ) # array zipping operator
또는 Oa
매개변수 확장 플래그를 사용하여 키+값 목록의 순서를 반대로 바꿉니다.
typeset -A reversed
reversed=( "${(@kvOa)hash}" )
또는 루프를 사용하십시오.
for k v ( "${(@kv}hash}" ) reversed[$v]=$k
큰따옴표는 @
빈 키와 값을 유지하기 위한 것입니다( bash
연관 배열은 빈 키를 지원하지 않습니다). 연관 배열의 요소는 특정 순서 없이 확장되므로 의 여러 요소가 $hash
동일한 값(결국 의 키가 됨 $reversed
)을 갖는 경우 어떤 키가 의 값으로 사용될지 알 수 없습니다 $reversed
.
당신의 주기를 위해
해시 첨자 플래그를 사용 R
하여 키가 아닌 값을 기반으로 요소를 가져온 다음 e
(와일드카드가 아닌) 정확한 일치와 결합한 다음 k
매개변수 확장 플래그를 사용하여 이러한 요소에 대한 키를 가져올 수 있습니다.
for value ("${(@u)hash}")
print -r "elements with '$value' as value: ${(@k)hash[(Re)$value]}"
귀하의 Perl 방법
zsh
배열의 배열은 지원되지 않지만 ( 와 반대로 ksh93
) 해당 변수에는 NUL 바이트가 포함될 수 있으므로 NUL 바이트가 포함되지 않은 요소를 분리하는 데 사용하거나 참조를 사용하여 목록을 인코딩 ${(q)var}
/ 디코딩할 수 있습니다.${(Q)${(z)var}}
typeset -A seen
for k v ("${(@kv)hash}")
seen[$v]+=" ${(q)k}"
for k v ("${(@kv)seen}")
print -r "elements with '$k' as value: ${(Q@)${(z)v}}"
크쉬 93
ksh93은 1993년에 연관 배열을 도입한 최초의 셸이었습니다. 전역 할당의 구문은 이를 프로그래밍 방식으로 수행하는 것이 매우 어렵다는 것을 의미 zsh
하지만 적어도 ksh93에서는 ksh93
다소 합리적인 복잡한 중첩 데이터 구조를 지원합니다.
특히 여기서 ksh93은 해시 요소의 값으로 배열을 지원하므로 다음을 수행할 수 있습니다.
typeset -A seen
for k in "${!hash[@]}"; do
seen[${hash[$k]}]+=("$k")
done
for k in "${!seen[@]}"; do
print -r "elements with '$k' as value ${x[$k][@]}"
done
세게 때리다
bash
연관 배열에 대한 지원은 수십 년 후에 추가되었으며 ksh93 구문은 복사되었지만 다른 고급 데이터 구조는 복사되지 않았으며 zsh의 고급 매개변수 확산 연산자도 복사되지 않았습니다.
당신 은 bash
사용할 수 있습니다참고문헌 목록zsh에 언급된 방법을 사용 printf %q
하거나 최신 버전을 사용하십시오 ${var@Q}
.
typeset -A seen
for k in "${!hash[@]}"; do
printf -v quoted_k %q "$k"
seen[${hash[$k]}]+=" $quoted_k"
done
for k in "${!seen[@]}"; do
eval "elements=(${seen[$k]})"
echo -E "elements with '$k' as value: ${elements[@]}"
done
그러나 앞서 언급한 것처럼 연관 배열은 null 값을 키로 지원하지 않기 때문에 일부 값이 null이면 bash
연관 배열이 작동하지 않습니다. 예를 들어 빈 문자열을 일부 자리 표시자로 바꾸 거나 나중에 표시하기 위해 제거할 키 앞에 일부 문자를 추가하도록 $hash
선택할 수 있습니다 .<EMPTY>
답변2
아시다시피 걸림돌은 인덱스 배열의 이름을 (다른) 변수의 값으로 사용할 때 인덱스 배열의 전체 값을 가져오는 것입니다. 중간 값보다 작은 값으로 이를 수행 ${v[@]}
한 다음 eval을 사용할 수 없습니다 . 따라서 접근 방식은 다음과 같습니다.
declare -A keys
N=0 # counter for the index variables IX1, IX2, IX3, ...
for key in "${!hash[@]}"; do
value="${hash[$key]}"
if [ -z "${keys[$value]}" ] ; then N=$((N+1)) ; keys[$value]=IX$N ; fi
index="${keys[$value]}" # 'index' is now name of index variable
X="\${$index[@]}"
eval "$index=( $X $key )" # adding next key to it
done
for value in "${!keys[@]}" ; do
index=${keys[$value]}
X="\${$index[@]}"
printf "Value %s is present with the following keys: %s\n" \
"$value" "$(eval echo "$X")"
done
이것은 리눅스용입니다 bash
. 발견한 다양한 값 등에 대한 인덱스 배열을 생성하고 IX1
해당 IX2
이름을 keys
값의 연관 배열에 저장합니다. 따라서 ${keys[$value]}
이 값의 키를 보유하는 인덱스 배열의 이름입니다. 그런 다음 X
값 모음으로 설정된 변수 "액세스 문구"는 eval echo "$X"
공백으로 구분된 값으로 변환할 수 있도록 설정됩니다. 예를 들어 값에 array 인덱스가 있으면 string 이 IX2
됩니다 .X
${IX2[@]}
zsh
배열의 배열을 지원하지 않는다는 점에서 비슷하다고 생각하므로 비슷한 솔루션이 필요할 수 있습니다. IMHO 액세스 문구가 zsh
조금 더 명확해졌습니다.
답변3
또 다른 접근 방식은 두 개의 인덱스 배열에 데이터를 저장하는 것입니다. 그 중 하나는 고유한 값을 가지며 두 번째 값은 중복/중복 값을 포함할 수 있습니다. 두 번째 배열에서 반복되는 요소는 키로, 첫 번째 배열의 해당 항목은 공백으로 구분된 값으로 연관 배열을 구성할 수 있습니다.
아래 코드는 루프 사용을 피하고 루프 eval
만 사용합니다.for
암호
source=("foo" "bar" "baz" "quux" "wibble" "wobble")
destination=("aa" "bb" "aa" "bb" "cc" "aa")
declare -A inverted_array
# Printout formatted arrays with headers
printf '%-10s %-20s %-30s\n' "Index" "Destination" "Source"
for ((i = ((${#source[@]} - 1)); i >= 0; i--)); do
source_i="${source["$i"]}"
destination_i="${destination["$i"]}"
printf '%-10s %-20s %-30s\n' "$i" "$destination_i" "$source_i"
tempstring="${inverted_array["$destination_i"]}"
inverted_array["$destination_i"]="$source_i"" ""$tempstring"
done
echo
printf '%-10s %-20s\n' "Key" "Value"
# Remove the last space from the every element of the resulted array and print it formatted
for index in "${!inverted_array[@]}"; do
removespace="${inverted_array[$index]}"
removespace=${removespace%" "}
inverted_array["$index"]="$removespace"
printf '%-10s %-20s\n' "$index" "${inverted_array["$index"]}"
done
echo
산출:
Index Destination Source
5 aa wobble
4 cc wibble
3 bb quux
2 aa baz
1 bb bar
0 aa foo
Key Value
bb bar quux
aa foo baz wobble
cc wibble
PS 위의 예를 더 확장/시연하기 위해 아래에는 두 개의 배열을 생성하는 코드가 있습니다. 그 중 하나에는 source
5자 길이의 임의 문자가 포함되어 있고, 두 번째에는 destination
값으로 임의 문자 0-9a-f만 포함되어 있습니다.
각각 100개 요소로 구성된 두 개의 인덱스 배열을 생성하는 코드:
for ((i = 0; i < 100; i++)); do
source+=("$(tr -dc 'a-zA-Z' </dev/urandom | head -c 5)")
destination+=("$(tr -dc '0-9a-f' </dev/urandom | head -c 1)")
done
위 코드를 사용하여 연관 배열을 생성하면 결과는 다음과 같습니다.
Key Value
9 soxRg PmUZv eOmkR cFuie wmlsO EdNdM XuloF SSfjE oHfnc FcIKE
8 hLRpa eXODM wRGkh MwZUW lfWaE WQiwU IHGjj nNEcg
7 Pdxmd ywPZQ lPQIx TKawd VTyqR
6 lIwla Docxu Dimnz ovywP HwzQv
5 ObezH tyFNS BqnWp CFlMk dDkYC
4 rNzLM GVLXH AgZSL ionEp tngzQ
3 yRfqn IdTne
2 sMSxm WKmGm ELjOL pqxqw stWnL
1 yxycd EAGRg WxBle ItLNz WUdVu shUaC qDNIO xIwdM
0 OXdHh VQcsT AFvFq sgrYK AQrjZ
f uXJor IkwDr AOGSK hYMGE PQQfu tUjbh NwrVi iqZKO hHLYU
e XhMpB TCCFr ATbxa
d ReqMh lbxFx bGivd YCGtv lAtZj
c Kvthr itbaF wIbaf LwUiB VTInv xvWbC gpyRZ
b riimt EkLbv QYpZq kgvTi tOJRH jZykW pRuMD FJVXZ xipDx wkCMN
a REJnb Xtunv raimk SemnZ xMwno EXwKi sekmg WUKhx
답변4
사용행복하다(이전 Perl_6)
my %hash = (
'foo' => 'aa',
'bar' => 'bb',
'baz' => 'aa',
'quux' => 'bb',
'wibble' => 'cc',
'wobble' => 'aa'
);
my %inverted = %hash.classify( { .value }, :as{ .key } );
for %inverted.kv -> $k, $v {
printf( "Value \"%s\" is present with the following keys: %s\n",
$k, $v ) };
산출:
Value "aa" is present with the following keys: wobble baz foo
Value "bb" is present with the following keys: bar quux
Value "cc" is present with the following keys: wibble
간단히 말해서 여기서 작업의 핵심은 구성 요소를 기반으로 요소를 테스트 하고 등가 값을 분류하는 Raku의 classify
루틴을 사용하여 수행하는 것입니다 .%hash
.value
:as
.key
대부분의 작업을 수행하는 한 줄의 코드는 다음과 같습니다(Raku REPL에서 실행 가능).
.say for %hash.classify: {.value}, :as{.key};
cc => [wibble]
aa => [baz wobble foo]
bb => [quux bar]
classify
부록 (1): Raku에는 비슷한 기능이 있습니다 categorize
. 위 코드의 경우에도 동일한 결과 classify
로 대체될 수 있습니다 .categorize
%hash
부록 (2): 원본 개체를 재구성 하려면 해당 루틴을 %inverted
호출하면 됩니다 invert
. 문서에 따르면:invert
"반전과 반전의 차이점은 antipairs
목록 값을 여러 쌍으로 확장한다는 것입니다."
https://docs.raku.org/routine/classify
https://docs.raku.org/routine/categorize
https://docs.raku.org/routine/invert
https://raku.org