Vẽ ngẫu nhiên một số dòng nhất định từ một tệp dữ liệu


13

Tôi có một danh sách dữ liệu, như

12345
23456
67891
-20000
200
600
20
...

Giả sử kích thước của tập dữ liệu này (tức là các dòng của tệp) là N. Tôi muốn vẽ ngẫu nhiên mcác dòng từ tệp dữ liệu này. Do đó, đầu ra phải là hai tệp, một là tệp bao gồm các mdòng dữ liệu này và một tệp khác bao gồm N-mcác dòng dữ liệu.

Có cách nào để làm điều đó bằng cách sử dụng lệnh Linux không?


1
Bạn có quan tâm về chuỗi các dòng? ví dụ. Bạn có muốn duy trì thứ tự nguồn hay bạn muốn chuỗi đó là ngẫu nhiên cũng như lựa chọn các dòng là ngẫu nhiên?
Peter.O

Câu trả lời:


18

Đây có thể không phải là cách hiệu quả nhất nhưng nó hoạt động:

shuf <file> > tmp
head -n $m tmp > out1
tail -n +$(( m + 1 )) tmp > out2

Với $mchứa số lượng dòng.


@userunknown, sort -Rchăm sóc sự ngẫu nhiên. Không chắc chắn nếu bạn đánh giá thấp câu trả lời cho điều đó, nhưng trước tiên hãy tìm nó trong trang chủ.
Rob Wouters

2
Lưu ý rằng sort -Rkhông sắp xếp chính xác đầu vào của nó một cách ngẫu nhiên: nó nhóm các dòng giống hệt nhau. Vì vậy, nếu đầu vào là ví dụ foo, foo, bar, barvà m = 2, sau đó một tập tin sẽ chứa cả foos và người kia sẽ chứa cả bars. GNU coreutils cũng có shuf, ngẫu nhiên các dòng đầu vào. Ngoài ra, bạn không cần một tập tin tạm thời .
Gilles 'SO- ngừng trở nên xấu xa'

tại sao không shuf <file> |head -n $m?
emanuele

@emanuele: Bởi vì chúng tôi cần cả đầu và đuôi trong hai tệp riêng biệt.
Rob Wouters

5

Kịch bản bash / awk này chọn các dòng một cách ngẫu nhiên và duy trì chuỗi gốc trong cả hai tệp đầu ra.

awk -v m=4 -v N=$(wc -l <file) -v out1=/tmp/out1 -v out2=/tmp/out2 \
 'BEGIN{ srand()
         do{ lnb = 1 + int(rand()*N)
             if ( !(lnb in R) ) {
                 R[lnb] = 1
                 ct++ }
         } while (ct<m)
  } { if (R[NR]==1) print > out1 
      else          print > out2       
  }' file
cat /tmp/out1
echo ========
cat /tmp/out2

Đầu ra, dựa trên dữ liệu trong câu hỏi.

12345
23456
200
600
========
67891
-20000
20

4

Như với tất cả mọi thứ Unix, Có một tiện ích cho TM đó .

Chương trình trong ngày: split
splitsẽ phân chia một tệp theo nhiều cách khác nhau, -bbyte, -ldòng, -nsố lượng tệp đầu ra. Chúng tôi sẽ sử dụng -ltùy chọn. Vì bạn muốn chọn các dòng ngẫu nhiên và không chỉ đầu tiên m, chúng tôi sẽ sortgửi tệp ngẫu nhiên trước. Nếu bạn muốn đọc về sort, hãy tham khảo câu trả lời của tôi ở đây .

Bây giờ, mã thực tế. Nó khá đơn giản, thực sự:

sort -R input_file | split -l $m output_prefix

Điều này sẽ tạo hai tệp, một có mdòng và một có N-mdòng, được đặt tên output_prefixaaoutput_prefixab. Đảm bảo mlà tệp lớn hơn bạn muốn hoặc bạn sẽ nhận được một số tệp có độ dài m(và một tệp có N % m).

Nếu bạn muốn đảm bảo rằng bạn sử dụng kích thước chính xác, đây là một ít mã để làm điều đó:

