我在文件中有一些CSV /表格数据,如下所示:
1,7,3,2
8,3,8,0
4,9,5,3
8,5,7,3
5,6,1,9
(它们并不总是数字,只是随机逗号分隔的值。但是,例如,单位数字更容易。)
我想随机抽取40%的任意列。举个例子,说第3个。所以也许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
)写入损坏的字符。有一次,它甚至设法用中文字符替换我的整个文件。虽然,老实说,这可能涉及到我在这个问题上做了一些额外的愚蠢。
答案 0 :(得分:4)
这适用于专门指定的列,但应足以指向正确的方向。这适用于现代bash shell,包括Cygwin:
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 :(得分:1)
<强>算法强>:
n
对的向量,从1
到number 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脚本非常有趣,而且有些情况下肯定会使用而不是。 (:
答案 2 :(得分:0)
我使用2遍方法,首先计算行数并将文件读入数组,然后使用awk的rand()函数生成随机数,以识别您将要更改的行然后再次rand()确定要交换哪些线对,然后在打印前交换数组元素。像这样的PSEUDO-CODE,粗略的算法:
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"