Bash - 按升序对内存中关联数组的键进行排序?

标签 bash sorting associative-array

这个 Bash 函数是我最近编写的脚本的一部分(下面是带有示例数据的脚本的最小工作示例,包括函数的调用):

#!/bin/bash

declare -A result

stopSchedule='[{"stopId":41571,"lineId":1,"routeId":28,"remainingTime":[3450,8970,13170]},{"stopId":41571,"lineId":2,"routeId":29,"remainingTime":[1410,7950,12750]},{"stopId":41571,"lineId":2,"routeId":30,"remainingTime":[3030,9570,14370]},{"stopId":41571,"lineId":13,"routeId":36,"remainingTime":[3210,7410]},{"stopId":41571,"lineId":6,"routeId":39,"remainingTime":[3090,8790,13110]},{"stopId":41571,"lineId":8,"routeId":43,"remainingTime":[524,4590,9030]},{"stopId":41571,"lineId":8,"routeId":44,"remainingTime":[2190,6150,10590]},{"stopId":41571,"lineId":12,"routeId":52,"remainingTime":[1590]},{"stopId":41571,"lineId":10,"routeId":54,"remainingTime":[2970]}]'
stopsData='[{"id":41571,"areaId":1,"number":"44","name":"МБАЛ Добрич","translations":{},"lat":43.56184005737305,"lon":27.818910598754883,"note":""}]'
routesData='[{"id":44,"lineId":8,"direction":"Практикер-Депо","name":"Практикер-Депо","begin":1682629200,"end":0,"length":9876,"stopIds":[41674,41822,41630,41747,41750,41806,41681,41718,41745,41552,41576,41571,41731,41589,41627,41802,41643,41590,41625,41600,41694,41604,41676,41579,41654,41641,41595],"stopOffsets":[0,120,180,240,300,360,480,540,600,660,720,780,840,900,960,1020,1080,1140,1260,1320,1380,1440,1500,1560,1620,1680,1740]},{"id":43,"lineId":8,"direction":"Депо-Практикер","name":"Депо-Практикер","begin":1682629200,"end":0,"length":10076,"stopIds":[41595,41641,41655,41578,41675,41603,41695,41624,41591,41642,41803,41626,41588,41602,41571,41658,41746,41717,41682,41807,41749,41748,41724,41631,41629,41674],"stopOffsets":[0,60,120,180,240,300,360,420,480,540,600,660,720,780,900,960,1020,1080,1140,1260,1320,1380,1440,1500,1560,1680]},{"id":39,"lineId":6,"direction":"Балик-Дружба-Център","name":"Балик-Дружба-Център","begin":1682629200,"end":0,"length":10237,"stopIds":[41688,41689,41710,41559,41686,41791,41567,41703,41636,41743,41612,41613,41617,41529,41615,41548,41805,41681,41665,41773,41576,41571,41658,41623,41764,41627,41802],"stopOffsets":[0,60,120,180,300,360,480,540,660,720,840,900,960,1080,1140,1200,1260,1320,1380,1440,1500,1560,1620,1740,1800,1860,1920]},{"id":29,"lineId":2,"direction":"Балик-Старт","name":"Балик-Старт","begin":1682542801,"end":0,"length":11100,"stopIds":[41688,41689,41710,41786,41559,41686,41693,41634,41565,41632,41795,41793,41536,41652,41821,41619,41681,41718,41745,41552,41576,41571,41731,41589,41627,41767,41734,41684,41692],"stopOffsets":[0,60,120,180,300,360,480,540,600,720,780,840,900,960,1020,1140,1260,1380,1440,1500,1620,1680,1740,1800,1860,1980,2040,2100,2160]},{"id":28,"lineId":1,"direction":"АПК-Строител","name":"АПК-Строител","begin":1682542801,"end":0,"length":9655,"stopIds":[41687,41545,41719,41580,41645,41647,41571,41658,41746,41717,41682,41608,41701,41811,41582,41728,41649,41594,41787,41711,41568,41754,41792,41910,41693],"stopOffsets":[0,60,180,300,360,420,540,600,660,780,840,900,960,1020,1080,1140,1200,1260,1320,1380,1440,1560,1620,1681,1740]},{"id":30,"lineId":2,"direction":"Старт-Балик","name":"Старт-Балик","begin":1682542801,"end":0,"length":11140,"stopIds":[41692,41683,41733,41768,41626,41588,41602,41571,41658,41746,41717,41682,41620,41666,41653,41537,41794,41796,41633,41912,41910,41693,41685,41787,41711,41688],"stopOffsets":[0,60,120,180,300,360,420,540,600,660,720,780,900,960,1080,1140,1200,1320,1380,1500,1560,1620,1740,1860,1920,1980]},{"id":52,"lineId":12,"direction":"Липите-Център","name":"Липите-Център","begin":1682629200,"end":0,"length":7705,"stopIds":[41669,41677,41679,41722,41551,41660,41799,41698,41818,41671,41605,41781,41576,41571,41658,41746,41717,41682,41608,41701,41811,41716],"stopOffsets":[0,120,180,300,360,420,540,600,660,720,840,900,960,1020,1080,1140,1260,1320,1380,1440,1500,1620]},{"id":34,"lineId":4,"direction":"Гробищен парк-Депо","name":"Гробищен парк-Депо","begin":1682629200,"end":0,"length":14352,"stopIds":[41592,41663,41809,41574,41724,41631,41629,41674,41628,41541,41616,41528,41618,41564,41594,41787,41711,41568,41754,41566,41632,41695,41789,41763,41570,41639,41534,41713,41759,41725,41585,41579,41654,41641,41595],"stopOffsets":[0,60,120,180,300,360,420,540,600,660,780,840,960,1080,1200,1320,1380,1440,1560,1680,1800,1860,1920,2040,2100,2160,2220,2280,2340,2400,2460,2520,2640,2700,2760]},{"id":54,"lineId":10,"direction":"Добротица-Рилци","name":"Добротица-Рилци","begin":1691960400,"end":0,"length":13838,"stopIds":[41724,41631,41629,41541,41805,41620,41666,41820,41653,41537,41753,41700,41681,41665,41773,41576,41571,41658,41623,41815,41709,41803,41829,41752,41766,41812,41816,41729,41690],"stopOffsets":[0,60,120,180,240,300,360,370,480,540,600,720,840,900,960,1020,1080,1140,1200,1260,1320,1440,1500,1620,1740,1860,1920,2040,2100]},{"id":36,"lineId":13,"direction":"Гробищен парк-Депо","name":"Гробищен парк-Депо","begin":1682629200,"end":0,"length":9814,"stopIds":[41592,41663,41809,41574,41747,41750,41806,41681,41665,41773,41571,41731,41589,41627,41715,41547,41570,41639,41534,41713,41759,41725,41585,41579,41654,41595],"stopOffsets":[0,60,120,180,300,360,480,600,720,780,900,960,1020,1080,1140,1200,1260,1320,1380,1440,1500,1560,1620,1680,1740,1800]}]'

