经济高效地将文件的每一行与所有其他行配对

经济高效地将文件的每一行与所有其他行配对

我有一个非常大的文件,仅包含数字。文件 -

123212
234234
12324
1243223
5453443

并希望将每条线与所有其他线配对。输出如下

123212,234234
123212,12324
123212,1243223
123212,5453443
234234,123212
234234,12324
234234,1243223
234234,5453443
12324,123212
12324,234234
12324,1243223
12324,5453443
1243223,123212
1243223,234234
1243223,12324
1243223,5453443
5453443,123212
5453443,234234
5453443,12324
5453443,1243223

因为输入文件包含超过 50L 条记录。因此,通过循环执行此操作将是一项成本高昂的操作。

答案1

所有创建此输出的方法都将是昂贵的。然而,即使文件比 RAM 大得多,这种方法也可以工作:

$ while read n; do awk -v n="$n" '$1!=n{print n "," $1}' file; done <file
123212,234234
123212,12324
123212,1243223
123212,5453443
234234,123212
234234,12324
234234,1243223
234234,5453443
12324,123212
12324,234234
12324,1243223
12324,5453443
1243223,123212
1243223,234234
1243223,12324
1243223,5453443
5453443,123212
5453443,234234
5453443,12324
5453443,1243223

写在多行上

while read n
do
    awk -v n="$n" '$1!=n{print n "," $1}' file
done <file

read nfile一次读取一个数字。对于每个,运行 awk 脚本来创建第一列中的n输出部分。n该选项-v n="$n"创建一个名为 的 awk 变量n,该变量与 shell 变量具有相同的值n。该条件$1!=n选择file文件中该行的编号不同于 的那些行n。对于这些行,我们打印 number n,后跟一个逗号,然后是该行的数字。

答案2

我同意约翰的观点,无论如何,这都会很昂贵。

join -o 1.2,1.3,2.2,2.3 -j 1 <(awk '{printf "%s %d %s\n", "x", FNR, $0}' file) \
<(awk '{printf "%s %d %s\n", "x", FNR, $0}' file) |
awk '$1 != $3{print $2, $4}'

您可以启动两个进程替换实例,每个实例使用awk,返回文件的内容,并在每个记录的开头插入两个合成字段,第一个字段包含固定值(x在上面的示例中),第二个字段包含行数字。然后可以将其馈送到join规定字段 1 作为连接字段。这会导致进程替换的第一个实例中的每个记录与第二个实例中的每个记录相匹配。使用awk后处理器丢弃与其自身匹配的记录实例(利用在这些情况下行号将相等的事实)

答案3

您是否还会考虑使用完全不同的应用程序,例如kdb+

(其 32 位版本是免费的-就像啤酒一样内存限制为 4 GB)

一些基础知识:

  1. 将文件作为单列数字列表加载。

    flip (enlist "I";",") 0: hsym `$"/path/to/input"
    
    • 0:是一个从输入文件加载的多用途函数。出于本问题的目的,将(enlist "I";",")其简单地视为文件格式规范,然后应用flip将输出转换为可用列表。
  2. 应用cross功能。

    a cross a:... <from above>
    
    • q(kdb+ 的语言)可以非常简洁,但这也意味着变量赋值(例如a:42设置42a)可以以有序的方式分配和使用。在这里,我们将文件输入分配给一个变量a,以便我们可以cross自己。
  3. 准备字符串输出。

    "," 0: flip a... <from above>
    
    • 再次0:用于将结果准备为逗号分隔的字符串。
  4. 写入输出文件。

    (hsym `$"/path/to/output") 0: ","... <from above>
    
    • 这一次,我们需要()围绕 的 左侧参数0:来明确功能用法hsym。最后,0:在这里用于第三写入文件的时间。

把它们放在一起:

(hsym`$"/path/to/output")0:","0:flip a cross a:flip(enlist"I";",")0:hsym`$"/path/to/input"

现在,说说坏消息……

32 位免费版本的 4 GB RAM 限制最多只能处理大约6000行...

q)\ts (hsym`$"output6k.txt")0:","0:flip a cross a:flip(enlist"I";",")0:hsym`$"test6k.txt"
23428 3378126736
q)count distinct flip (enlist "I";",") 0:hsym`$"test6k.txt"
6000

\ts显示所用时间不到 24 秒,占用了近 3.4 GB 内存。

(我决定仍然将其作为答案发布,以免浪费我的努力......

答案4

创建一个 SQLite 数据库以将每一行相互连接:

sqlite3 tmp.db
sqlite> CREATE TABLE T (x INTEGER);
sqlite> .import input_file T
sqlite> .mode csv
sqlite> .output output_file
sqlite> SELECT * FROM T JOIN T AS S WHERE T.x != S.x;

该解决方案不保证输入行的顺序,但它仅启动一个进程,没有外部循环,并且应该在有限的 RAM 下工作。

更新: 修复 select 语句,使其不会将值与其自身连接起来。如果相等的值只要不在同一行就可以,请使用WHERE T.rowid != S.rowid

相关内容