我有 gawk (4.++) 的真正多维数组,我想将其“展平”(转换为 OLD/simulated – pre-gawk 4.x)数组。

arr["a"]["a"] = 21
arr["a"]["b"] = 21
arr["b"]["a"] = 2
arr["b"]["b"] = 3
arr["c"]["a"] = 4

转换为

arrFlat["a", "a"] = 21
arrFlat["a", "b"] = 21
arrFlat["b", "a"] = 2
arrFlat["b", "b"] = 3
arrFlat["c", "a"] = 4

我一直在研究处理多维数组的多个线程,包括

还有一些处理 walk_array 等问题。我确信这个问题以前有人问过,但我找不到正确的递归调用来将一个数组表示转换为另一个数组表示。我需要这个来简化多维数组的“按值排序”(数字或其他)。

显然,数组(源 arr)的索引“深度”并不像示例中那样限制为 2。

有谁能分享任何金块吗!

5

  • 考虑通过修改 walk_array() 函数的尝试来更新问题;另外,请使用有关多维数组来源的详细信息来更新问题(例如,它是否在块中硬编码BEGIN{}?它是否是从输入数据集构建的?其他什么?)


    – 

  • @markp-fuso,好的 – 我已经用正确的带引号的字符串更新了 OP 作为两个数组的索引 – 只是为了确保它正确地举例说明结构。真正的多维数组通过解析 syslog 文件进行进一步分析来填充,其中索引来自 syslog 中的某些字段,并且值由 gawk 本身计算/聚合/等。但这不应该有关系 – 只是一个例子。看着 Ed Morton 的 copy_array,想不出如何将其调整为“扁平化”而不是“复制”数组。希望我能得到正确的方向。


    – 


  • 2
    听起来“更简单”的方法可能是修改原始gawk代码以同时生成两个数组(arr[]arrFlat[]),因为代码(显然?)可以访问此时的索引;至于轻推……您需要通过将“当前”复合索引传递给函数来构建复合索引,然后将下一级索引附加到所述复合索引的末尾,然后再传递给下一个函数调用(Duh, Mark!?)


    – 


  • 2
    如果你展示你的(编码)努力,那么你获得帮助的机会就会增加,否则很有可能问题会因为需要更多细节、清晰度或重点而被关闭


    – 


  • 2
    你说的“我需要这个来简化多维数组的‘按值排序’(数字或其他)”是什么意思?听起来这才是你的真正问题,转换数组是你的解决方案,而不是你试图解决的实际问题,但也许有比展平数组更好的方法来解决该问题。你可能想问一个关于这个问题的问题。


    – 



最佳答案
3

我认为我找到了一种方法来调整@EdMorton“copy_array”的实现,使其变成可以“展平”真正的多维数组的东西:

$ cat flatten.awk
BEGIN {
    arr["a"]["a"] = 21
    arr["a"]["b"] = 21
    arr["b"]["a"] = 2
    arr["b"]["b"] = 3
    arr["c"]["a"] = 4
    arr["d"]["x"]["y"]= 90
    arr["d"]["x"]["z"]= 95


    walk_array(arr, "arr")

    PROCINFO["sorted_in"]="@val_num_asc"
    flatten_array(arr, flatten)

    print "----------"

    walk_array(flatten, "flatten")
}

function flatten_array(orig, flatten, idx,      i)
{
    for (i in orig) {
        if (isarray(orig[i])) {
            flatten_array(orig[i], flatten, (idx=="" && idx==0) ? i : (idx SUBSEP i) )
        }
        else {
            #printf("idx->[%s]\n", idx) > "/dev/stderr"
            flatten[idx SUBSEP i] = orig[i]
        }
    }
}

function walk_array(arr, name,      i)
{
    for (i in arr) {
        if (isarray(arr[i]))
            walk_array(arr[i], (name "[" i "]"))
        else
            printf("%s[%s] = %s\n", name, i, arr[i])
    }
}

