Thay thế chuỗi trong một tệp văn bản lớn (70GB), một dòng


126

Tôi có một tệp lớn (70GB), một dòng , tệp văn bản và tôi muốn thay thế một chuỗi (mã thông báo) trong đó. Tôi muốn thay thế mã thông báo <unk>, bằng một mã thông báo giả khác ( vấn đề găng tay ).

Tôi đã thử sed:

sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

nhưng tập tin đầu ra corpus.txt.newcó byte không!

Tôi cũng đã thử sử dụng perl:

perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

nhưng tôi đã hết một lỗi bộ nhớ.

Đối với các tệp nhỏ hơn, cả hai lệnh trên đều hoạt động.

Làm thế nào tôi có thể thay thế một chuỗi là một tập tin như vậy? Đây là một câu hỏi liên quan, nhưng không có câu trả lời nào phù hợp với tôi.

Chỉnh sửa : Điều gì về việc chia tệp thành từng phần 10 GB (hoặc bất cứ thứ gì) và áp dụng sedcho từng tệp và sau đó hợp nhất chúng với cat? Điều đó có ý nghĩa? Có một giải pháp thanh lịch hơn?


như @Gilles đã lưu ý, bạn có thể phát hiện một số ký tự lặp đi lặp lại có thể đóng vai trò là dấu phân cách tùy chỉnh trong dòng lớn duy nhất của bạn không?
RomanPerekhrest

Tôi nghĩ rằng một công cụ chỉ có thể thực hiện tìm kiếm và thay thế, nhưng không phải bất kỳ regex phức tạp nào, sẽ nhanh hơn. Nó cũng sẽ không được hưởng lợi từ việc thực hiện một dòng tại một thời điểm, vì vậy sẽ không bị sặc trên tệp này. Thật không may, tôi không biết về sự tồn tại của một công cụ như vậy, mặc dù nó sẽ không khó để viết. Nếu nó là một trong những thay thế sau đó thay thế bằng các ký tự dòng mới như trong một trong những câu trả lời có lẽ sẽ dễ dàng nhất.
ctrl-alt-delor

Tập tin của bạn có chứa gì khác ngoài ASCII không? Nếu vậy, tất cả các xử lý unicode có thể được bỏ qua và các byte thô có thể được xử lý.
Patrick Bucher

Tôi đồng ý với @PatrickButcher Hãy nhìn vào một bức tranh lớn hơn. Bên cạnh nhu cầu ngay lập tức để thay thế văn bản này, tập tin này còn được sử dụng cho mục đích gì khác? Nếu nó là một bản ghi của một số loại, không ai sẽ có thể làm việc với nó một cách hiệu quả. Nếu đó là tệp dữ liệu mà một số ứng dụng sử dụng, thì ứng dụng đó phải chịu trách nhiệm duy trì dữ liệu trong tệp đó.
Thomas Carlisle

2
Bạn có thể sử dụng splitvới -btùy chọn xác định kích thước tệp chunk theo byte. Xử lý lần lượt từng bộ phận sử dụng sedvà lắp ráp lại. Có một rủi ro là <unk>có thể chia thành hai tệp và sẽ không được tìm thấy ...
Vladislavs Dovgalecs

Câu trả lời:


106

Các công cụ xử lý văn bản thông thường không được thiết kế để xử lý các dòng không phù hợp với RAM. Họ có xu hướng làm việc bằng cách đọc một bản ghi (một dòng), thao tác với nó và xuất kết quả, sau đó chuyển sang bản ghi tiếp theo (dòng).

Nếu có một ký tự ASCII xuất hiện thường xuyên trong tệp và không xuất hiện trong <unk>hoặc <raw_unk>, thì bạn có thể sử dụng ký tự đó làm dấu tách bản ghi. Vì hầu hết các công cụ không cho phép phân tách bản ghi tùy chỉnh, trao đổi giữa ký tự đó và dòng mới. trxử lý byte, không phải dòng, vì vậy nó không quan tâm đến bất kỳ kích thước bản ghi nào. Giả sử rằng ;hoạt động:

<corpus.txt tr '\n;' ';\n' |
sed 's/<unk>/<raw_unk>/g' |
tr '\n;' ';\n' >corpus.txt.new

Bạn cũng có thể neo vào ký tự đầu tiên của văn bản bạn đang tìm kiếm, giả sử rằng nó không được lặp lại trong văn bản tìm kiếm và nó xuất hiện đủ thường xuyên. Nếu tệp có thể bắt đầu bằng unk>, hãy thay đổi lệnh sed sed '2,$ s/…để tránh khớp giả.

<corpus.txt tr '\n<' '<\n' |
sed 's/^unk>/raw_unk>/g' |
tr '\n<' '<\n' >corpus.txt.new

Ngoài ra, sử dụng ký tự cuối cùng.

<corpus.txt tr '\n>' '>\n' |
sed 's/<unk$/<raw_unk/g' |
tr '\n>' '>\n' >corpus.txt.new

Lưu ý rằng kỹ thuật này giả định rằng sed hoạt động liền mạch trên một tệp không kết thúc bằng một dòng mới, tức là nó xử lý dòng một phần cuối cùng mà không cắt bớt nó và không nối thêm một dòng mới. Nó hoạt động với GNU sed. Nếu bạn có thể chọn ký tự cuối cùng của tệp làm dấu tách bản ghi, bạn sẽ tránh mọi rắc rối về tính di động.


8
Tôi không có tệp nào để kiểm tra, nhưng trong Awk, bạn có thể chỉ định "Trình phân tách bản ghi" và "Trình phân tách bản ghi đầu ra". Vì vậy, giả sử bạn có một ít dấu phẩy trong tệp của mình, bạn có thể giải quyết vấn đề này bằng: awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}' Không?
tự đại diện

4
@Wildcard Vâng, đó là một giải pháp khác. Awk có xu hướng chậm hơn sed, đó là lý do tại sao tôi không cung cấp nó như là giải pháp ưa thích cho một tệp lớn.
Gilles

Bạn có thể đặt các dấu phân cách kỷ lục trong Perl với tùy chọn dòng lệnh -0và giá trị bát phân của một char, hoặc bên trong kịch bản nó có thể được thiết lập với biến đặc biệt$/
beasy

@Gilles: Nhưng sử dụng awktránh truyền luồng hai lần đến tr. Vì vậy, nó sẽ vẫn chậm hơn?
dùng285259

2
@ user285259 Điển hình là không. trlà rất nhanh và đường ống thậm chí có thể được song song.
Gilles

110

Đối với một tệp lớn như vậy, một khả năng là Flex. Hãy unk.llà:

%%
\<unk\>     printf("<raw_unk>");  
%%

Sau đó biên dịch và thực thi:

$ flex -o unk.c  unk.l
$ cc -o unk -O2 unk.c -lfl
$ unk < corpus.txt > corpus.txt.new

5
makecó các quy tắc mặc định cho điều này, thay vì flex / cc, bạn có thể thêm một %option maindòng đầu tiên của unk.l và sau đó chỉ make unk. Tôi ít nhiều sử dụng theo phản xạ %option main 8bit fastvà có export CFLAGS='-march=native -pipe -Os'trong tôi .bashrc.
jthill

1
@undercat: Nếu nó không có chủ đề, tôi có thể chỉ cho bạn một số ứng dụng ngoại vi không biên dịch, từ giải quyết vấn đề mực nước đến phân tích cú pháp đầu vào cho mục đích đặc biệt. Thật đáng kinh ngạc những gì bạn có thể làm với nó, nếu bạn nghĩ ra bên ngoài một chút :-)
jamesqf

@jthill, cảm ơn bạn: %option main+ make+ tùy chọn CFLAGSlà một mẹo rất hay !! Là -march=nativehành vi mặc định?
JJoao

1
@jamesqf như bạn đã nói - sẽ rất khó để đưa ra câu hỏi về chủ đề này - nhưng tôi cũng muốn xem nó
Steven Penny

1
@jamesqf Một prof của tôi tại uni đã sử dụng flex để xây dựng một công cụ nhận dạng các loại vải cho một nhà máy! Làm thế nào về việc hỏi một cái gì đó như: "flex có vẻ như là một công cụ rất mạnh nhưng tôi không thể viết bất kỳ trình biên dịch / trình phân tích cú pháp nào - có trường hợp sử dụng nào khác cho flex không?"
Paul Evans

40

Vì vậy, bạn không có đủ bộ nhớ vật lý (RAM) để giữ toàn bộ tệp cùng một lúc, nhưng trên hệ thống 64 bit, bạn có đủ không gian địa chỉ ảo để ánh xạ toàn bộ tệp. Ánh xạ ảo có thể hữu ích như một hack đơn giản trong trường hợp như thế này.

