使用awk

时间:2016-11-21 19:07:50

标签: bash parsing awk

我是为了回应Reddit的日常程序员挑战而写的这篇文章,我希望得到一些反馈,以改进代码(它似乎有用)。挑战如下:

我们在"短手"中给出了一个数字列表。范围表示法,其中只写下一个数字的重要部分,因为我们知道数字总是在增加(例如" 1,3,7,2,4,1和#34;代表[1,3,7, 12,14,21])。有些人在他们的范围内使用不同的分隔符(例如" 1-3,1-2"," 1:3,1:2"," 1..3, 1..2和#34;表示相同的数字[1,2,3,11,12]),它们有时指定范围步骤的第三个数字(例如" 1:5:2"表示[1,3,5])。

注意:对于此挑战,范围限制始终具有包容性。 我们的工作是返回完整数字的列表。 可能的分隔符是:[" - ",":"," .."]

示例输入:

104..02
545,64:11

示例输出:

104 105 106...200 201 202 # truncated for simplicity
545 564 565 566...609 610 611 # truncated for simplicity

我的解决方案:

BEGIN { FS = "," }
function next_value(current_value, previous_value) {
    regexp = current_value "$"
    while(current_value <= previous_value || !(current_value ~ regexp)) {
        current_value += 10
    }
    return current_value;
}
{
    j = 0
    delete number_list
    for(i = 1; i <= NF; i++) {
        # handle fields with ranges
        if($i ~ /-|:|\.\./) {
            split($i, range, /-|:|\.\./)
            if(range[1] > range[2]) {
                if(j != 0) {
                    range[1] = next_value(range[1], number_list[j-1])
                    range[2] = next_value(range[2], range[1])
                }
                else
                    range[2] = next_value(range[2], range[1]);
            }

            if(range[3] == "")
                number_to_iterate_by = 1;
            else
                number_to_iterate_by = range[3];

            range_iterator = range[1]
            while(range_iterator <= range[2]) {
                number_list[j] = range_iterator
                range_iterator += number_to_iterate_by
                j++
            }
        }
        else {
            number_list[j] = $i
            j++
        }
    }
    # apply increasing range logic and print
    for(i = 0; i < j; i++ ) {
        if(i == 0) {
            if(NR != 1) printf "\n"
            current_value = number_list[i]
        }
        else {
            previous_value = current_value
            current_value = next_value(number_list[i], previous_value)
        }
        printf "%s ", current_value
    }
}
END { printf "\n" } 

2 个答案:

答案 0 :(得分:2)

我的解决方案使用gawkRT(它包含与RS表示的文本匹配的输入文本)和next_n函数使用模运算来查找基于最后

cat range.awk

BEGIN{
    RS="\\.\\.|,|:|-"
    start = ""
    end = 0
    temp = ""
}
function next_n(n, last){
    mod = last % (10**length(n))
    if(mod < n) return last - mod + n
    return last + ((10**length(n))-mod) + n
}
{
    if(RT==":" || RT==".." || RT=="-"){
        if(start=="") start = next_n($1,end)
        else temp = $1
    }else{
        if(start != ""){
            if(temp==""){
                end = next_n($1,start)
                step = 1
            }else {
                end = next_n(temp,start)
                step = $1
            }
            for(i=start; i<=end; i+=step) printf "%s ", i
            start = ""
            temp = ""
        }else{
            end = next_n($1,end)
            printf "%s ", end
        }
    }
}
END{
    print ""
}

TEST 1

echo "104..02" | awk -f range.awk

输出1

104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202

TEST 2

echo "545,64:11" | awk -f range.awk

输出2

545 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611

TEST 3

echo "2..5,7,2-1,2:1,0-3,2-7,8..0,4,4,2..1" | awk -f range.awk

输出3

2 3 4 5 7 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 40 41 42 43 52 53 54 55 56 57 58 59 60 64 74 82 83 84 85 86 87 88 89 90 91

TEST 4 with step

echo "1:5:2,99,88..7..3" | awk -f range.awk"

输出4

1 3 5 99 188 191 194 197

答案 1 :(得分:2)

这是BASH(非AWK)。 我认为这是一个有效的答案,因为最初的挑战并没有指定语言。

#!/bin/bash
mkord(){ local v=$1 dig base
          max=$2
          (( dig=10**${#v} , base=max/dig*dig , v+=base ))
          while (( v < max )); do (( v+=dig )); done
          max=$v
        }

while read line; do
    line="${line//[,\"]/ }" line="${line//[:-]/..}"
    IFS=' ' read -a arr <<<"$line"
    max=0 a='' res=''
    for val in "${arr[@]//../ }"; do
        IFS=" " read v1 v2 v3 <<<"$val"
        (( a==0 )) && max=$v1
        [[ $v1  ]] && mkord "$v1" "$max" && v1=$max
        [[ $v2  ]] && mkord "$v2" "$max" && v2=$max
        res=$res${a:+,}${v2:+\{}$v1${v2:+\.\.}$v2${v3:+\.\.}$v3${v2:+\}}
        a=1
    done
    (( ${#arr[@]} > 1 )) &&  res={$res}
    eval set -- $res
    echo "\"$*\""
done <"infile"

如果测试的来源是:

$ cat infile
"1,3,7,2,4,1"
"1-3,1-2"
"1:5:2"
"104-2"
"104..02"
"545,64:11"

结果将是:

"1 3 7 12 14 21"
"1 2 3 11 12"
"1 3 5"
"104 105 106 107 108 109 110 111 112"
"104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202"
"545 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611"

这会在7毫秒内完成列表。