【问题标题】:randomly select n lines from a file with possible repetition从可能重复的文件中随机选择 n 行
【发布时间】:2012-04-21 20:14:02
【问题描述】:

我有一个超过 100 万行的文本文件。单独的行不是很大(每行大约 200-270 个字符)。

我试图在输入中随机选择 60% 的行数,其中每一行都可以在输出中重复。在上面的示例中,我的输出将有 600,000 行,但其中只有 500,000 行可能是唯一的。我还需要在不同的输出文件中根本没有选择的行。任何单独的行都不应该出现在两个输出文件中。

输入文件中的每一行都有如下记录。

  • 记录1
  • 记录2
  • 记录3
  • 记录4
  • 记录5
  • 记录6
  • 记录7

如果我想在文件 output1.txt 中选择 5 个随机行,其中每一行都可以重复。可以说以下是选择的行并在 output1.txt 中

  • 记录3
  • 记录5
  • 记录2
  • 记录2
  • 记录5

剩下的记录应该去output2.txt。

  • 记录1
  • 记录4
  • 记录6
  • 记录7

记录的顺序无关紧要。

我认为我可以使用 Java 编写代码来执行此操作,但我想知道我可以使用一些命令或脚本快速完成此操作。我尝试使用 'shuf' 来选择线条,但我怎样才能确保已选择的线条不会出现在我想要获取的第二个输出中。

我正在使用 Linux 机器。欢迎任何建议或cmets。谢谢。

【问题讨论】:

  • whathaveyoutried.com - 你试过什么?这里不是为您编写代码的地方。
  • 听起来第一部分很简单(几行简单的 php 行或其他),但第二部分,即分别输出第一部分中没有的部分,使它更难...
  • 如果听起来我在找人告诉我代码,我很抱歉。我正在寻找的是已经完成我需要的任何想法或命令。顺便说一句,我已经在问题中写了我尝试了什么。
  • @Ben:正是我被卡住的地方。我可以通过简单地使用“shuf”命令来获得第一部分的输出。
  • 你必须澄清并且非常明确。这两个文件究竟应该有什么?如果我对您的解释正确...您正在寻找的数学术语如下:您有一组 100 万个元素,并且您希望“不替换”地选择样本元素。此外,您希望这将您的集合划分为两个集合:拾取和未拾取的元素。实际上,如果您将集合表示为一个数组,您可以打乱数组并将其分成两部分:让前 60% 的元素被选中,而让剩余的 40% 的元素不被选中。有什么问题?

标签: linux perl bash


【解决方案1】:

这是一个 Perl 解决方案。

我最近似乎写了很多,但是索引一个非常大的文本文件是随机访问它而不将整个文件读入内存的最佳方法。

该程序使用tell 运算符来建立当前记录在源文件中的偏移量,seek 运算符返回到特定记录,vec 跟踪已选择的记录。

请注意,do { ... } while EXPR 表单会在第一次检查条件之前执行 do-block,并且是专门为此目的选择的。

程序希望扫描文件以查找要在命令行上指定的数据。选择的 60% 的输出文件为 selected.txt,其余的为 unselected.txt

use strict;
use warnings;

my $file = shift or die "No input file specified";

open my $infh, '<', $file or die qq(Unable to open "$file" for input: $!);
my @index;
do { push @index, tell $infh } while <$infh>;

my $used = "\0" x (@index / 8 + 1);

my $outfh;

open $outfh, '>', 'selected.txt' or die $!;
my $n = 0;
while ($n++ / @index < 0.6) {
  my $rec = int rand scalar @index;
  seek $infh, $index[$rec], 0;
  print $outfh scalar <$infh>;
  vec($used, $rec, 1) = 1;
}

open $outfh, '>', 'unselected.txt' or die $!;
for my $rec (0 .. $#index) {
  next if vec($used, $rec, 1);
  seek $infh, $index[$rec], 0;
  print $outfh scalar <$infh>;
}

编辑

我不愿使用模块来替换这么少的代码,但这里有一个使用 Tie::File 的版本,ikegami 推荐,以防有人更喜欢这种方法。

use strict;
use warnings;

use Tie::File;

my $file = shift or die "No input file specified";

tie my @index, 'Tie::File', $file, mode => O_RDONLY
    or die qq(Unable to open "$file" for input: $!);

my $outfh;
my @used;

open $outfh, '>', 'selected.txt' or die $!;
my $n = 0;
while ($n++ / @index < 0.6) {
  my $rec = int rand scalar @index;
  print $outfh $index[$rec], "\n";
  $used[$rec]++;
}