Các hoạt động cần thiết đều được bao gồm trong Python. Có một số sự tinh tế khó chịu, nhưng nó không tránh phải viết mã C. Cụ thể, cần cẩn thận để tránh sao chép tệp trong bộ nhớ, điều này sẽ đánh bại điểm hoàn toàn. Về mặt tích cực, bạn nhận được báo cáo lỗi miễn phí ("ngoại lệ" python) :).

#!/usr/bin/python3
# This script takes input from stdin
# (but it must be a regular file, to support mapping it),
# and writes the result to stdout.

search = b'<unk>'
replace = b'<raw_unk>'


import sys
import os
import mmap

# sys.stdout requires str, but we want to write bytes
out_bytes = sys.stdout.buffer

mem = mmap.mmap(sys.stdin.fileno(), 0, access=mmap.ACCESS_READ)
i = mem.find(search)
if i < 0:
    sys.exit("Search string not found")

# mmap object subscripts to bytes (making a copy)
# memoryview object subscripts to a memoryview object
# (it implements the buffer protocol).
view = memoryview(mem)

out_bytes.write(view[:i])
out_bytes.write(replace)
out_bytes.write(view[i+len(search):])

Nếu hệ thống của tôi có khoảng 4 gb bộ nhớ do không có bộ nhớ trong 8 gb, thì mem = mmap.mmap (sys.stdin.fileno (), 0, access = mmap.ACCESS_READ) có nghĩa là nó đặt dữ liệu vào không gian đó? Hoặc nó sẽ thấp hơn nhiều (1gb?)>
Rahul

1
@Rahul "Vì vậy, bạn không có đủ RAM, nhưng trên hệ thống 64 bit, bạn có đủ không gian địa chỉ ảo để ánh xạ toàn bộ tệp." Đó là phân trang trong và ngoài ram vật lý theo yêu cầu (hoặc thiếu nó). Chương trình này sẽ hoạt động mà không cần bất kỳ lượng RAM vật lý lớn nào. Hệ thống 64 bit có không gian địa chỉ ảo nhiều hơn so với ram vật lý tối đa. Ngoài ra mỗi quá trình chạy có không gian địa chỉ ảo của riêng nó. Điều này có nghĩa là toàn bộ hệ thống hết không gian địa chỉ ảo không phải là một điều, nó không phải là một khái niệm hợp lệ.
sourcejedi

4
@Rahul vâng! python mmap.mmap () là một trình bao bọc khá mỏng xung quanh hàm C mmap (). Và mmap () là cùng một cơ chế được sử dụng để chạy các tệp thực thi và mã từ các thư viện dùng chung.
sourcejedi

2
@jamesqf Tôi có thể sai, nhưng tôi cảm thấy đó chỉ là một lựa chọn cá nhân. Vì tổn thất hiệu năng sẽ không đáng kể (vì như ông nói, hàm thực tế gọi hàm c), lãng phí trên không rất thấp, vì không có thứ gì khác xảy ra ở giữa. C sẽ tốt hơn, nhưng giải pháp này không nhằm mục đích tối ưu hóa, chỉ để giải quyết vấn đề 70gb lớn hơn và khó khăn hơn.
La Mã

1
Nói chung, viết bằng python nhỏ gọn hơn. Trong trường hợp này, hóa ra có một vài chi tiết trong phiên bản python và phiên bản C có thể đã được viết đẹp hơn. (Mặc dù nó không đơn giản nếu searchcó thể chứa một ký tự NUL. Và tôi nhận thấy phiên bản C khác ở đây không hỗ trợ các ký tự NUL replace.). Bạn rất hoan nghênh lấy phiên bản C cho mục đích so sánh. Tuy nhiên, hãy nhớ rằng phiên bản của tôi bao gồm báo cáo lỗi cơ bản cho các hoạt động mà nó thực hiện. Phiên bản C ít nhất sẽ gây khó chịu hơn khi đọc IMO, khi bao gồm báo cáo lỗi.
sourcejedi

16

Có một replacetiện ích trong gói mariadb-server / mysql-server. Nó thay thế các chuỗi đơn giản (không phải biểu thức chính quy) và không giống như grep / sed / awk replacekhông quan tâm \n\0. Tiêu thụ bộ nhớ là không đổi với bất kỳ tệp đầu vào nào (khoảng 400kb trên máy của tôi).

Tất nhiên bạn không cần phải chạy máy chủ mysql để sử dụng replace, nó chỉ được đóng gói theo cách đó trong Fedora. Các distro / hệ điều hành khác có thể có nó được đóng gói riêng.


16

Tôi nghĩ rằng phiên bản C có thể hoạt động tốt hơn nhiều:

#include <stdio.h>
#include <string.h>

#define PAT_LEN 5

int main()
{
    /* note this is not a general solution. In particular the pattern
     * must not have a repeated sequence at the start, so <unk> is fine
     * but aardvark is not, because it starts with "a" repeated, and ababc
     * is not because it starts with "ab" repeated. */
    char pattern[] = "<unk>";          /* set PAT_LEN to length of this */
    char replacement[] = "<raw_unk>"; 
    int c;
    int i, j;

    for (i = 0; (c = getchar()) != EOF;) {
        if (c == pattern[i]) {
            i++;
            if (i == PAT_LEN) {
                printf("%s", replacement);
                i = 0;
            }
        } else {
            if (i > 0) {
                for (j = 0; j < i; j++) {
                    putchar(pattern[j]);
                }
                i = 0;
            }
            if (c == pattern[0]) {
                i = 1;
            } else {
                putchar(c);
            }
        }
    }
    /* TODO: fix up end of file if it ends with a part of pattern */
    return 0;
}

EDIT: Sửa đổi theo ý kiến ​​từ các ý kiến. Cũng sửa lỗi với mẫu <<unk>.


