【问题标题】:Print many specific rows from a text file using an index file使用索引文件从文本文件中打印许多特定行
【发布时间】:2017-03-28 12:25:57
【问题描述】:

我有一个包含超过 1 亿行的大型文本文件,名为 reads.fastq。此外,我还有另一个名为takeThese.txt 的文件,其中包含文件reads.fastq 中应该打印的行号(每行一个)。

目前我使用

awk 'FNR == NR { h[$1]; next } (FNR in h)' takeThese.txt reads.fastq > subsample.fastq

显然这需要很长时间。有没有办法使用存储在另一个文件中的行号从文本文件中提取行?如果对takeThese.txt 文件进行排序,它会加快速度吗?

编辑:

我拥有的文件的几行示例:

reads.fastq:

@HWI-1KL157:36:C2468ACXX
TGTTCAGTTTCTTCGTTCTTTTTTTGGAC
+
@@@DDDDDFF>FFGGC@F?HDHIHIFIGG
@HWI-1KL157:36:C2468ACXX
CGAGGCGGTGACGGAGAGGGGGGAGACGC
+
BCCFFFFFHHHHHIGHHIHIJJDDBBDDD
@HWI-1KL157:36:C2468ACXX
TCATATTTTCTGATTTCTCCGTCACTCAA

takeThese.txt

5
6
7
8

所以输出看起来像这样:

@HWI-1KL157:36:C2468ACXX
CGAGGCGGTGACGGAGAGGGGGGAGACGC
+
BCCFFFFFHHHHHIGHHIHIJJDDBBDDD

编辑:建议脚本的比较:

$ time perl AndreasWederbrand.pl takeThese.txt reads.fastq  > /dev/null

real    0m1.928s
user    0m0.819s
sys     0m1.100s

$ time ./karakfa  takeThese_numbered.txt reads_numbered.fastq  > /dev/null

real    0m8.334s
user    0m9.973s
sys     0m0.226s

$ time ./EdMorton takeThese.txt reads.fastq  > /dev/null

real    0m0.695s
user    0m0.553s
sys     0m0.130s

$ time ./ABrothers  takeThese.txt reads.fastq  > /dev/null

real    0m1.870s
user    0m1.676s
sys     0m0.186s

$ time ./GlenJackman takeThese.txt reads.fastq  > /dev/null

real    0m1.414s
user    0m1.277s
sys     0m0.147s

$ time ./DanielFischer takeThese.txt reads.fastq  > /dev/null

real    0m1.893s
user    0m1.744s
sys     0m0.138s

感谢所有建议和努力!

【问题讨论】:

  • 问题是,该答案中提供的解决方案也相当慢。如果没有比这更快的东西,那么这个问题肯定是重复的,对不起 - 我什至没有找到那个线程。
  • 该脚本似乎输出相反的内容,即。文件takeThese.txt 中未列出的行。对于 reads.fastq 中的 100M 行(仅作为数据的行号)和 takeThat.txt 中均匀分布的 1M 行号,我的迷你笔记本电脑上的执行时间为 52 秒(>/dev/null)。我认为值得一提的是@glennjackman 的解决方案只用了 18 秒。
  • 我用awk 'BEGIN {for(i=1;i<=100000000;i++) print i}' > reads.fastqawk 'BEGIN {for(i=1;i<=1000000;i++) print i*100}' > takeThat.txt 生成了我的测试文件,顺便说一句,所以它是1000000 个均匀分布的命中。
  • 感谢所有提出方法的人!我比较了较小子集的相应时间。我添加到问题中的结果。

标签: bash unix awk sed


【解决方案1】:

您问题中的脚本将非常快,因为它所做的只是对数组h 中的当前行号进行哈希查找。除非您想从 reads.fastq 打印最后一个行号,否则这会更快,因为它在打印最后一个所需的行号后退出,而不是继续读取 reads.fastq 的其余部分:

awk 'FNR==NR{h[$1]; c++; next} FNR in h{print; if (!--c) exit}' takeThese.txt reads.fastq

您可以在print; 之后添加delete h[FNR]; 以减少数组大小,因此可能会加快查找时间,但如果这真的会提高性能,因为数组访问是哈希查找,所以会是速度非常快,因此添加 delete 可能最终会降低整个脚本的速度。

实际上,这会更快,因为它避免了为两个文件中的每一行测试 NR==FNR:

awk -v nums='takeThese.txt' '
    BEGIN{ while ((getline i < nums) > 0) {h[i]; c++} }
    NR in h{print; if (!--c) exit}