$ gawk -f flatten.awk
arr[a][a] = 21
arr[a][b] = 21
arr[b][a] = 2
arr[b][b] = 3
arr[c][a] = 4
arr[d][x][y] = 90
arr[d][x][z] = 95
----------
flatten[ba] = 2
flatten[bb] = 3
flatten[ca] = 4
flatten[aa] = 21
flatten[ab] = 21
flatten[dxy] = 90
flatten[dxz] = 95

不是特别优雅,但是…除非其他人可以提供替代方案……

5

  • 1
    由于你必须以递归方式“取消构建”多维数组的索引,因此不确定你是否能够获得“更”优雅的效果;在构建原始数组的同时构建扁平数组可能是你能获得的最优雅的方式;由于flatten[]是一维数组,因此你可以放弃walk_array()对单个for i in flatten printf ...循环的调用;话虽如此……它有效,如果用户理解其工作原理walk_array()copy_array()那么这是一个易于理解的解决方案;赞


    – 

  • 1
    看起来不错,但""对于数组索引来说是一个有效值,因此,您不应该先进行第一次调用flatten_array(arr, flatten, ""),然后用 进行测试,而(length(idx) ? (idx SUBSEP i) : i)应该先进行第一次调用flatten_array(arr, flatten),然后测试变为(idx=="" && idx==0 ? i : (idx SUBSEP i))。您可能希望命名扁平数组,flat而不是只是flatten为了稍微提高清晰度。


    – 


  • 1
    @EdMorton 所有评论都很有效——感谢!


    – 

  • @EdMorton:idx=="" && idx==0– 一个数组不能同时具有空字符串("")和空值( idx == "" && idx == 0 )作为索引(否则arr[""]("" in arr)成为歧义语法),因此 IIRC 仅针对空字符串进行测试就足够了。(但将(1)数字或字符串零与(2)空字符串或空值配对是完全没问题的)


    – 

  • @RAREKpopManifesto 数组不可以,因为所有数组索引都是字符串,但函数参数可以,并且 OP 通过使用设置为idx == ""作为参数的函数来测试第一次与后续对函数的调用,但是由于是有效的数组索引,因此无法工作idx""""


    – 


使用标准作为新函数的模板flatten_array()

awk '
BEGIN { #### sample multi-dimensional array

        arr["a"]["a"] = 21
        arr["a"]["b"] = 21
        arr["b"]["a"] = 2
        arr["b"]["b"] = 3
        arr["c"]["a"] = 4
        arr["d"] = 19
        arr["e"]["f"]["g"]["h"] = 14

        flatten_array(arr, "arr")

        #### OP mentions needing to sort the (flattened) array by value ...

        print "############ @val_num_desc"

        PROCINFO["sorted_in"] = "@val_num_desc"                 # sort arrFlat[] by numeric value in descending order
        for (ndx in arrFlat)
            printf "arrFlat[%s] = %s\n", ndx,arrFlat[ndx]

        print "############ @val_num_asc"

        PROCINFO["sorted_in"] = "@val_num_asc"                  # sort arrFlat[] by numeric value in ascending order
        for (ndx in arrFlat)
            printf "arrFlat[%s] = %s\n", ndx,arrFlat[ndx]
      }

function flatten_array(arr, name, ndx,     i)
{
    for (i in arr) {
        new_ndx = ndx (ndx=="" ? "" : ",") i                    # "," is a literal comma; replace "," with SUBSEP if you wish to use the system default delimiter
        if (isarray(arr[i]))
            flatten_array(arr[i], (name "[" i "]"), new_ndx )
        else
            arrFlat[new_ndx] = arr[i]
    }
}
'

笔记:

  • 回复:索引分隔符 – 逗号与 SUBSEP:
  • new_ndx = ndx (ndx=="" ? "" : ",") i– 这使用文字逗号作为索引分隔符,这在打印数组索引时是可见的
  • new_ndx = ndx (ndx=="" ? "" : SUBSEP) i– 这将使用 awk 的标准索引分隔符\034,但它通常不会在打印到 stdout 时显示
  • "a" SUBSEP "b"通常会显示为,ab但是如果你通过管道传输,od -c你会看到a \034 b
  • 如果索引不包含逗号,那么 OP 应该能够根据显示(标准输出)要求使用任一分隔符(逗号、SUBSEP)
  • 回复:目标数组名称:
  • 目标数组名称( )在函数arrFlat[]中硬编码flatten_array()
  • 目标数组名称的参数化留给读者练习
  • 提示:请参阅,了解参数化数组名称的方法

这将生成:

############ @val_num_desc
arrFlat[a,b] = 21
arrFlat[a,a] = 21
arrFlat[d] = 19
arrFlat[e,f,g,h] = 14
arrFlat[c,a] = 4
arrFlat[b,b] = 3
arrFlat[b,a] = 2
############ @val_num_asc
arrFlat[b,a] = 2
arrFlat[b,b] = 3
arrFlat[c,a] = 4
arrFlat[e,f,g,h] = 14
arrFlat[d] = 19
arrFlat[a,a] = 21
arrFlat[a,b] = 21

2

  • 1
    @markp_fuso,谢谢 – 和我采取的方法差不多。感谢您的努力!


    – 

  • 1
    类似的评论ndx==""。您还可以考虑设置SUBSEP=","然后使用SUBSEP而不是硬编码",",并添加注释以将其替换为SUBSEP


    – 


假设您不需要能够以交互方式执行此操作,也许这就是您要找的。我试图弄清楚如何将数组作为输入传递给此脚本,因为我认为这无法以交互方式工作,尽管也许其他人可以采用它并对其进行一些改进。

编辑:好的,我刚刚写了 shell 脚本。

#!/bin/bash

# In case you need to do this programmatically 
if [ -z "$1" ]; then
    echo "Usage: $0 '<input array to flatten>'"
    echo "Example: $0 'arr[\"a\"][\"a\"]=21; \
               arr[\"a\"][\"b\"]=21; arr[\"b\"][\"a\"]=2; \
               arr[\"b\"][\"b\"]=3; arr[\"c\"][\"a\"]=4; \
               arr[\"c\"][\"d\"][\"e\"]=10;'"
    exit 1
fi


array_definition="$1"

# Temporary file 
gawk_script="temp_flatten_sort.awk"

# create a temporary custom hardcoded script
cat << EOF > "$gawk_script"
# Recursive function to flatten multi-dimensional arrays
function flatten_array(src, dest, prefix, key, flat_key) {
    for (key in src) {
        if (isarray(src[key])) {
            flat_key = (prefix == "") ? key : prefix "," key
            flatten_array(src[key], dest, flat_key)
        } else {
            flat_key = (prefix == "") ? key : prefix "," key
            dest[flat_key] = src[key]
        }
    }
}

BEGIN {
   
    $array_definition

    delete flattened_array  # Initialize the flattened array

   
    flatten_array(arr, flattened_array)

    # Sort by values
    n = 0
    for (k in flattened_array) {
        n++
        keys[n] = k
    }
    for (i = 1; i <= n; i++) {
        for (j = i + 1; j <= n; j++) {
            if (flattened_array[keys[i]] > flattened_array[keys[j]]) {
                temp = keys[i]
                keys[i] = keys[j]
                keys[j] = temp
            }
        }
    }

    # Print
    for (i = 1; i <= n; i++) {
        print "flattened_array[\"" keys[i] "\"] =", flattened_array[keys[i]]
    }
}
EOF

# exec
gawk -f "$gawk_script"
# cleanup
rm "$gawk_script"

现在,你可以运行它:

$./flatten.sh 'arr["a"]["a"]=21; arr["a"]["b"]=21; arr["b"]["a"]=2; arr["b"]["b"]=3; arr["c"]["a"]=4; arr["c"]["d"]["e"]=10;'

2

  • 感谢分享,@Chev_603 – 与我的尝试类似。


    – 

  • 嗯,其他的更好。我创建了一个包装脚本,以便您可以交互调用它。黑客之上的黑客 xD。


    –