2
bạn có thể in (mẫu [j]) thay vì (buf [j]) (chúng bằng nhau tại thời điểm này, vì vậy bạn không cần bộ đệm
RịaD 30/12/17

3
Ngoài ra mã sẽ không hoạt động cho chuỗi "<< unk>" ideone.com/ncM2yy
RiaD

10
30 MB trong 0,3 giây? Đó chỉ là 90 MB / giây. memcpytốc độ (tức là tắc nghẽn bộ nhớ) là khoảng 12GB / giây trên CPU x86 gần đây (ví dụ Skylake). Ngay cả với chi phí cuộc gọi hệ thống stdio +, đối với tệp 30 MB nóng trong bộ đệm đĩa, tôi mong đợi có thể 1GB / giây để triển khai hiệu quả. Bạn đã biên dịch với tối ưu hóa bị vô hiệu hóa, hoặc I / O một lần thực sự chậm? getchar_unlocked/ putchar_unlockedCó thể giúp đỡ, nhưng chắc chắn tốt hơn để đọc / ghi trong khối có thể 128kiB (một nửa kích thước bộ nhớ cache L2 trên hầu hết các CPU x86, vì vậy bạn chủ yếu đánh vào L2 trong khi Looping sau khi đọc)
Peter Cordes

2
từ đỉnh đầu của tôi, getchar và putchar chậm.
Rui F Ribeiro

3
Các fixchương trình cho "<<unk>"vẫn không hoạt động nếu patternbắt đầu với một chuỗi lặp đi lặp lại của các nhân vật (tức là nó sẽ không hoạt động nếu bạn đã cố gắng để thay thế lợn đất với ngựa vằn và bạn có đầu vào của aaardvak, hoặc bạn đã cố gắng để thay thế ababc và có đầu vào của abababc). Nói chung, bạn không thể tiến lên theo số lượng ký tự bạn đã đọc trừ khi bạn biết rằng không có khả năng trận đấu bắt đầu trong các ký tự bạn đã đọc.
icarus

14

GNU grepcó thể hiển thị cho bạn phần bù của các kết quả khớp trong các tệp "nhị phân" mà không cần phải đọc toàn bộ dòng vào bộ nhớ. Sau đó, bạn có thể sử dụng ddđể đọc tối đa phần bù này, bỏ qua phần khớp, sau đó tiếp tục sao chép từ tệp.

file=...
newfile=...
replace='<raw_unk>'
grep -o -b -a -F '<unk>' <"$file" |
(   pos=0
    while IFS=$IFS: read offset pattern
    do size=${#pattern}
       let skip=offset-pos
       let big=skip/1048576
       let skip=skip-big*1048576
       dd bs=1048576 count=$big <&3
       dd bs=1 count=$skip <&3
       dd bs=1 count=$size of=/dev/null <&3
       printf "%s" "$replace"
       let pos=offset+size
    done
    cat <&3
) 3<"$file" >"$newfile"

Đối với tốc độ, tôi đã chia ddthành một lần đọc lớn kích thước khối 1048576 và đọc 1 byte nhỏ hơn tại một thời điểm, nhưng thao tác này vẫn sẽ chậm một chút trên một tệp lớn như vậy. Đầu grepra là, ví dụ, 13977:<unk>và điều này được phân chia trên dấu hai chấm bằng cách đọc thành các biến offsetpattern. Chúng ta phải theo dõi posxem có bao nhiêu byte đã được sao chép từ tệp.


11

Đây là một dòng lệnh UNIX đơn khác có thể hoạt động tốt hơn các tùy chọn khác, bởi vì bạn có thể "săn" "kích thước khối" hoạt động tốt. Để điều này trở nên mạnh mẽ, bạn cần biết rằng bạn có ít nhất một khoảng trắng trong mỗi ký tự X, trong đó X là "kích thước khối" tùy ý của bạn. Trong ví dụ dưới đây, tôi đã chọn "kích thước khối" gồm 1024 ký tự.

fold -w 1024 -s corpus.txt | sed 's/<unk>/<raw_unk>/g' | tr '/n' '/0'

Ở đây, Fold sẽ lấy tối đa 1024 byte, nhưng -s đảm bảo rằng nó sẽ phá vỡ trên một khoảng trắng nếu có ít nhất một kể từ lần ngắt cuối cùng.

Lệnh sed là của bạn và làm những gì bạn mong đợi.

Sau đó, lệnh tr sẽ "mở ra" tệp chuyển đổi các dòng mới được chèn trở lại thành không có gì.

Bạn nên xem xét thử kích thước khối lớn hơn để xem nó có hoạt động nhanh hơn không. Thay vì 1024, bạn có thể thử 10240 và 102400 và 1048576 cho tùy chọn -w của nếp gấp.

Dưới đây là một ví dụ được chia nhỏ theo từng bước chuyển đổi tất cả chữ N thành chữ thường:

[root@alpha ~]# cat mailtest.txt
test XJS C4JD QADN1 NSBN3 2IDNEN GTUBE STANDARD ANTI UBE-TEST EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt
test XJS C4JD QADN1
NSBN3 2IDNEN GTUBE
STANDARD ANTI
UBE-TEST
EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g'
test XJS C4JD QADn1
nSBn3 2IDnEn GTUBE
STAnDARD AnTI
UBE-TEST
EMAIL*C.34X test

[root@alpha ~]# fold -w 20 -s mailtest.txt | sed 's/N/n/g' | tr '\n' '\0'
test XJS C4JD QADn1 nSBn3 2IDnEn GTUBE STAnDARD AnTI UBE-TEST EMAIL*C.34X test

Bạn sẽ cần thêm một dòng mới vào cuối tập tin nếu có, bởi vì lệnh tr sẽ loại bỏ nó.


1
Làm thế nào để bạn chắc chắn rằng bạn không phá vỡ mô hình trong các trường hợp cạnh khi không có đủ khoảng trắng?
rackandboneman

1
Như đã nêu, để điều này trở nên mạnh mẽ, có một yêu cầu là có ít nhất một khoảng trắng cho mỗi ký tự X. Bạn có thể thực hiện phân tích đó đủ dễ dàng, với bất kỳ kích thước khối nào bạn chọn: Fold -w X mailtest.txt | grep -v "" | wc -l Số nó trả về là số đường gấp với các trường hợp cạnh tiềm năng. Nếu nó bằng không, giải pháp được đảm bảo để hoạt động.
alfreema

10

Sử dụng perl

Quản lý bộ đệm của riêng bạn

Bạn có thể sử dụng IO::Handle's setvbufđể quản lý bộ đệm mặc định, hoặc bạn có thể quản lý bộ đệm của riêng bạn với sysreadsyswrite. Kiểm tra perldoc -f sysreadperldoc -f syswriteđể biết thêm thông tin, về cơ bản họ bỏ qua bộ đệm io.

Ở đây chúng tôi cuộn bộ đệm IO của riêng mình, nhưng chúng tôi thực hiện thủ công và tùy ý trên 1024 byte. Chúng tôi cũng mở tệp cho RW để chúng tôi thực hiện tất cả trên cùng một FH.

use strict;
use warnings;
use Fcntl qw(:flock O_RDWR);
use autodie;
use bytes;

use constant CHUNK_SIZE => 1024 * 32;

sysopen my $fh, 'file', O_RDWR;
flock($fh, LOCK_EX);

my $chunk = 1;
while ( sysread $fh, my $bytes, CHUNK_SIZE * $chunk ) {
  if ( $bytes =~ s/<unk>/<raw_unk>/g ) {
    seek( $fh, ($chunk-1)* CHUNK_SIZE, 0 );
    syswrite( $fh, $bytes, 1024);
    seek( $fh, $chunk * CHUNK_SIZE, 0 );
  }
  $chunk++;
}

Nếu bạn đang đi theo con đường này

  1. Hãy chắc chắn <unk><raw_unk>có cùng kích thước byte.
  2. Bạn có thể muốn đảm bảo phương thức đệm của chúng tôi không vượt qua CHUNKSIZEranh giới, nếu bạn thay thế hơn 1 byte.

2
Điều gì nếu <unk>rơi vào một ranh giới giữa các khối?
liori

8

Bạn có thể thử bbe ( trình chỉnh sửa khối nhị phân ), " sedcho tệp nhị phân".

Tôi đã thành công khi sử dụng nó trên tệp văn bản 7 GB không có EOLký tự, thay thế nhiều lần xuất hiện của một chuỗi bằng một độ dài khác nhau. Không cần cố gắng tối ưu hóa, nó đã cho thông lượng xử lý trung bình> 50MB / s.


5

Với perl, bạn có thể làm việc với các bản ghi độ dài cố định như:

perl -pe 'BEGIN{$/=\1e8}
          s/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new

Và hy vọng rằng sẽ không có <unk>khoảng hai trong số các hồ sơ 100 MB đó.


Tôi cũng đã suy nghĩ về phương pháp này, nhưng sử dụng while read -N 1000 chunk;( 1000ví dụ được chọn làm ví dụ). Giải pháp cho <unk>, bị phá vỡ giữa các khối, là hai lần đi qua tệp: lần đầu tiên với các đoạn 100 MB và lần thứ hai với các đoạn '100MB + 5 byte'. Nhưng nó không phải là giải pháp tối ưu trong trường hợp tệp 70GB.
MiniMax

3
Bạn thậm chí không cần hai đường chuyền. Đọc khối A. Trong khi không phải EOF, hãy đọc khối B. Tìm kiếm / Thay thế trong A + B. A: = B. Vòng lặp. Sự phức tạp đang đảm bảo bạn không thay thế bên trong thay thế.
roaima

@MiniMax, lần chuyển thứ hai đó không nhất thiết phải giúp vì lần đầu tiên sẽ có thêm 5 byte cho mỗi lần xuất hiện <unk>.
Stéphane Chazelas

1
@roaima, vâng, đó sẽ là một giải pháp liên quan nhiều hơn. Đây là một cách tiếp cận đơn giản chỉ có xác suất cao (giả sử các <unk>sự cố xảy ra rất xa, nếu không, sử dụng $/ = ">"s/<unk>\z/<raw_unk>/g) là chính xác.
Stéphane Chazelas

5

Đây là một chương trình Go nhỏ thực hiện nhiệm vụ ( unk.go):

package main

import (
    "bufio"
    "fmt"
    "log"
    "os"
)

func main() {
    const (
        pattern     = "<unk>"
        replacement = "<raw_unk>"
    )
    var match int
    var char rune
    scanner := bufio.NewScanner(os.Stdin)
    scanner.Split(bufio.ScanRunes)
    for scanner.Scan() {
        char = rune(scanner.Text()[0])
        if char == []rune(pattern)[match] {
            match++
            if match == len(pattern) {
                fmt.Print(replacement)
                match = 0
            }
        } else {
            if match > 0 {
                fmt.Print(string(pattern[:match]))
                match = 0
            }
            if char == rune(pattern[0]) {
                match = 1
            } else {
                fmt.Print(string(char))
            }
        }
    }
    if err := scanner.Err(); err != nil {
        log.Fatal(err)
    }
}

Chỉ cần xây dựng nó go build unk.govà chạy nó như ./unk <input >output.

BIÊN TẬP:

Xin lỗi, tôi đã không đọc rằng mọi thứ đều nằm trong một dòng, vì vậy tôi đã cố gắng đọc từng ký tự tệp.

EDIT II:

Áp dụng sửa chữa tương tự như chương trình C.


1
Điều này có tránh đọc toàn bộ tập tin vào bộ nhớ không?
con mèo

1
Nó đọc ký tự tệp theo ký tự và không bao giờ giữ toàn bộ tệp trong bộ nhớ, chỉ các ký tự riêng lẻ.
Patrick Bucher

1
scanner.Split(bufio.ScanRunes)làm phép thuật.
Patrick Bucher

Cũng kiểm tra go doc bufio.MaxScanTokenSizekích thước bộ đệm mặc định.
Patrick Bucher

Giống như Cchương trình của bạn , điều này không hoạt động để thay thế aardvark bằng ngựa vằn bằng đầu vào của aaardvark.
icarus

1

Đây có thể là quá mức cần thiết cho một tệp 70 GB và tìm kiếm & thay thế đơn giản, nhưng khung MapReduce của Hadoop sẽ giải quyết vấn đề của bạn ngay bây giờ miễn phí (chọn tùy chọn 'Single Node' khi thiết lập để chạy nó cục bộ) - và sẽ có thể mở rộng đến khả năng vô hạn trong tương lai mà không cần phải sửa đổi mã của bạn.

Hướng dẫn chính thức tại https://hadoop.apache.org/docs/urdy/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html sử dụng Java (cực kỳ đơn giản) nhưng bạn có thể tìm thấy các thư viện khách cho Perl hoặc bất cứ ngôn ngữ nào bạn cảm thấy thích sử dụng.

Vì vậy, nếu sau này bạn thấy rằng bạn đang thực hiện các thao tác phức tạp hơn trên các tệp văn bản 7000GB - và phải thực hiện việc này 100 lần mỗi ngày - bạn có thể phân phối khối lượng công việc qua nhiều nút mà bạn cung cấp hoặc được cung cấp tự động cho bạn bởi một đám mây- dựa trên cụm Hadoop.


1
Vâng vâng nó là. "Đừng sử dụng Hadoop - dữ liệu của bạn không lớn như vậy" . Đây là một vấn đề IO phát trực tuyến rất đơn giản.
nguồn

0

Tất cả các đề xuất trước đó yêu cầu đọc toàn bộ tệp và viết toàn bộ tệp. Điều này không chỉ mất nhiều thời gian mà còn cần 70GB dung lượng trống.

1) Nếu tôi hiểu bạn một cách chính xác trường hợp cụ thể nó sẽ được chấp nhận để thay thế <không rõ> với một số chuỗi khác cùng độ dài?

2a) Có nhiều lần xuất hiện không? 2b) Nếu vậy bạn có biết bao nhiêu?

