我有一个包含10个数百行,具有不同长度的文本文件。 现在,我要选择N行随机,它们保存在另一个文件中,并从原来的文件删除它们。 我已经找到了一些回答这个问题,但大部分都使用一个简单的想法:文件排序,然后选择第一个或最后N行。 遗憾的是这个想法行不通给我,因为我想保留行的顺序。 我想这一段代码,但它是非常缓慢,需要数个小时。
FILEsrc=$1;
FILEtrg=$2;
MaxLines=$3;
let LineIndex=1;
while [ "$LineIndex" -le "$MaxLines" ]
do
# count number of lines
NUM=$(wc -l $FILEsrc | sed 's/[ \r\t].*$//g');
let X=(${RANDOM} % ${NUM} + 1);
echo $X;
sed -n ${X}p ${FILEsrc}>>$FILEtrg; #write selected line into target file
sed -i -e ${X}d ${FILEsrc}; #remove selected line from source file
LineIndex=`expr $LineIndex + 1`;
done
我发现这行最耗时的一个中的代码:
sed -i -e ${X}d ${FILEsrc};
有没有什么办法来克服这个问题,使代码更快吗? 由于我在赶时间的时候,我可能会要求你给我完整的C / C ++代码,这样做呢?
Answer 1:
一个简单的O(n)的算法被描述在:
http://en.wikipedia.org/wiki/Reservoir_sampling
array R[k]; // result
integer i, j;
// fill the reservoir array
for each i in 1 to k do
R[i] := S[i]
done;
// replace elements with gradually decreasing probability
for each i in k+1 to length(S) do
j := random(1, i); // important: inclusive range
if j <= k then
R[j] := S[i]
fi
done
Answer 2:
生成所有的偏移量,则会使整个文件一次通过。 假设你有偏移的在期望数量的offsets
(每行一个数),则可以生成单个sed
这样脚本:
sed "s!.*!&{w $FILEtrg\nd;}!" offsets
输出是sed
脚本,你可以保存到一个临时文件,或者(如果您的sed
方言支持它)管材第二sed
实例:
... | sed -i -f - "$FILEsrc"
产生offsets
留作练习文件。
既然你有Linux的标签,这应该工作了蝙蝠的权利。 默认sed
一些其他平台可能不明白\n
和/或接受-f -
阅读从标准输入脚本。
这是一个完整的脚本,更新为使用shuf
(感谢@Thor!),以避免可能的重复的随机数。
#!/bin/sh
FILEsrc=$1
FILEtrg=$2
MaxLines=$3
# Add a line number to each input line
nl -ba "$FILEsrc" |
# Rearrange lines
shuf |
# Pick out the line number from the first $MaxLines ones into sed script
sed "1,${MaxLines}s!^ *\([1-9][0-9]*\).*!\1{w $FILEtrg\nd;}!;t;D;q" |
# Run the generated sed script on the original input file
sed -i -f - "$FILEsrc"
Answer 3:
[我已经更新了每个解决方案从输入删除选定的线,但我不是正面的awk
是正确的。 我偏爱bash
解自己,所以我不打算花时间调试它。 随意编辑任何错误。]
这里有一个简单awk
脚本(概率是简单的浮点数,不很好地混合管理bash
):
tmp=$(mktemp /tmp/XXXXXXXX)
awk -v total=$(wc -l < "$FILEsrc") -v maxLines=$MaxLines '
BEGIN { srand(); }
maxLines==0 { exit; }
{ if (rand() < maxLines/total--) {
print; maxLines--;
} else {
print $0 > /dev/fd/3
}
}' "$FILEsrc" > "$FILEtrg" 3> $tmp
mv $tmp "$FILEsrc"
当你打印一行输出,你递减maxLines
降低选择进一步线的概率。 但是当你消耗的输入,您可以降低total
的概率增加。 在极端情况的概率降为零时maxLines
做,这样你就可以停止处理该输入。 在另一个极端,概率命中1一旦total
小于或等于maxLines
,你会被接受所有进一步线。
下面是相同的算法,在(几乎)纯实施bash
使用整数运算:
FILEsrc=$1
FILEtrg=$2
MaxLines=$3
tmp=$(mktemp /tmp/XXXXXXXX)
total=$(wc -l < "$FILEsrc")
while read -r line && (( MaxLines > 0 )); do
(( MaxLines * 32768 > RANDOM * total-- )) || { printf >&3 "$line\n"; continue; }
(( MaxLines-- ))
printf "$line\n"
done < "$FILEsrc" > "$FILEtrg" 3> $tmp
mv $tmp "$FILEsrc"
Answer 4:
这里有一个完整的围棋程序:
package main
import (
"bufio"
"fmt"
"log"
"math/rand"
"os"
"sort"
"time"
)
func main() {
N := 10
rand.Seed( time.Now().UTC().UnixNano())
f, err := os.Open(os.Args[1]) // open the file
if err!=nil { // and tell the user if the file wasn't found or readable
log.Fatal(err)
}
r := bufio.NewReader(f)
var lines []string // this will contain all the lines of the file
for {
if line, err := r.ReadString('\n'); err == nil {
lines = append(lines, line)
} else {
break
}
}
nums := make([]int, N) // creates the array of desired line indexes
for i, _ := range nums { // fills the array with random numbers (lower than the number of lines)
nums[i] = rand.Intn(len(lines))
}
sort.Ints(nums) // sorts this array
for _, n := range nums { // let's print the line
fmt.Println(lines[n])
}
}
只要你把旅途中文件的目录名为randomlines
在GOPATH
,你可以建立这样的:
go build randomlines
然后调用它是这样的:
./randomlines "path_to_my_file"
这将打印N(在这里10)随机线在你的文件,但没有改变的顺序。 当然,它甚至与大文件几乎瞬时的。
Answer 5:
下面是用的coreutils,sed和awk一个有趣的双通道选项:
n=5
total=$(wc -l < infile)
seq 1 $total | shuf | head -n $n \
| sed 's/^/NR == /; $! s/$/ ||/' \
| tr '\n' ' ' \
| sed 's/.*/ & { print >> "rndlines" }\n!( &) { print >> "leftover" }/' \
| awk -f - infile
随机数的列表被传递给sed的产生awk脚本。 如果AWK从上面的管线上拆下,这将是输出:
{ if(NR == 14 || NR == 1 || NR == 11 || NR == 20 || NR == 21 ) print > "rndlines"; else print > "leftover" }
因此,随机行都会被保存在rndlines
,其余在leftover
。
Answer 6:
提到“10个数百名”行应排得相当快,所以这是为装饰,排序,去除装饰图案漂亮的情况下。 它实际上是创建两个新的文件,从原来的一个可以重新命名模拟删除线。
注:头尾不能用来代替awk的,因为他们关闭给行数后的文件描述符,使得三通退出从而在.rest文件导致丢失数据。
FILE=input.txt
SAMPLE=10
SEP=$'\t'
<$FILE nl -s $"SEP" -nln -w1 |
sort -R |
tee \
>(awk "NR > $SAMPLE" | sort -t"$SEP" -k1n,1 | cut -d"$SEP" -f2- > $FILE.rest) \
>(awk "NR <= $SAMPLE" | sort -t"$SEP" -k1n,1 | cut -d"$SEP" -f2- > $FILE.sample) \
>/dev/null
# check the results
wc -l $FILE*
# 'remove' the lines, if needed
mv $FILE.rest $FILE
Answer 7:
这可能会为你工作(GNU sed的,排序和seq):
n=10
seq 1 $(sed '$=;d' input_file) |
sort -R |
sed $nq |
sed 's/.*/&{w output_file\nd}/' |
sed -i -f - input_file
其中$n
是行提取的数目。
文章来源: How to select random lines from a file