m=10 # size you want one file to be
N=$(wc -l input_file)
m=$(( m > N/2 ? m : N - m ))
sort -R input_file | split -l $m output_prefix

Chỉnh sửa: Tôi đã nhận thấy rằng một số sorttriển khai không có -Rcờ. Nếu bạn có perl, bạn có thể thay thế perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.


1
Thật không may, sort -Rdường như chỉ có trong một số phiên bản sắp xếp (có thể là phiên bản gnu). Đối với các nền tảng khác, tôi đã viết một công cụ gọi là "randline", không có gì ngoài việc ngẫu nhiên hóa stdin. Đó là tại beesbuzz.biz/code cho bất cứ ai cần nó. (Tôi có xu hướng xáo trộn nội dung tệp khá nhiều.)
fluffy

1
Lưu ý rằng sort -Rkhông sắp xếp chính xác đầu vào của nó một cách ngẫu nhiên: nó nhóm các dòng giống hệt nhau. Vì vậy, nếu đầu vào là ví dụ foo, foo, bar, barvà m = 2, sau đó một tập tin sẽ chứa cả foos và người kia sẽ chứa cả bars. GNU coreutils cũng có shuf, ngẫu nhiên các dòng đầu vào. Ngoài ra, bạn có thể chọn tên tệp đầu ra bằng cách sử dụng headtailthay vìsplit .
Gilles 'SO- ngừng trở nên xấu xa'

4

Nếu bạn không nhớ sắp xếp lại các dòng và bạn có GNU coreutils (tức là trên Linux hoặc Cygwin không được nhúng, không quá cổ kể từ khi shufxuất hiện trong phiên bản 6.0), shuf(tạm thời xáo trộn) sẽ sắp xếp lại các dòng của tệp một cách ngẫu nhiên. Vì vậy, bạn có thể xáo trộn tệp và gửi các dòng m đầu tiên vào một tệp và phần còn lại vào một tệp khác.

Không có cách lý tưởng để thực hiện công văn đó. Bạn không thể xâu chuỗi headtailheadsẽ đệm trước. Bạn có thể sử dụng split, nhưng bạn không có được sự linh hoạt nào đối với tên tệp đầu ra. Bạn có thể sử dụng awk, tất nhiên:

<input shuf | awk -v m=$m '{ if (NR <= m) {print >"output1"} else {print} }'

Bạn có thể sử dụng sed, điều này tối nghĩa nhưng có thể nhanh hơn đối với các tệp lớn.

<input shuf | sed -e "1,${m} w output1" -e "1,${m} d" >output2

Hoặc bạn có thể sử dụng teeđể sao chép dữ liệu, nếu nền tảng của bạn có /dev/fd; Không sao nếu m nhỏ:

<input shuf | { tee /dev/fd/3 | head -n $m >output1; } 3>&1 | tail -n +$(($m+1)) >output2

Có thể sử dụng awk để gửi lần lượt từng dòng. Lưu ý rằng awk không tốt lắm trong việc khởi tạo trình tạo số ngẫu nhiên của nó; tính ngẫu nhiên không chỉ chắc chắn không phù hợp với mật mã, mà thậm chí còn không tốt cho mô phỏng số. Hạt giống sẽ giống nhau cho tất cả các yêu cầu awk trên bất kỳ hệ thống nào trong khoảng thời gian một giây.

<input awk -v N=$(wc -l <input) -v m=3 '
    BEGIN {srand()}
    {
        if (rand() * N < m) {--m; print >"output1"} else {print >"output2"}
        --N;
    }'

Nếu bạn cần sự ngẫu nhiên tốt hơn, bạn có thể làm điều tương tự trong Perl, hạt giống RNG của nó một cách dứt khoát.

<input perl -e '
    open OUT1, ">", "output1" or die $!;
    open OUT2, ">", "output2" or die $!;
    my $N = `wc -l <input`;
    my $m = $ARGV[0];
    while (<STDIN>) {
        if (rand($N) < $m) { --$m; print OUT1 $_; } else { print OUT2 $_; }
        --$N;
    }
    close OUT1 or die $!;
    close OUT2 or die $!;
