AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 506891
Accepted
Kusalananda
Kusalananda
Asked: 2019-03-18 14:48:05 +0800 CST2019-03-18 14:48:05 +0800 CST 2019-03-18 14:48:05 +0800 CST

反转关联数组

  • 772

假设我有一个关联数组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上面所说的值时构建一组索引数组?)。感觉应该有一种方法可以在线性时间内做到这一点。

bash scripting
  • 4 4 个回答
  • 1408 Views

4 个回答

  • Voted
  1. Best Answer
    Stéphane Chazelas
    2019-03-18T23:38:06+08:002019-03-18T23:38:06+08:00

    zsh

    反转键 <=> 值

    in zsh,定义哈希的主要语法hash=(k1 v1 k2 v2...)类似于 in perl(较新的版本还支持笨拙的 ksh93/bash 语法以实现兼容性,尽管在引用键时会有所变化)

    keys=( "${(@k)hash}" )
    values=( "${(@v)hash}" )
    
    typeset -A reversed
    reversed=( "${(@)values:^keys}" ) # array zipping operator
    

    或者使用Oa参数扩展标志来反转key+value列表的顺序:

    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}}"
    

    ksh93

    ksh93 是 1993 年第一个引入关联数组的 shell。分配值的语法作为一个整体意味着很难以编程方式执行它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
    

    然而,如前所述,关联数组不支持将空值作为键,因此如果某些' 值是空的bash,它将不起作用。$hash您可以选择用一些占位符替换空字符串,<EMPTY>或者在密钥前面加上一些您稍后会剥离以显示的字符。

    • 13
  2. Ralph Rönnquist
    2019-03-18T20:09:05+08:002019-03-18T20:09:05+08:00

    我相信您知道,绊脚石是在将索引数组的名称作为(另一个)变量的值时获取索引数组的整个值。我不能只使用一个中间值,其值变为格式${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
    

    这是针对 Linux 的bash。它为遇到的各种值创建索引数组IX1、IX2等,并将这些名称保存在keys值的关联数组中。因此,${keys[$value]}是保存该值的键的索引数组的名称。然后X设置为值集合的变量“访问短语”,允许eval echo "$X"转换为具有空格分隔的值。例如,如果一个值有索引数组IX2,那么X将是字符串${IX2[@]}。

    我相信zsh在不支持数组数组方面是相似的,所以它可能需要一个类似的解决方案。不过恕我直言,其中的访问短语zsh稍微清晰一些。

    • 3
  3. jubilatious1
    2022-03-17T09:06:41+08:002022-03-17T09:06:41+08:00

    使用Raku(以前称为 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]
    

    附录(1):Raku 有一个类似于classify已知的功能categorize。对于上面的代码,classify可以categorize用相同的结果替换。

    附录(2):如果%hash要从 重构原始对象%inverted,可以调用invert上面的例程。根据文档: “和之间的区别在于invert 将列表值扩展为多对。”invertantipairs

    https://docs.raku.org/routine/classify
    https://docs.raku.org/routine/categorize
    https://docs.raku.org/routine/invert
    https://raku.org

    • 1
  4. Invisible999
    2020-11-30T08:09:54+08:002020-11-30T08:09:54+08:00

    这是另一种方法 - 将数据放在两个索引数组中。其中一个具有唯一值,第二个可以包含重复/重复的值。可以构造关联数组,该数组将第二个数组中的重复元素作为键,将第一个数组中的相应条目作为由空格分隔的值。

    下面的代码避免使用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 个 char 长的随机字符,第二个 -destination包含一个值,仅随机一个 char 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
    
    • 0

相关问题

  • 通过命令的标准输出以编程方式导出环境变量[重复]

  • 从文本文件传递变量的奇怪问题

  • 虽然行读取保持转义空间?

  • `tee` 和 `bash` 进程替换顺序

  • 运行一个非常慢的脚本直到它成功

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve