从潜在的巨大文件中读取n条随机行


16

这个挑战是关于从潜在的巨大文件中读取随机行,而不是将整个文件读取到内存中。

输入值

整数n和文本文件的名称。

输出量

n 文本文件的多行随机选择而不替换。

您可以假定该n值在文件行数的范围内(1)。

n从您得到的答案是一致的范围内随机抽样数字时,请当心。rand()%n例如,C中的代码不一致。每个结果都必须具有同等的可能性。

规则与限制

文本文件的每一行将具有相同数量的字符,并且最多不超过80个字符。

您的代码不得读取文本文件的任何内容,但以下情况除外:

  • 它输出的那些行。
  • 第一行计算出文本文件中每行有多少个字符。

我们可以假设文本文件中的每个字符仅占用一个字节。

行分隔符假定为1个字节长。解决方案仅在指定此需求时才可以使用2字节长的行分隔符。您还可以假设最后一行由行分隔符终止。

您的答案应该是完整的程序,但是您可以通过任何方便的方式指定输入。

语言和图书馆

您可以使用任何喜欢的语言或库。

笔记

人们担心要计算文件中的行数。正如nimi在注释中指出的那样,您可以从文件大小和每行字符数推断出这一点。

动机

在聊天中,有人问这是否真的是“没有Y的X”。我解释这是为了问这些限制是否异常人为。

从海量文件中随机采样行的任务并不罕见,实际上我有时必须要做。一种方法是在bash中:

shuf -n <num-lines>

但是,这对于大型文件来说非常慢,因为它会读取整个文件。


为什么要下票?

3
对于像C这样具有的语言,这是微不足道的fseek,而在其他语言中则是不可能的。此外,如果n大于文件中的行数怎么办?
芒果

4
@Mego:关于您的观点b):您可以通过将文件大小除以一行的长度来计算行数。
nimi

8
在没有Y的情况下执行X是从“这并不总是很糟糕”开始的警告。主要问题是人为的限制,例如“请勿使用+”,这会限制具有的语言sum()。不将文件读入内存是一个明确且一致的限制,这绝对不是任意的。可以使用大于内存的文件进行测试,该文件无法通过语言差异解决。它也碰巧具有现实世界的应用(尽管对于高尔夫来说不是必需的...)。
trichoplax

1
看起来这实际上是一个受限制的复杂性代码,尽管有潜在的巨大文件,但内存使用受到限制。这不是在代码中没有某些内容,而是对代码行为的限制。
xnor

Answers:


5

Dyalog APL,63字节

⎕NREAD¨t 82l∘,¨lׯ1+⎕?(⎕NSIZE t)÷l←10⍳⍨⎕NREAD 83 80,⍨t←⍞⎕NTIE 0

提示输入文件名,然后提示输入多少行。

说明

提示输入文本(文件名)
⎕NTIE 0使用下一个可用的联系编号(在干净的系统上为-1 ) 将文件联系在一起。
t←将所选的联系编号存储为t
83 80,⍨Append [83,80],产生[-1,83,80]
⎕NREAD读取前80个字节文件-1的8位整数(转换代码83)
10⍳⍨查找第一个数字的索引10(LF)
l←将行长 存储为l
(⎕NSIZE t)÷用文件长度的文件-1的大小除以行长
提示输入数字(所需的行数) ) 从前
?Y个自然数中减去X个随机选择(不替换),
¯1+加-1以获得0的行号*
乘以
t 82l∘,¨行长,以获取起始字节 将[-1,82,LineLength]前缀到每个起始字节(创建的参数列表⎕NREAD
⎕NREAD¨ 将每行读为8位字符(转换代码82)

实际例子

文件/tmp/records.txt包含:

Hello
Think
12345
Klaus
Nilad

通过在APL会话中输入以下内容,使RandLines程序完全包含上述代码:

∇RandLines
⎕NREAD¨t 82l∘,¨lׯ1+⎕?(⎕NSIZE t)÷l←10⍳⍨⎕NREAD 83 80,⍨t←⍞⎕NTIE 0
∇

在APL会话中RandLines,按Enter。

系统将光标移动到下一行,这是字符数据的长度为0的提示符;输入/tmp/records.txt

系统现在输出⎕:并等待数字输入。输入4

系统输出四个随机行。

现实生活

实际上,您可能希望提供文件名并计数作为参数,并以表的形式接收结果。可以通过输入以下内容来完成:

RandLs←{↑⎕NREAD¨t 82l∘,¨lׯ1+⍺?(⎕NSIZE t)÷l←10⍳⍨⎕NREAD 83 80,⍨t←⍵⎕NTIE 0}

现在,使MyLines包含三个随机行,它们分别是:

MyLines←3 RandLs'/tmp/records.txt'

如果未指定count,如何只返回一条随机行:

RandL←{⍺←1 ⋄ ↑⎕NREAD¨t 82l∘,¨lׯ1+⍺?(⎕NSIZE t)÷l←10⍳⍨⎕NREAD 83 80,⍨t←⍵⎕NTIE 0}

现在,您可以同时执行以下操作:

MyLines←2 RandL'/tmp/records.txt'

和(注意没有左参数):

MyLine←RandL'/tmp/records.txt'

使代码可读

打高尔夫球的APL单衬板不是一个好主意。这是我在生产系统中的写法:

RandL←{ ⍝ Read X random lines from file Y without reading entire file
    ⍺←1 ⍝ default count
    tie←⍵⎕NTIE 0 ⍝ tie file
    length←10⍳⍨⎕NREAD 83 80,⍨tie ⍝ find first NL
    size←⎕NSIZE tie ⍝ total file length
    starts←lengthׯ1+⍺?size÷length ⍝ beginning of each line
    ↑⎕NREAD¨tie 82length∘,¨starts ⍝ read each line as character and convert list to table
}

*我可以通过在0来源模式下运行来保存字节,这在某些APL系统上是标准的:在之前¯1+插入和插入。1+10


啊.. APL :)有什么方法可以在Linux中测试此代码?

@Lembik当然,此代码是跨平台的。从dyalog.com下载–Adám
2013年

由于我不阅读APL,您能解释一下代码吗?棘手的部分是没有替换的采样行并且直接跳到文件中的正确位置以读取这些行。

@Lembik这部分很容易。READNREAD的参数是TieNumber ConversionCode BytesToRead [StartByte]。它仅读取所需的字节。剩下的只是弄清楚要读什么。
阿达姆,2013年

@Lembik我很好奇为什么我的答案没有赢得赏金。
阿达姆

7

Ruby,104 94 92 90字节

文件名和行数被传递到命令行中。例如,如果程序为shuffle.rb,文件名为a.txt,则运行ruby shuffle.rb a.txt 3三行。

通过open在Ruby中发现语法而不是-4个字节File.new

f=open$*[0]
puts [*0..f.size/n=f.gets.size+1].sample($*[1].to_i).map{|e|f.seek n*e;f.gets}

另外,这是一个85字节的匿名函数解决方案,该解决方案以字符串和数字作为参数。

->f,l{f=open f;puts [*0..f.size/n=f.gets.size+1].sample(l).map{|e|f.seek n*e;f.gets}}

100字节以下!也许Ruby毕竟是最好的高尔夫语言。“采样”会避免重复吗?

@Lembik ruby-doc.org/core-2.2.0/Array.html#method-i-sample确实可以避免重复。不要告诉我...我应该重复吗?
价值墨水

不,你是完美的:)

您可以通过从stdin读取来保存任何字节吗? ruby shuffle.rb 3 < a.txt给你一个令人难忘的标准输入。不过,IDK Ruby。
彼得·科德斯

1
@PeterCordes这是有道理的,但是如上所述,失败的要点是Ruby无法读取stdin的文件大小,因此无法解决。
价值墨水

5

Haskell中,240个 224 236字节

import Test.QuickCheck
import System.IO
g=hGetLine
main=do;f<-getLine;n<-readLn;h<-openFile f ReadMode;l<-(\x->1+sum[1|_<-x])<$>g h;s<-hFileSize h;generate(shuffle[0..div s l-1])>>=mapM(\p->hSeek h(toEnum 0)(l*p)>>g h>>=putStrLn).take n

从标准输入读取文件名和n。

怎么运行的:

main=do
  f<-getLine                   -- read file name from stdin
  n<-readLn                    -- read n from stdin
  h<-openFile f ReadMode       -- open the file
  l<-(\x->1+sum[1|_<-x])<$>g h -- read first line and bind l to it's length +1
                               -- sum[1|_<-x] is a custom length function
                               -- because of type restrictions, otherwise I'd have
                               -- to use "toInteger.length"
  s<-hFileSize h               -- get file size
  generate(shuffle[0..div s l-1])>>=
                               -- shuffle all possible line numbers 
  mapM (\->p  ...  ).take n    -- for each of the first n shuffled line numbers 
     hSeek h(toEnum 0).(l*p)>> -- jump to that line ("toEnum 0" is short for "AbsoluteSeek")
     g h>>=                    -- read a line from current position
     putStrLn                  -- and print

由于效率低下,可怕的是,要为多行文件运行该程序需要花费大量时间和内存shuffle

编辑:我错过了“无替换的随机”部分(感谢@feersum注意!)。


Haskell岩石:)

1
如何避免选择已经选择的线?
feersum '16

@feersum:哦,我错过了那部分。固定。
nimi


1
在小空间内不更换样本的情况下,也许应该面临另外一个挑战。

3

PowerShell v2 +,209字节

param($a,$n)
$f=New-Object System.IO.FileStream $a,"Open"
for(;$f.ReadByte()-ne10){$l++}
$t=$f.Length/++$l-1
[byte[]]$z=,0*$l
0..$t|Get-Random -c $n|%{$a=$f.Seek($l*$_,0);$a=$f.Read($z,0,$l-1);-join[char[]]$z}

将输入$a作为文件名和$n行数。请注意,该名称$a必须是全路径文件名,并假定为ANSI编码。

然后,我们建立一个新的FileStream对象,并告诉它来访问文件$aOpen特权。

for循环.Read()S穿透第一线,直到我们打了一个\n字,我们增加线路长度计数器每个字符。然后,我们$t将文件大小设置为等于文件流的大小(即流的长度)除以每行有多少个字符(加一个,这样它就算出终止符)减去一(因为我们的索引为零)。然后我们构造缓冲区$z为行长。

最后一行使用..范围运算符构造一个动态数组。1我们通过管道将数组传递Get-Random-C$n以随机选择$n行号而不重复。幸运数字通过传递到一个循环中|%{...}。每次迭代时,我们都会.Seek到特定位置,然后.Read将一行中的字符值存储到中$z。我们重新铸造$z为字符数组,然后-join其重新转换在一起,将结果字符串保留在管道上,并且在程序结尾处隐式包含输出。

从技术上讲,我们应该以$f.Close()以关闭文件调用结束,但这会占用字节!:p

a.txt:
a0000000000000000000000000000000000000000000000001
a0000000000000000000000000000000000000000000000002
a0000000000000000000000000000000000000000000000003
a0000000000000000000000000000000000000000000000004
a0000000000000000000000000000000000000000000000005
a0000000000000000000000000000000000000000000000006
a0000000000000000000000000000000000000000000000007
a0000000000000000000000000000000000000000000000008
a0000000000000000000000000000000000000000000000009
a0000000000000000000000000000000000000000000000010

PS C:\Tools\Scripts\golfing> .\read-n-random-lines.ps1 "c:\tools\scripts\golfing\a.txt" 5
a0000000000000000000000000000000000000000000000002 
a0000000000000000000000000000000000000000000000001 
a0000000000000000000000000000000000000000000000004 
a0000000000000000000000000000000000000000000000010 
a0000000000000000000000000000000000000000000000006 

1从技术上讲,这意味着我们最多只能支持50,000行,因为这是可以以此方式动态创建的最大范围。:-/但是,我们不能只是循环执行一次Get-Random命令$n,每次循环都丢弃重复项,因为这不是确定性的...


2

Python 3中,146个 139字节

from random import*
i=input
f=open(i())
l=len(f.readline())
[(f.seek(v*l),print(f.read(l)))for v in sample(range(f.seek(0,2)//l),int(i()))]
#print is here^

输入:[文件名] \ n [行] \ n

此解决方案从@pppery大量盗用,但仅适用于python3.5 是一个完整的程序。

编辑:感谢@Mego的内联范围和python3.x兼容性。Edit2:澄清打印的位置,因为我对此有两个评论。(注释显然不是代码或字节数的一部分。)


谢谢!python 3.5仅哪一部分?

2
r=range(f.seek(0,2)//l)将起作用,将减少3个字节并消除对3.5字节的需要。更好的是,通过在range调用中内联该调用来减少3个字节sample。此外,这不是一个完整的程序-您需要实际打印列表。
芒果

@Lembik:那是3.5,仅因为我用过,r=[*range(f.seek(0,2)//l)]因为我以为我不能sample发电。原来我可以。@Mega:这是完整的,因为它在列表理解中打印出每一行print(f.read(l))
Alexander Nigl

但是,您确实需要打印声明。

打印在列表理解中。
亚历山大·尼格

2

Lua中,126 122

r=io.read;f=io.open(r())c=2+f:read():len()for i=1,r()do f:seek("set",c*math.random(0,f:seek("end")/c-1))print(f:read())end

使用2个字节作为换行符。将2更改为1表示1。我只有2,因为这就是我的测试文件。

在PHP条目下找到自己,但仍然排名第二(当前)。诅咒你,Ruby入门!


1
Lua是我学习的第一门编程语言,尽管从那时起我已经学过很多东西,但它仍然是我的最爱。它具有多种用途,易于编写。
布拉博

2

Bash(好吧,coreutils),100字节

n=`head -1 $2|wc -c`;shuf -i0-$[`stat -c%s $2`/$n] -n$1|xargs -i dd if=$2 bs=$n skip={} count=1 2>&-

说明

这样可以避免读取整个文件dd以提取我们需要的文件部分而不必完全读取文件,不幸的是,由于使用了所有必须指定的选项,它最终会变得非常大:

if是输入文件
bs的块大小(此处我们将其设置为$n第一行的长度
skip设置为从中提取的随机整数,shuf并等于ibs值skipping skip* ibsbytes 返回
countibs长度部分的数量)
status=none是需要去掉通常由以下人员输出的信息dd

我们使用来获取head -1 $2|wc -c行长,使用来获取文件大小stat -c%s $2

用法

另存为file.sh并使用运行file.sh n filename

时机

time ~/randlines.sh 4 test.txt
9412647
4124435
7401105
1132619

real    0m0.125s
user    0m0.035s
sys     0m0.061s

time shuf -n4 test.txt
1204350
3496441
3472713
3985479

real    0m1.280s
user    0m0.287s
sys     0m0.272s

以上是使用生成的68MiB文件的时间seq 1000000 9999999 > test.txt

感谢@PeterCordes的-1小费!


1
我一直很喜欢bash的答案,但是您能解释一下它不会读取整个文件吗?

2
@Lembik添加了解释!
唐·黑斯廷斯

1
您可以bs=代替ibs=,因为也可以进行设置obs。我猜你不能代替if=$2<$2虽然,因为这仍然是xargs的命令行。 \<$2也不起作用(xargs在没有外壳的情况下直接使用exec)。
彼得·科德斯

我希望这不是太多,但我有点喜欢这个答案:)刚刚用1GB的文件进行了测试。

1
回复:将stderr重定向到stdin:您也可以使用来关闭stderr 2>&-,这样就不会有输出到任何地方的危险(例如,如果stdin碰巧是一个读写文件描述符)。它可以与GNU一起使用ddstdout在尝试写入和写入失败之前,它仍会生成它stderr
彼得·科德斯

1

Python的3 - 161个160 149字节

from random import*;
def f(n,g):f=open(g);l=len(f.readline());r=list(range(f.seek(0,2)/l));shuffle(r);[(f.seek(v*l),print(f.read(l)))for v in r[:k]]

这段代码定义了一个函数,就像 f(10,'input.txt')


1
这个挑战需要一个完整的程序,所以恐怕函数定义还不够。
nimi

要节省一个字节,请删除import和*之间的空间。
mriklojn

1
@nimi需要一个完整的程序来应对这一挑战,似乎是在随意重写默认的代码格式规则
pppery

@ppperry:是的,也许,但这就是事实。
nimi

要获取文件的长度,可以使用f.seek(0,2),这会使import os和os.stat变得过时。
亚历山大·尼格

1

C#259字节,无重复

class Program{static void Main(string[]a){int c=Convert.ToInt32(a[1]);var h=File.ReadLines(a[0]);HashSet<int>n=new HashSet<int>();while(n.Count<c)n.Add(new Random().Next(0,h.Count()));for(;c>0;c--)Console.WriteLine(h.Skip(n.ElementAt(c-1)).Take(1).First());}}

不打高尔夫球

class Program{static void Main(string[] a)
{
        int c = Convert.ToInt32(a[1]);
        var h = File.ReadLines(a[0]);
        HashSet<int> n = new HashSet<int>();
        while (n.Count < c)
            n.Add(new Random().Next(0, h.Count()));           
        for (; c > 0; c--)
            Console.WriteLine(h.Skip(n.ElementAt(c-1)).Take(1).First());
    }
}

File.ReadLines是惰性的。这具有额外的好处,即所有行的长度可以不同。

运行它将是:

sample.exe a.txt 10000

C#206字节重复

class Program{static void Main(string[]a){var n=new Random();int c=Convert.ToInt32(a[1]);var h=File.ReadLines(a[0]);for(;c>0;c--)Console.WriteLine(h.Skip((int)(n.NextDouble()*h.Count())).Take(1).First());}}

不打高尔夫球

class Program
{
    static void Main(string[] a)
    {
        Random n = new Random();
        int c = Convert.ToInt32(a[1]);
        var h = File.ReadLines(a[0]);
        for (; c > 0; c--)
            Console.WriteLine(h.Skip((int)(n.NextDouble()*h.Count())).Take(1).First());
    }
}

我没有完全关注您的解决方案。如果所有行的长度都不同,则该任务是不可能的。另外,您如何随机采样生产线而不进行精确替换?我很抱歉,我的C#不够好。

@Lembik你是对的,我没想到重复。而且我可以计算行数并按行号提取行,这就是为什么行可能变长的原因。
Master117 '16

但是您只知道行号就必须跳到文件中的某个位置,不是吗?除非所有行的长度都相同,否则您无法确定该位置。

@Lembik File.ReadLines(“ pathToFile”)在文件的所有行上创建一个惰性枚举,File.ReadLines(“ pathToFile”)。elementAt(19)返回文件的第19行。有点像所有Linestarts的地图。
Master117 '16

我不认为惰性枚举会令人遗憾地在文件中跳转(或查找)。因此,它不适合当前的规则。

1

Python(141字节)

保持每行相等的可能性,也与管道一起使用。但是它没有回答问题的跳过限制...

用法cat largefile | python randxlines.py 100python randxlines 100 < largefile(如@petercordes指出的)

import random,sys
N=int(sys.argv[1])
x=['']*N
for c,L in enumerate(sys.stdin):
    t=random.randrange(c+1)
    if(t<N):x[t] = L
print("".join(x))

3
这个问题的全部重点是您必须在输入流中进行搜索。您可能应该说这您忽略的问题限制的一部分(尽管“从管道读取”的示例用法已经很清楚了)。不过,从stdin读取python ./randxlines.py 100 < largefile具有正确答案是可以的,在这种情况下,stdin将是可取的。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.