【问题标题】:Bash - Swap Values in ColumnBash - 交换列中的值
【发布时间】:2013-03-19 04:47:07
【问题描述】:

我在一个文件中有一些 CSV/表格数据,如下所示:

1,7,3,2
8,3,8,0
4,9,5,3
8,5,7,3
5,6,1,9

(它们并不总是数字,只是随机的逗号分隔值。不过,单个数字更容易举例。)

我想随机打乱任何 40% 的列。例如,说第三个。所以也许 3 和 1 互相交换。现在第三列是:

1 << Came from the last position
8
5
7
3 << Came from the first position

我正在尝试在我正在处理的bash 脚本中的文件中执行此操作,但我运气不佳。我一直在一些非常疯狂且毫无结果的grep 兔子洞里徘徊,这让我觉得自己走错了路(不断的失败让我失望了)。

我用一连串的东西标记了这个问题,因为我不完全确定我什至应该使用哪种工具。

编辑:我可能最终会接受鲁本斯的回答,不管它多么古怪,因为它直接包含交换概念(我想我本可以在原始问题中更加强调),它允许我指定要交换的列的百分比。它也恰好起作用,这总是一个优点。

对于不需要这个并且只想要基本洗牌的人,Jim Garrison 的答案也有效(我测试过)。

不过,请注意鲁本斯的解决方案。我拿了这个:

for (i = 1; i <= NF; ++i) {
  delim = (i != NF) ? "," : "";
  ...
}
printf "\n";

删除了printf "\n"; 并将换行符向上移动,如下所示:

for (i = 1; i <= NF; ++i) {
  delim = (i != NF) ? "," : "\n";
  ...
}

因为在 else 情况下仅使用 "" 会导致 awk 在每行末尾写入损坏的字符 (\00)。有一次,它甚至设法用汉字替换了我的整个文件。虽然老实说,这可能让我在这个问题之上做了一些更愚蠢的事情。

【问题讨论】:

  • 随机化不是 sedawk 等文本处理工具的强项。
  • 您想选择 40% 的列并完全打乱这些列,还是选择一个(或更多)列并打乱其 40% 的行?
  • 后者(一列中 40% 的行)。
  • 除了改组不是强项之外,使用任何标记的实用程序都完全不可能进行就地编辑。需要一个外部工具——标准的工具很差。你需要一门真正的语言。
  • 是否允许使用 sed/awk?

标签: linux bash sed awk grep


【解决方案1】:

这适用于专门指定的列,但应该足以为您指明正确的方向。这适用于包括 Cygwin 在内的现代 bash shell:

paste -d, <(cut -d, -f1-2 test.dat) <(cut -d, -f3 test.dat|shuf) <(cut -d, -f4- test.dat)

操作特征是“process substitution”。

paste 命令水平连接文件,三个部分通过cut 从原始文件中拆分,第二部分(要随机化的列)通过shuf 命令运行以重新排序行。这是运行它几次的输出:

$ cat test.dat
1,7,3,2
8,3,8,0
4,9,5,3
8,5,7,3
5,6,1,9

$ paste -d, <(cut -d, -f1-2 test.dat) <(cut -d, -f3 test.dat|shuf) <(cut -d, -f4- test.dat)
1,7,1,2
8,3,8,0
4,9,7,3
8,5,3,3
5,6,5,9

$ paste -d, <(cut -d, -f1-2 test.dat) <(cut -d, -f3 test.dat|shuf) <(cut -d, -f4- test.dat)
1,7,8,2
8,3,1,0
4,9,3,3
8,5,7,3
5,6,5,9

【讨论】:

  • +1 shuf 可能需要用自定义洗牌器替换以处理 40% 约束,但其他方面很好(假设列数固定)。
【解决方案2】:

算法

  • 创建一个包含n 对的向量,从1number of lines,以及行中的相应值(对于所选列),然后对其进行随机排序;
  • 找出应该随机化多少行:num_random = percentage * num_lines / 100;
  • 从您的随机向量中选择第一个 num_random 条目;
  • 你可以对选中的行进行随机排序,但应该已经随机排序了;
  • 打印输出:

    i = 0
    for num_line, value in column; do
        if num_line not in random_vector:
            print value; # printing non-randomized value
        else:
            print random_vector[i]; # randomized entry
            i++;
    done
    

实施

#! /bin/bash

infile=$1
col=$2
n_lines=$(wc -l < ${infile})
prob=$(bc <<< "$3 * ${n_lines} / 100")

# Selected lines
tmp=$(tempfile)
paste -d ',' <(seq 1 ${n_lines}) <(cut -d ',' -f ${col} ${infile}) \
    | sort -R | head -n ${prob} > ${tmp}

# Rewriting file
awk -v "col=$col" -F "," '
(NR == FNR) {id[$1] = $2; next}
(FNR == 1) {
    i = c = 1;
    for (v in id) {value[i] = id[v]; ++i;}
}
{
    for (i = 1; i <= NF; ++i) {
        delim = (i != NF) ? "," : "";
        if (i != col) {printf "%s%c", $i, delim; continue;}
        if (FNR in id) {printf "%s%c", value[c], delim; c++;}
        else {printf "%s%c", $i, delim;}
    }
    printf "\n";
}
' ${tmp} ${infile}

rm ${tmp}

如果您想要接近 in-placement 的方法,您可以使用sponge 将输出返回 传送到输入文件。

执行

要执行,只需使用:

$ ./script.sh <inpath> <column> <percentage>

如:

$ ./script.sh infile 3 40
1,7,3,2
8,3,8,0
4,9,1,3
8,5,7,3
5,6,5,9

结论

这允许您选择列,随机排序该列中一定百分比的条目,并替换原始文件中的新列。

这个脚本与众不同,不仅证明 shell 脚本非常有趣,而且在某些情况下绝对应该使用它。 (:

【讨论】:

    【解决方案3】:

    我会使用 2-pass 方法,首先计算行数并将文件读入数组,然后使用 awk 的 rand() 函数生成随机数来识别您将更改的行然后 rand() 再次确定将交换哪些行对,然后在打印之前交换数组元素。像这样的伪代码,粗略的算法:

    awk -F, -v pct=40 -v col=3 '
    NR == FNR {
        array[++totNumLines] = $0
        next
    }
    
    FNR == 1{
        pctNumLines = totNumLines * pct / 100
    
        srand()
    
        for (i=1; i<=(pctNumLines / 2); i++) {
            oldLineNr = rand() * some factor to produce a line number that's in the 1 to totNumLines range but is not already recorded as processed in the "swapped" array.
            newLineNr = ditto plus must not equal oldLineNr
    
            swap field $col between array[oldLineNr] and array[newLineNr]
    
            swapped[oldLineNr]
            swapped[newLineNr]
        }
        next
    }
    
    { print array[FNR] }
    
    ' "$file" "$file" > tmp &&
    mv tmp "$file"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-11-23
      • 2014-03-16
      • 2010-09-07
      • 2019-06-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多