' reads.fastq

这是更快还是@glennjackman 发布的脚本更快取决于takeThese.txt 中有多少行以及它们发生在reads.fastq 末尾的距离。由于 Glenns 会读取整个 reads.fastq,无论 takeThese.txt 的内容是什么,它都会在大约恒定的时间内执行,而我的速度会明显更快,距离 reads.fastq 结尾越远,takeThese.txt 中的最后一行出现.例如

$ awk 'BEGIN {for(i=1;i<=100000000;i++) print i}' > reads.fastq

.

$ awk 'BEGIN {for(i=1;i<=1000000;i++) print i*100}' > takeThese.txt

$ time awk -v nums=takeThese.txt '
    function next_index() {
        ("sort -n " nums) | getline i
        return i
    }
    BEGIN { linenum = next_index() }
    NR == linenum { print; linenum = next_index() }
' reads.fastq > /dev/null
real    0m28.720s
user    0m27.876s
sys     0m0.450s

$ time awk -v nums=takeThese.txt '
    BEGIN{ while ((getline i < nums) > 0) {h[i]; c++} }
    NR in h{print; if (!--c) exit}
' reads.fastq > /dev/null
real    0m50.060s
user    0m47.564s
sys     0m0.405s

.

$ awk 'BEGIN {for(i=1;i<=100;i++) print i*100}' > takeThat.txt

$ time awk -v nums=takeThat.txt '
    function next_index() {
        ("sort -n " nums) | getline i
        return i
    }
    BEGIN { linenum = next_index() }
    NR == linenum { print; linenum = next_index() }
' reads.fastq > /dev/null
real    0m26.738s
user    0m23.556s
sys     0m0.310s

$ time awk -v nums=takeThat.txt '
    BEGIN{ while ((getline i < nums) > 0) {h[i]; c++} }
    NR in h{print; if (!--c) exit}
' reads.fastq > /dev/null
real    0m0.094s
user    0m0.015s
sys     0m0.000s

但你可以两全其美:

$ time awk -v nums=takeThese.txt '
    function next_index() {
        if ( ( ("sort -n " nums) | getline i) > 0 ) {
            return i
        }
        else {
            exit
        }
    }
    BEGIN { linenum = next_index() }
    NR == linenum { print; linenum = next_index() }
' reads.fastq > /dev/null
real    0m28.057s
user    0m26.675s
sys     0m0.498s


$ time awk -v nums=takeThat.txt '
    function next_index() {
        if ( ( ("sort -n " nums) | getline i) > 0 ) {
            return i
        }
        else {
            exit
        }
    }
    BEGIN { linenum = next_index() }
    NR == linenum { print; linenum = next_index() }
' reads.fastq > /dev/null
real    0m0.094s
user    0m0.030s
sys     0m0.062s

如果我们假设 takeThese.txt 已经排序,则可以简化为:

$ time awk -v nums=takeThese.txt '
    BEGIN { getline linenum < nums }
    NR == linenum { print; if ((getline linenum < nums) < 1) exit }
' reads.fastq > /dev/null
real    0m27.362s
user    0m25.599s
sys     0m0.280s

$ time awk -v nums=takeThat.txt '
    BEGIN { getline linenum < nums }
    NR == linenum { print; if ((getline linenum < nums) < 1) exit }
' reads.fastq > /dev/null
real    0m0.047s
user    0m0.030s
sys     0m0.016s

【讨论】:

  • 不幸的是我不能确定,如果我需要最后的行号不是。但是我当然可以单独测试那个。
  • 您不需要确定或测试任何额外的东西,只需使用它即可。当您确实需要打印最后一个行号时,它不会明显变慢,但当您不需要打印它时,它会明显更快。你今天的脚本会读取 reads.fastq 的每一行,即使 takeThese.txt 中只有第 1 到 10 行,所以在这种情况下会不必要地读取数百万行。阅读第 10 行后,我的脚本将退出。
  • 哇,这真是一个详尽的答案,非常感谢!我现在对takeThese.txt 文件进行了排序,所以我可以选择你的最后一个脚本。它目前正在运行,但需要一段时间来评估手头的真实数据的时间安排。还要感谢页面上的所有其他答案,我将尝试使用 time 将它们与真实数据进行比较。
  • 谢谢,您的排序行解决方案似乎对我目前使用的现有功能带来了最好的改进!
【解决方案2】:

认为问题中的解决方案将 takeThese.txt 中的所有行存储到数组 h[] 中,然后对于 reads.fastq 中的每一行在 h[] 中进行线性搜索行号。