function stopNameAndNumById {
    echo $stopsData | jq -r --arg id "$1" '.[] | select(.id == ($id | tonumber)) | "\(.number),\(.name)"'
}

function collectLinesDesc {
    if [ -z "$linesDesc" ]; then
        linesDesc='[{"id":13,"kind":"BUS","number":"4а","name":"Линия 4A","nightly":false,"routeIds":[35,36],"type":"URBAN","carrier":"X"}]'
    fi
}

function linesFromStopId {
    IFS=', ' read -ra id_arr <<< "$1"
    i=true

    for id in "${id_arr[@]}"; do
        stopNumName=$(stopNameAndNumById "$id")
        for row in $(echo "$stopSchedule" | jq -c -r '.[] | {lineId, remainingTime, routeId}'); do
            lineId=$(echo "$row" | jq -r '.lineId')
            routeId=$(echo "$row" | jq -r '.routeId')
            remainingTime=$(echo "$row" | jq -r '.remainingTime[0]')
            routeName=$(echo "$routesData" | jq -r '.[] | select(.id == '$routeId') | .name')
            remainingTimeInMinutes=$((remainingTime / 60))
            key=$lineId
            while [[ -n "${result[$key]}" ]]; do
                key="${key}_"
            done
            result["$key"]="$remainingTimeInMinutes,$routeName"
        done
        [ $i = true ] && i=false || echo
        echo -e "Спирка $(cut -d',' -f2 <<< $stopNumName) ($(cut -d',' -f1 <<< $stopNumName))\n"
        for key in "${!result[@]}"; do
            newKey="$key"
            if [[ $key == *_ ]]; then
                newKey="${key%_}"
            fi
            IFS=',' read -ra values <<< "${result[$key]}"
            if [ $newKey -gt "12" ]; then
                collectLinesDesc
                newKey=$(echo $linesDesc | jq -r --arg id "$newKey" '.[] | select(.id == ($id | tonumber)) | .number' | awk '{ print toupper($0) }')
            fi
            echo "Линия $newKey (${values[1]}) - ${values[0]} минути"
        done
        result=()
    done
}

