潜在的に巨大なファイルからn個のランダムな行を読み取ります


16

この課題は、ファイル全体をメモリに読み込まずに、潜在的に巨大なファイルからランダムな行を読み込むことです。

入力

整数nとテキストファイルの名前。

出力

n 置換せずにランダムに均一に選択されたテキストファイルの行。

これnは、1からファイル内の行数の範囲にあると想定できます。

n答えが均一になる範囲からランダムに数値をサンプリングする場合は注意してください。rand()%nCでは、たとえば均一ではありません。すべての結果は等しく等しくなければなりません。

ルールと制限

テキストファイルの各行の文字数は同じで、80文字以下です。

次の場合を除き、コードはテキストファイルの内容を読み取ってはなりません。

  • 出力する行。
  • テキストファイルに1行あたりの文字数を計算する最初の行。

テキストファイルの各文字は、正確に1バイトであると想定できます。

行区切り文字は1バイト長であると想定されます。ソリューションでは、この必要性が指定されている場合にのみ、2バイトの長い行区切り文字を使用できます。最後の行が行区切り文字で終了していると仮定することもできます。

答えは完全なプログラムである必要がありますが、便利な方法で入力を指定できます。

言語とライブラリ

任意の言語またはライブラリを使用できます。

ノート

ファイルの行数の計算に懸念がありました。nimiがコメントで指摘しているように、これはファイルサイズと1行あたりの文字数から推測できます。

動機

チャットでは、これは本当に「YなしでXを行う」の質問かと尋ねられた人もいました。これを解釈して、制限が異常に人工的なものであるかどうかを尋ねます。

巨大なファイルから行をランダムにサンプリングするタスクは珍しくなく、実際に私が時々しなければならないタスクです。これを行う1つの方法はbashです。

shuf -n <num-lines>

ただし、ファイル全体を読み取るため、大きなファイルでは非常に遅くなります。


なぜ下票なのですか?

3
これはCのような言語では簡単ですが、他の言語ではfseek不可能です。さらに、nファイルの行数よりも大きい場合はどうなりますか?
メゴ

4
@Mego:ポイントについてb):ファイルサイズを行の長さで割ることにより、行数を計算できます。
nimi

8
YなしでXを実行することは、「これは必ずしも悪いことではありません」で始まる警告です。主な問題は、「+を使用しない」などの人為的な制限sum()です。ファイルをメモリに読み込まないことは、明確で一貫した制限であり、決してway意的ではありません。メモリよりも大きいファイルでテストできますが、言語の違いによって回避することはできません。また、実世界での用途もあります(ただし、ゴルフには必要ありません...)。
-trichoplax

1
これは実際には、複雑な制限のあるゴルフであり、潜在的に巨大なファイルがあるにもかかわらずメモリ使用量が制限されているようです。コードに特定のものがないことではなく、コードがどのように動作するかについての制限です。
-xnor

回答:


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のサイズを行の長さで除算
数値入力のプロンプト(希望の行数) )
?Xランダム選択(置換なし)最初のY自然数
¯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ます。

システムは4つのランダムなラインを出力します。

実生活

実際には、ファイル名を指定して引数としてカウントし、結果をテーブルとして受け取りたい場合があります。これは、次のように入力することで実行できます。

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

ここで、MyLinesに次の3つのランダムな行が含まれるようにします。

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

countが指定されていない場合、ランダムな1行だけを返す方法は次のとおりです。

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
}

削除:*私はいくつかのAPLシステム上の標準である0・原点モードで実行してバイトを救うことができる¯1+し、挿入1+の前に10


Ahh .. APL :) Linuxでこのコードをテストする方法はありますか?

@Lembik確かに、このコードはクロスプラットフォームです。dyalog.comからダウンロード
アダム

APLを読んでいないので、コードを説明してもらえますか?難しい部分は、置換せずにサンプリングし、ファイル内の適切な場所に直接ジャンプして行を読み取ることです。

@Lembikその部分は簡単です。NREADの引数は、TieNumber ConversionCode BytesToRead [StartByte]です。必要なバイトだけを読み取ります。あとは何を読むかを考え出すだけです。
アダム

@Lembik私の答えが賞金に勝たなかった理由が知りたいです。
アダム

7

ルビー、104 94 92 90バイト

ファイル名と行数がコマンドラインに渡されます。たとえば、プログラムがshuffle.rbでファイル名がのa.txt場合、ruby shuffle.rb a.txt 3ランダムな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

stdinからファイル名と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 rocks :)