在不同的语言中有几个简单的改进。如果您对 java 不满意,我会尝试 perl。

基本上你应该确保 takeThese.txt 已排序,然后一次只通过 reads.fastq 一行,扫描与 takeThese.txt 中的下一个行号匹配的行号,然后弹出并继续。

由于行的长度不同,您别无选择,只能扫描换行符(大多数语言中的基本for each line-construct)。

perl 中的示例,快速而肮脏但有效

open(F1,"reads.fastq");
open(F2,"takeThese.txt");
$f1_pos = 1;
foreach $index (<F2>) {
   while ($f1_pos <= $index) {
      $out = <F1>; $f1_pos++;
   } 
   print $out;
}

【讨论】:

  • 谢谢,知道排序会加快处理速度。所以我会试一试。
  • 所以它完全按照我说的做,除了它是一个散列而不是一个数组。这使得查找速度更快,但它仍然必须将整个查找文件保存在内存中,并在该哈希中从 reads.fastq 中搜索每个行号,而不是知道要查找的行号。
  • 我只是说,与其通过散列搜索(这可能非常快),不如将当前行号保留在变量中,甚至不进行散列搜索。
【解决方案3】:

我会尝试其中一种

  1. 可能导致误报:

    cat -n reads.fastq | grep -Fwf takeThese.txt | cut -d$'\t' -f20
    
  2. 需要 {bash,ksh,zsh} 之一:

    sed -n -f <(sed 's/$/p/' takeThese.txt) reads.fastq
    
  3. 这类似于 Andreas Wederbrand 的 perl 答案,在 awk 中实现

    awk -v nums=takeThese.txt '
        function next_index() {
            ("sort -n " nums) | getline i
            return i
        }
        BEGIN { linenum = next_index() }
        NR == linenum { print; linenum = next_index() }
    ' reads.fastq
    

但是,如果您要处理大量数据,那么文本处理工具将需要时间。您的另一个选择是将数据导入适当的数据库并使用 SQL 来提取它:数据库引擎就是为这类东西构建的。

【讨论】:

  • 我创建了具有 100M 行的测试文件和另一个具有 1M 行号的测试文件,并尝试了几种(实际上是 3 种)方法来使用 awk 解决这个问题。这个 awk 解决方案比我的任何解决方案都快。 ++
【解决方案4】:

由于我得了流感并且很无聊,我测试了一些方法来尝试加快最初的解决方案。测试文件:

$ awk 'BEGIN {for(i=1;i<=100000000;i++) print i}' > reads.fastq 

$ awk 'BEGIN {for(i=1;i<=1000000;i++) print i*100}' > takeThat.txt

第一个文件只是从 1 到 100000000 的数字。它不代表真实数据,但我对 awk 解决方案之间的执行时间很好奇,所以我假设真实数据只会将结果时间乘以一个常数(之前内存开始耗尽)。

第二个文件代表第一个文件平均分布命中率的百分之一:

100
200
300
...

首先,OP的原始脚本:

$ time awk 'FNR==NR {h[$1]; next} (FNR in h)' takeThese.txt reads.fastq > /dev/null

real    0m52.901s
user    0m52.596s
sys     0m0.284s

我的解决方案:

BEGIN {
    j=1
    while((getline a[++i] < "takeThese.txt") > 0 );  # row numbers to a
} 
NR<a[j] { next }                                     # skip rows before next match
j++                                                  # print and iterate j
j==i { exit }                                        # exit after last hit

定时运行:

$ time awk -f program.awk reads.fastq > /dev/null

real    0m25.894s
user    0m25.676s
sys     0m0.208s

行号文件takeThese.txt预计会被订购。