linesFromStopId "41571"

我正在寻找一种优雅的方法来在内存中按升序对 $result 关联数组的键进行排序,而不是在将数据打印到输出时进行排序。

while [[ -n "${result[$key]}"]] 之前; do 循环,保证数组的键只包含数字(一位或两位数字长)。

collectLinesDesc 从 RESTful API 收集公交线路的描述(如果我们之前尚未收集过的话)。

最小工作示例的当前输出将是:

Спирка МБАЛ Добрич (44)

Линия 8 (Практикер-Депо) - 0 м.
Линия 8 (Депо-Практикер) - 0 м.
Линия 6 (Балик-Дружба-Център) - 0 м.
Линия 2 (Балик-Старт) - 0 м.
Линия 1 (АПК-Строител) - 0 м.
Линия 2 (Старт-Балик) - 0 м.
Линия 12 (Липите-Център) - 0 м.
Линия 4А (Гробищен парк-Депо) - 0 м.
Линия 10 (Добротица-Рилци) - 0 м.

预期输出(“Линия”后面的数字按升序排列):

Спирка МБАЛ Добрич (44)

Линия 1 (Практикер-Депо) - 0 м.
Линия 2 (Депо-Практикер) - 0 м.
Линия 2 (Балик-Дружба-Център) - 0 м.
Линия 6 (Балик-Старт) - 0 м.
Линия 8 (АПК-Строител) - 0 м.
Линия 8 (Старт-Балик) - 0 м.
Линия 10 (Липите-Център) - 0 м.
Линия 12 (Гробищен парк-Депо) - 0 м.
Линия 4A (Добротица-Рилци) - 0 м.

(这里的“4A”是一个显示值,在数组中会是一个大于12的数字,所以升序排列总是在最后,所以逻辑和显示逻辑一致)

我能想到的一种方法是让 jq 输出按 lineId 排序的 JSON 中的数据,然后使用第二个索引数组来保持数据的顺序条目,然后当我在最终输出数据的循环中迭代它时,使用该索引数组作为关联数组 $result 的键,但是有没有更好/更短/更简单的方法来实现在 Bash 中也一样吗?

最佳答案

由于键被定义为小整数,因此您可以使用 -a 而不是 -A 来简单地保持结果(部分)排序>,因为索引数组是通过增加数字索引来遍历的。

将 key 存储为放大值(例如乘以 1000)。每当发生碰撞时,不要附加 _,而是添加 1。

稍后使用这些键时,请缩小(例如除以 1000)。由于 bash 使用整数运算,因此这将删除任何小数部分,从而给出原始的预碰撞 key 。

例如:

# declare -A result
declare -a result
# ...
            # key=$lineId
            (( key = lineId*1000 ))
            while [[ -n "${result[$key]}" ]]; do
                # key="${key}_"
                (( key++ ))
            done
# ...
        for key in "${!result[@]}"; do
            # newKey="$key"
            # if [[ $key == *_ ]]; then
            #    newKey="${key%_}"
            # fi
            (( newKey = key/1000 ))
# ...

但这会产生与请求的不同的输出:

Спирка МБАЛ Добрич (44)

Линия 1 (АПК-Строител) - 57 минути
Линия 2 (Балик-Старт) - 23 минути
Линия 2 (Старт-Балик) - 50 минути
Линия 6 (Балик-Дружба-Център) - 51 минути
Линия 8 (Депо-Практикер) - 8 минути
Линия 8 (Практикер-Депо) - 36 минути
Линия 10 (Добротица-Рилци) - 49 минути
Линия 12 (Липите-Център) - 26 минути
Линия 4А (Гробищен парк-Депо) - 53 минути

目前还不清楚为什么您期望对键进行排序会更改存储在数组中的值。


原始代码看起来在存在多个冲突的情况下可能会出现错误(例如,考虑一下如果将 6 替换为 8 中会发生什么)数据)。

关于Bash - 按升序对内存中关联数组的键进行排序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77164406/

相关文章:

c - 如何在apache下从bash脚本启动c程序

php - 如果用户在 AJAX 请求结束之前退出浏览器或更改页面会发生什么

regex - Bash Regex 接受 [Number],但我只想接受 Number

iphone - Objective C 排序数组

datetime - 为什么 map[time.Time]string 有时不起作用?

linux - 使用 sed 或 awk 或新安装的 Debian 9 中的工具,如何添加未修剪的文本?

c - 快速排序算法表现异常

python - Django - 按日期而不是日期时间对查询集进行排序

javascript - 从 Javascript 中的关联数组中删除项目

associative-array - ksh 关联数组