1
すでに選択されている行を選択しないようにするにはどうすればよいですか?
feersum

@feersum:ああ、私はその部分を見逃した。修繕。
-nimi

stackoverflow.com/questions/13779630/が表示されます……やや冗長です!

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()我々がヒットするまで、最初のラインを通ってよ\n、私たちのライン長カウンタ各文字をインクリメント、文字を。次に$t、ファイルのサイズ(つまり、ストリームの長さ)を1行あたりの文字数(プラス1文字でターミネーターをカウントする)で割った値に1を加えた値(ゼロインデックスが付けられているため)を設定します。次に、$z行の長さになるようにバッファーを構築します。

最後の行は、..範囲演算子を使用して動的配列を構築します。1私達管とアレイGet-Random-Cのount $nランダム選択に$n反復することなく行番号。ラッキーナンバーはでループにパイプされます|%{...}.Seek特定の場所への各反復、そして.Read1行分の文字で、に格納され$zます。$zchar-arrayとして再キャストし、-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のみであり、完全なプログラムです。

編集:インライン範囲とpython3.xの互換性について@Megoに感謝します。編集2:私はそれについて2つのコメントを得たため、印刷がどこにあるかを明確化。(コメントは明らかにコードやバイト数の一部ではありません。)


ありがとうございました!どの部分が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

ただし、printステートメントは必要です。

printはリストの内包です。
アレクサンダーニグル

2

ルア、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エントリの下に行きましたが、まだ2位です(現在)。呪い、Rubyエントリー!


1
Luaは私が学んだ最初のプログラミング言語であり、それ以来私が学んだすべてのものでさえ、それは今でも私のお気に入りです。簡単に書くことができるので、非常に用途が広いです。
-Blab

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スキップ値skip* ibsバイト
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

-1のヒントをくれた@PeterCordesに感謝します!


1
私はいつもbashの回答が大好きですが、これがファイル全体を読み取れないことを説明できますか?

2
@Lembikは説明を追加しました!
ドムヘイスティングス

1
bs=代わりにibs=設定することもできobsます。私はあなたが交換することはできません推測if=$2して<$2、これはまだであることから、けれどもxargsのコマンドライン。 \<$2どちらも機能しません(xargsは、シェルなしでexecを直接使用します)。
ピーターコーデス

これがあまり多くないことを願っていますが、この答えが好きです:) 1GBのファイルでテストしました。

1
re:stderrをstdinにリダイレクト:また2>&-、stderrをで閉じることもできます。そのため、出力がどこかに行く危険はありません(たとえば、stdinが読み取り/書き込みファイル記述子である場合)。これは、GNUで動作しますdd:それはまだそのを生成stdoutしようとしてへの書き込みに失敗する前に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

バイトを保存するには、インポートと*の間のスペースを削除します。
mriklojn

1
@nimiこのチャレンジに完全なプログラムを要求することは、デフォルトのコード形式ルールを任意にオーバーライドするようです
-pppery

@ppperry:はい、多分、しかしそれはまさにそれです。
nimi

ファイルの長さを取得するには、f.seek(0,2)を使用します。これにより、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#で十分ではないことをおaびします。

@Lembikあなたは正しいです、重複については考えませんでした。また、行の数を数えて行番号で行を抽出できるため、行の長さが可変になる場合があります。
Master117

しかし、行番号を知っているだけでファイル内の場所にジャンプする必要がありますか?すべての行の長さが同じでない限り、それがどこにあるかはわかりません。

@Lembik File.ReadLines( "pathToFile")は、ファイルのすべての行で遅延列挙を作成します。File.ReadLines( "pathToFile")。elementAt(19)は、ファイルの19行目を返します。すべてのラインスタートの地図のようなものです。
Master117

Lazy enumerationがファイル内で悲しいことにジャンプ(またはシーク)するとは思わない。そのため、現在のルールには適合していません。

1

Python(141バイト)

各行を同じ確率で保持し、パイプでも使用します。しかし、それは質問の先読み制限に答えません...

使用法cat largefile | python randxlines.py 100またはpython 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
この質問の要点は、入力ストリームを探す必要があるということです。おそらく、それはあなたが無視している質問の制限の一部であると言うべきです(ただし、パイプからの読み取りの使用例はそれをかなり明確にしますが)。python ./randxlines.py 100 < largefileしかし、stdinからの読み取りは、適切な答えを得るには問題ありません。その場合stdinは、シーク可能です。
ピーターコーデス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.