' 42

@Gilles:awkdụ: -v N=$(wc -l <file) -v m=4... và nó chỉ in một dòng "ngẫu nhiên" khi giá trị ngẫu nhiên nhỏ hơn $m, thay vì in $mcác dòng ngẫu nhiên ... Có vẻ như perlcó thể đang làm điều tương tự với rand , nhưng tôi không 'Tôi biết perlđủ rõ để vượt qua lỗi biên dịch: lỗi cú pháp ở -e dòng 7, gần ") in"
Peter.O

@ Peter.O Cảm ơn, đó là những gì xuất phát từ việc gõ trình duyệt và chỉnh sửa một cách bất cẩn. Tôi đã sửa mã awk và perl.
Gilles 'SO- ngừng trở nên xấu xa'

Tất cả 3 phương pháp đều hoạt động tốt và nhanh chóng .. cảm ơn (+1) ... Tôi đang dần dần hiểu được ... và đó là một tệp đặc biệt thú vị và hữu ích trong shufví dụ.
Peter.O

Một vấn đề đệm? . Tui bỏ lỡ điều gì vậy? Kết head cathợp gây mất dữ liệu trong thử nghiệm thứ hai sau 3-4 .... KIỂM TRA 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } .. KIỂM TRA 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... wc -lkết quả cho các kết quả đầu ra của KIỂM TRA 1-25000 5000 (tốt), nhưng đối với KIỂM TRA 3-45000 4539 (không tốt) .. Sự khác biệt tùy thuộc vào kích thước tệp có liên quan ... Đây là liên kết đến mã kiểm tra
Peter.O

@ Peter.O Ngay lần nữa, cảm ơn. Thật vậy, headđọc trước; những gì nó đọc trước và không in ra bị loại bỏ. Tôi đã cập nhật câu trả lời của mình với các giải pháp ít thanh lịch hơn nhưng (tôi khá chắc chắn).
Gilles 'SO- ngừng trở nên xấu xa'

2

Giả sử m = 7N = 21:

cp ints ints.bak
for i in {1..7}
do
    rnd=$((RANDOM%(21-i)+1))
    # echo $rnd;  
    sed -n "${rnd}{p,q}" 10k.dat >> mlines 
    sed -i "${rnd}d" ints 
done

Lưu ý: Nếu bạn thay thế 7bằng một biến như $1hoặc $m, bạn phải sử dụng seq, không phải là {from..to}chú thích, không thực hiện mở rộng biến.

Nó hoạt động bằng cách xóa từng dòng từ tệp, càng ngày càng ngắn, do đó, số dòng, có thể được loại bỏ, phải ngày càng nhỏ hơn.

Không nên sử dụng tệp này cho các tệp dài hơn và nhiều dòng, vì đối với mỗi số, trung bình, một nửa tệp cần được đọc cho số 1 và toàn bộ tệp cho mã sed thứ 2 .


Anh ta cần một tập tin với các dòng được loại bỏ quá.
Rob Wouters

Tôi nghĩ rằng "bao gồm cả m dòng dữ liệu" này cũng có nghĩa including themnhưng cũng là dòng gốc - do đó including, không consisting ofvà không sử dụng only, nhưng tôi đoán cách giải thích của bạn là, user288609 có nghĩa là gì. Tôi sẽ điều chỉnh kịch bản của mình cho phù hợp.
người dùng không xác định

Có vẻ tốt. `` ``
Rob Wouters

@user không rõ: Bạn có +1ở sai chỗ. Nó phải là rnd=$((RANDOM%(N-i)+1))nơi N = 21 trong ví dụ của bạn .. Nó hiện gây ra sedsự cố khi rndđược đánh giá 0. .. Ngoài ra, nó không mở rộng rất tốt với tất cả các tập tin đó viết lại. ví dụ: 123 giây để trích xuất 5.000 dòng ngẫu nhiên từ tệp 10.000 dòng so với 0,03 giây cho phương pháp trực tiếp hơn ...
Peter.O

@ Peter.O: Bạn đúng (đã sửa) và bạn đúng.
người dùng không xác định
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.