如何使用awk对单词的字符进行排序?

How to sort the characters of a word using awk?

我似乎找不到任何方法来根据 awk 中的字符对单词进行排序。 例如,如果单词是“hello”,那么其排序后的等价词是“ehllo”。如何在 awk 中实现这一点?

gawk 会更可行,它包括 asort 函数来对数组进行排序:

awk 'BEGIN{FS=OFS=ORS=""}{split([=10=],a);asort(a);for(i in a)print a[i]}'<<<hello

这输出:

ehllo

演示:https://ideone.com/ylWQLJ

您需要编写一个函数来对单词中的字母进行排序(参见:https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html):

function siw(word,        result, arr, arrlen, arridx) {
    split(word, arr, "")
    arrlen = asort(arr)
    for (arridx = 1; arridx <= arrlen; arridx++) {
        result = result arr[arridx]
    }
    return result
}

并定义一个排序子函数来比较两个单词(参见:https://www.gnu.org/software/gawk/manual/html_node/Array-Sorting-Functions.html):

function compare_by_letters(i1, v1, i2, v2,        left, right) {
    left  = siw(v1)
    right = siw(v2)
    if (left < right)
        return -1
    else if (left == right)
        return 0
    else
        return 1
}

并将此函数与 awk 排序函数一起使用:

asort(array_test, array_test_result, "compare_by_letters")

那么,示例程序为:

function siw(word,        result, arr, arrlen, arridx) {
    result = hash_word[word]
    if (result != "") {
        return result
    }
    split(word, arr, "")
    arrlen = asort(arr)
    for (arridx = 1; arridx <= arrlen; arridx++) {
        result = result arr[arridx]
    }
    hash_word[word] = result
    return result
}

function compare_by_letters(i1, v1, i2, v2,        left, right) {
    left  = siw(v1)
    right = siw(v2)
    if (left < right)
        return -1
    else if (left == right)
        return 0
    else
        return 1
}

{
    array_test[i++] = [=13=]
}

END {
    alen = asort(array_test, array_test_result, "compare_by_letters")
    for (aind = 1; aind <= alen; aind++) {
        print array_test_result[aind]
    }
}

这样执行:

echo -e "fail\nhello\nborn" | awk -f sort_letter.awk

输出:

fail
born
hello

当然,如果你有一个大输入,你可以调整 siw 函数来记住最快计算的结果:

function siw(word,        result, arr, arrlen, arridx) {
    result = hash_word[word]
    if (result != "") {
        return result
    }
    split(word, arr, "")
    arrlen = asort(arr)
    for (arridx = 1; arridx <= arrlen; arridx++) {
        result = result arr[arridx]
    }
    hash_word[word] = result
    return result
}

另一种选择是 装饰-排序-取消装饰sed。本质上,您使用 sed"hello" 分成每行一个字符(用换行符 '\n' 装饰每个字符)并将结果传递给 sort。然后使用 sed 进行反向操作(通过删除 '\n' 取消修饰每一行)将这些行重新连接在一起。

printf "hello" | sed 's/\(.\)/\n/g' | sort | sed '{:a N;s/\n//;ta}'
ehllo

您可以使用多种方法,但这种方法 shell 友好,但该行为需要 GNU sed

使用 GNU awk for PROCINFO[]、“sorted_in”(参见 https://www.gnu.org/software/gawk/manual/gawk.html#Controlling-Scanning)并使用空分隔符拆分,生成字符数组:

$ echo 'hello' |
awk '
    BEGIN { PROCINFO["sorted_in"]="@val_str_asc" }
    {
        split(,chars,"")
        word = ""
        for (i in chars) {
            word = word chars[i]
        }
        print word
    }
'
ehllo

$ echo 'hello' | awk -v ordr='@val_str_asc' 'BEGIN{PROCINFO["sorted_in"]=ordr} {split(,chars,""); word=""; for (i in chars) word=word chars[i]; print word}'
ehllo

$ echo 'hello' | awk -v ordr='@val_str_desc' 'BEGIN{PROCINFO["sorted_in"]=ordr} {split(,chars,""); word=""; for (i in chars) word=word chars[i]; print word}'
ollhe

这里有一个非常非正统的方法,如果你真的想将“hello”排序为“ehllo”:

mawk/mawk2/gawk 'BEGIN { FS="^$" 

        # to make it AaBbCc… etc;  chr(65) = ascii "A"

        for (x = 65; x < 91; x++) {

            ref = sprintf("%s%c%c",ref, x, x+32) 

   } } /^[[:alpha:]]$/ { print } /[[:alpha:]][[:alpha:]]+/ { 
   
             # for gawk/nawk, feel free to change 
             # that to /[[:alpha:]]{2,}/
             # the >= 2+ condition is to prevent wasting time
             # sorting single letter words "A" and "I"

      s=""; x=1; len=length(inp=[=10=]);

      while ( len && (x<53) ) {  
         if (inp~(ch = substr(ref,x++,1))) {
            while ( sub(ch,"",inp) ) {
                   s  = s ch; 
                 len -= 1   ;
      } } }

    print s }'

我知道这是进行选择排序的一种极其低效的方法。潜在的节省时间源于在所有字母完成时立即结束循环,而不是每次都迭代所有 52 个字母。缺点是它不会预先分析输入

(例如,如果您检测到该行只是小写字母,那么您可以使用仅小写字母的循环来加速它)

好处是它消除了对自定义函数的需要,消除了任何 gawk 依赖性,也消除了将每一行拆分为数组(或每个字符到其自己的字段)的需要

我的意思是,从技术上讲,可以将 FS 设置为空字符串,从而自动将 NF 作为字符串长度。但有时如果输入有点大,它可能会很慢。如果您需要 unicode 支持,那么基于 match() 的方法更可取。

  • 添加 (x<53) 条件以防止 运行-away 无限循环,以防输入不是纯 ASCII 字母