【讨论】:

    【解决方案5】:

    派对迟到了,但这也可能是一个快速的选择。它利用joins 的原始速度,但需要将匹配字段转换为按字典顺序排列。

    $ join <(awk '{printf "%09d\n", $1}' pick.list) <(nl -w9 -ba -nrz big.file) | 
      cut -d' ' -f2-
    

    预处理您的选择列表以添加前导零,为您的大文件添加行号,前导零(相同宽度),假设您的选择列表按数字顺序排列,否则先排序。

    用您自己的文件名更改“pick.list”和“big.file”的文件名。此外,如果大文件的行数超过 999,999,999,请相应调整宽度(“%09”和“w9”)。

    如果您尝试这样做,请发布您的时间安排。我的猜测是它会比awk 替代品快得多。

    nl 选项

    w9 数字宽度为 9
    ba 将数字添加到空白行以及正文中
    nrz 格式化数字,前导零,右对齐,即000000001

    【讨论】:

    • 谢谢,我试试看。为了添加前导零,我将使用 awk -F: '{ printf "%09d %s\n", $1,$2 }' takeThese.txtawk '{printf("%09d %s\n", NR,$0)}' reads.fastq
    • 选择列表中的第二个字段是什么? join 默认只使用第一个字段,它应该是您在大文件中搜索的行号。另一个应该没问题,但nl 更快。
    • 选择列表中没有第二个字段。正如我之前所写的,我的 shell 脚本是从其他问题中复制+粘贴,然后我尝试了哪些可行的方法。所以我实际上可以删除$2,你是对的。
    • 在测试运行中,joinawk 解决方案相比,当我在具有 2.5 个 mio 条目的较小测试样本上运行它时,它的运行速度出奇地慢。整晚我都会让它继续运行以获取完整数据。
    • 我在与我的笔记本电脑相同的笔记本电脑上尝试了这个解决方案:real 2m27.746s, user 2m26.780s, sys 0m0.848s
    【解决方案6】:

    我在您的 awk 中看到的问题是您将要提取的所有行号加载到一个数组中,然后,对于每一行,您都需要访问该数组。

    我确信in 关键字必须按照循环遍历数组的每个元素并将该索引处的值与FNR 值进行比较...

    因此,如果您有 1,000,000 行要提取,那么对于 reads.fastq 的每一行,您都需要遍历要提取的 1,000,000 行! 100,000,000(reads.fastq 行)X 1,000,000(查找数组长度)=1e+14。这是很多查找。

    同样,awks in 关键字可以做各种花哨的技巧和有效的事情,但最后你应该明白为什么这不起作用。

    一种方法是使用一个包含我们想要的当前行的变量,一个索引变量来跟踪我们在查找数组中的位置,以及一个 max 变量来查看我们是否可以停止处理文件!这样,我们只执行N 数组查找,每行一个我们想要的,其余时间我们将 FNR 与变量进行比较,这应该更快。此外,我们在打印出我们想要的最后一行后停止执行。

    显然,这要求我们有一个要提取的行的排序列表。

    readthese 是您的"takeThese.txt"list.txt 是行编号为 1 - 1,000,000 的文件`

    awk 'BEGIN{i=1; max=1;} FNR==NR{ if($1 != ""){h[max]=$1;  max++; next}} { if(!l){l=h[i]; i++; } if( FNR == l ){ print $0; l=h[i]; i++; if(i == max){  exit; } } }'
    

    以更易读的格式

     awk '
        BEGIN{i=1; max=1;}
    
        FNR==NR{ 
            if($1 != ""){
                h[max]=$1;  max++; next
            }
        } 
        { 
            if(!l){
                l=h[i]; i++;
            }
    
            if( FNR == l ){ 
                print $0;
                l=h[i];
                i++;
                if(i == max){
                  exit;
                }
            } 
        }' readthese list.txt
    

    i 是我们在h 数组中的当前位置,我们存储要提取的行。 max 基本上是h 数组的长度,当i == h 我们知道我们可以停止。 l 是我们要提取的下一行的值。

    编辑:如果您需要对行文件进行排序,可以将 readthese 替换为 &lt;(sort -n readthese)

    【讨论】:

    • 我尝试将您的脚本与此处发布的其他脚本进行比较,但它只给出 1 到 9 之间的行,如果索引较大,则不会打印它们。
    • 哎呀!看起来我在将它们移动到堆栈溢出时设法破坏了这两个命令,现在修复它们......另外,takeThese.txt 已排序并且不包含空行或任何奇怪的东西也很重要。
    • 谢谢!我真的应该更多地了解awk,以便能够自己解决这些问题。
    【解决方案7】:

    reads.fastq 中的行长度是否相同?

    如果是这样,Java 或任何其他语言中的简单算法可以获取takeThese.txt 中的每个行号,并通过将行号乘以行长来找到reads.fastq 中行的开始位置。

    如果不是,那么找到正确行的唯一方法是计算换行符,这意味着读取每个字符。这仍然可能比 awk 快,而且它肯定有助于对行号进行排序。

    【讨论】:

    • 不,不幸的是行长度不同,并且条目以四个为一组。问题是,我对 awk 或 sed 不是很流利,所以也许我错误地提出了这个问题。例如。 R 等效项是这样的:reads.fastq[takeThese.txt, ](假设对象将在文件被调用时被命名)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-30
    相关资源
    最近更新 更多