Tôi chắc rằng bạn đã giải quyết vấn đề cộng thêm trong năm nay và tôi muốn biết bạn đã sử dụng giải pháp nào.

Tôi muốn đề xuất một giải pháp (rất có thể trong C) sẽ đọc BLOCKS của tệp đang tìm kiếm từng chuỗi có tính đến việc vượt qua khối có thể. Khi tìm thấy thay thế chuỗi bằng chiều dài SAME thay thế và chỉ ghi BLOCK đó. Tiếp tục cho số lần xuất hiện đã biết hoặc cho đến khi kết thúc tập tin. Điều này sẽ yêu cầu ít nhất là số lần xuất hiện và nhiều nhất là hai lần (nếu mỗi lần xuất hiện được phân chia giữa 2 khối). Điều này sẽ không cần thêm không gian!


-1

Nếu chúng tôi có số tiền tối thiểu <unk>(theo dự kiến ​​của luật Zipf),

awk -v RS="<unk>" -v ORS="<raw_unk>" 1

1
Số sedđọc một dòng tại một thời điểm vào bộ nhớ bất kể. Nó sẽ không thể phù hợp với dòng này.
Kusalananda

1
Tôi không thể tìm thấy tài liệu nào nói bất cứ điều gì khác ngoài GNU sedsẽ không thực hiện đệm đầu vào / đầu ra khi sử dụng cờ này. Tôi không thể thấy rằng nó sẽ đọc một phần dòng.
Kusalananda
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.