open $outfh, '>', 'unselected.txt' or die $!;
for my $rec (0 .. $#index) {
  print $outfh $index[$rec], "\n" unless $used[$rec];
}

【讨论】:

  • 为了进一步参考,您可能需要检查 Tie::File。我在之前发布的解决方案中使用了它,因为它会为您提供所有信息。 vec() 是一个不错的选择,但可能不需要这种内存节省。
【解决方案2】:

这会随机选择文件的 N 行中的一行,直到选择 N/6 行。重复率不受控制。

为了节省内存,我们会将行的文件位置保留在内存中,而不是行本身。 Tie::File 为我们做这件事。

#!/usr/bin/env perl
use strict;
use warnings;

use Tie::File  qw( );

my ($input_qfn, $picked_qfn, $unpicked_qfn) = @ARGV;

tie(my @lines, 'Tie::File', $input_qfn, autochomp => 0)
   or die;

my $num_lines = @lines;
my @unpicked_indexes = 0..$num_lines-1;
my @picked_indexes;
for (1..$num_lines*.6) {
   my $rnd_idx = int(rand($num_lines));
   $unpicked_indexes[$rnd_idx] = undef;
   push @picked_indexes, $rnd_idx;
}

open(my $picked_fh, '>', $picked_qfn)
   or die $!;
print($picked_fh $lines[$_]) for @picked_indexes;

open(my $unpicked_fh, '>', $unpicked_qfn)
   or die $!;
print($unpicked_fh $lines[$_]) for grep defined, @unpicked_indexes;

【讨论】:

  • 添加了缺失的位以使其成为可运行的脚本。
【解决方案3】:

您可以在bash script 中使用此代码:

输出中不重复行:

#!/bin/bash

lines=$(wc -l inputfile.txt | awk '{print $1}')

echo $lines

# computation of percentage of random lines we
# want to pick e.g. 60%
let percentage=$((lines*60/100))

echo $percentage

# pick the random lines
random_lines=$(sort -R inputfile.txt | head -n $percentage)

# show the random lines
echo $random_lines

在输出中重复行:

#!/bin/bash

lines=$(wc -l inputfile.txt | awk '{print $1}')

echo $lines

# computation of percentage of random lines we
# want to pick e.g. 60%
let percentage=$((lines*60/100))

echo $percentage

# pick the random lines
for ((i=1; i<$percentage; i++))
do
  echo $(sort -R inputfile.txt | head -n 1)
done

【讨论】:

  • 同样,这不允许each line can be repeated in the output 的要求。
  • @Thanasis Petsas:谢谢。但是一次又一次地对整个文件进行排序是个好主意,因为输入文件有 100 万条记录。
【解决方案4】:

您正在寻找的数学术语如下:您有一组 100 万个元素,并且您希望“替换”选择样本元素;此外,您还想知道未被选中的元素。

universe = range(10**6)  # or whatever your elements are
numElementsToChoose = int(0.6*len(universe))

chosen = [random.choice(universe) for _ in range(numElementsToChoose)]
unchosen = set(universe) - set(chosen)

演示:

>>> len(chosen), len(unchosen)
(600000, 548815)

(这段代码不优雅,因为universe 应该是一个集合,但python本身不支持从集合中选择一个随机元素,只支持一个序列......呃。)

【讨论】:

  • 我假设你所说的集合是指一个集合数据结构。一个集合的数据结构不能有重复的记录。此外,choosen 和剩余的总和可以超过原始行数(1000000),因为在choosen 中可以重复记录。
  • @NishantNagwani:哎呀,谢谢,已编辑。一个也使用collections.Counter,但如果一个内容将chosen 存储为列表,这也可以。
【解决方案5】:

例如 ~ 10% 在 STDOUT 中两次,另外 50% 在 STDOUT 中一次,在 STDERR 中剩下 40%

awk 'BEGIN {srand()} !/^$/ { r = rand(); if (r <= .60) print $0; if (r <= 0.10) print $0; if (r > .60) print $0 > "/dev/stderr"; }'

注意:将 STDOUT 重定向到一个文件 &gt; file1 并将 STDERR 重定向到另一个文件 2&gt; file2 ...

【讨论】:

  • 非常感谢您的帮助。但是,问题明确提到“我还需要在不同的输出文件中根本没有选择的行。任何单独的行都不应该出现在两个输出文件中。”我所做的唯一更改是包含示例。请阅读有关该问题的 cmets。再次感谢。
  • 我认为它不包括重复的情况。
【解决方案6】:

如果你有shuf,你可能有comm,它有-3 选项来比较两个排序的文件并输出仅在一个文件中找到的行。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-03
    • 1970-01-01
    相关资源
    最近更新 更多