Видаліть повторювані рядки, зберігаючи порядок рядків


14
[root@server]# awk '!seen[$0]++' out.txt > cleaned
awk: (FILENAME=out.txt FNR=8547098) fatal error: internal error
Aborted
[root@server]#

"" Сервер "" має: 8 Гбайт оперативної пам'яті + 16 Гбайт SWAP, x> 300 ГБ вільного місця, amd64, настільний процесор. Науковий Linux 6.6. Більше нічого не працює на ньому, щоб зробити ЗАВАНТАЖЕННЯ. Awk перериває через кілька секунд .. out.txt становить ~ 1,6 Гб. GNU Awk 3.1.7.

Питання : Як я можу видалити повторювані рядки, зберігаючи порядок рядків? Справа теж важлива, наприклад: "A" і "a" - це два різних рядки, їх потрібно зберегти. Але "a" і "a" - це дублікат, потрібен лише перший.

Відповідь може бути у чому завгодно, якщо awk не для цього хороший .. то perl / sed .. у чому може бути проблема?

[root@server]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 61945
max locked memory       (kbytes, -l) 99999999
max memory size         (kbytes, -m) unlimited
open files                      (-n) 999999
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 99999999
cpu time               (seconds, -t) unlimited
max user processes              (-u) 61945
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
[root@server]# 

Оновлення: Я спробував це на машині RHEL, він не перериває, але я не встиг дочекатися його завершення. Чому SL linux відрізняється від RHEL?

Оновлення: я пробую віртуальні догадки Ubuntu 14 .. поки що це працює! Це не безмежна проблема: mawk 1.3.3

root@asdf-VirtualBox:~# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 51331
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 51331
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited
root@asdf-VirtualBox:~# 

2
У вашому прикладі немає дублікатів рядків ...?
mikeserv

1
Що таке awkверсії у двох машинах?
cuonglm

оновлений rhel та оновлений sl linux, не знаю версії rhel .. sl: GNU Awk 3.1.7
somelooser28533

Наскільки великий out.txt? Чи працює та сама команда, якщо ви спробуєте її на меншому файлі? Скільки користувачів на машині? Чи було достатньо доступної пам’яті для процесу? Чи є щось особливе у рядку 8547098 вхідного файлу?
тердон

Відповіді:


22

Я сумніваюся, що це змінить, але, на всяк випадок, ось як зробити те саме в Perl:

perl -ne 'print if ++$k{$_}==1' out.txt

Якщо проблема зберігається в пам’яті унікальних ліній, це матиме таку ж проблему, що і awkви намагалися. Отже, іншим підходом може бути:

cat -n out.txt | sort -k2 -k1n  | uniq -f1 | sort -nk1,1 | cut -f2-

Як це працює:

  1. У системі GNU cat -nдодасть номер рядка до кожного рядка, дотримуючись деяку кількість пробілів, а за ним символ <tab> . catпередає це вхідне подання на sort.

  2. sort«S -k2опція указует , що тільки не брати до уваги символи з другого поля до кінця рядка при сортуванні і sortрозколи полів за замовчуванням на білому просторі (або cat" вставляють пробілу і <Tab> ) .
    Коли слід -k1n, sortвважає 2 - е поле, а потім , по- друге, в разі однакових -k2полів, він вважає , що поле 1 - й , але , як упорядковано чисельно. Тож повторювані рядки будуть відсортовані разом, але в тому порядку, в якому вони з'явилися.

  3. Результати переносяться на те uniq, до якого повідомляється ігнорувати перше поле ( -f1- а також як розділене пробілом) - і це призводить до списку унікальних рядків у вихідному файлі і повертається назад sort.
  4. На цей раз sortсортується в першому полі ( catвставлений номер рядка) чисельно, повертаючи порядок сортування до того, що було в оригінальному файлі, і передає ці результати cut.
  5. Нарешті, cutвидаляє номери рядків, які були вставлені cat. Це відбувається шляхом cutдруку лише з 2-го поля через кінець рядка cutроздільником за замовчуванням є <tab> символ) .

Проілюструвати:

$ cat file
bb
aa
bb
dd
cc
dd
aa
bb
cc
$ cat -n file | sort -k2 | uniq -f1 | sort -k1 | cut -f2-
bb
aa    
dd
cc

Привіт Тердоне, ОП потрібно підтримувати порядок ліній, тому метод | cat | сортування | uniq метод не буде працювати ... Як і ваша версія Perl, хоча ...
Lambert

1
Гарне рішення з sort! Але більшість sortможе зробити uniqсамостійно, тому ви можете скоротити сценарій до sort -uk2 | sort -bk1,1n
Costas

@Costas це найбільше sort? Я думав, -uце особливість GNU.
тердон

@don_crissti ах, так це, дякую. Як я міг би тут використати його? Як я щойно помітив (і редагував, щоб виправити), мені потрібно спершу сортувати по 2-му полі, а потім по 1-му числовому, щоб дотримуватися порядку рядків. Як я можу потім використовувати -uта вказати, що воно повинно ігнорувати 1-е поле? За словами man sort, -uце не один із можливих варіантів -f, тому я не думаю, що його тут можна використовувати.
тердон

1
це трансформація Шварца ! (+1)
JJoao

7
#!/usr/bin/perl 
use DB_File;
tie %h, 'DB_File';

while(<>){ not $h{$_} and print and $h{$_}=1 }

РЕДАКЦІЯ 1: Це дійсно працює? (порівняння)

Sol1 : Terdon et all Schwartzian-transform-like one-liner
    cat -n _1 | sort -uk2 | sort -nk1 | cut -f2-

Sol2 : perl  + DB_File (this answer)
    perl dbfile-uniq _1

Sol3 : PO (John W. Gill solution has a similar behavior)
    awk '!seen[$0]++' _1

Sol4: Terdon perl
    perl -ne 'print if ++$k{$_}==1' _1

Case1 : 100_000_000 випадкових чисел (по 5 цифр у кожному), 566 Мбайт, 31_212 різних значень:

$ while true ; do echo $RANDOM; done | head -100000000 > _1

Випадок 2 : номери рангів 50_000_000 (10 цифр у кожному), 516 Мбайт, 48_351_464 різних значень:

$ shuf _1 |  sed 'N;s/\n/ /' > _11

(наступні цифри не дуже точні):

┌────────┬────────┬────────────────┬────────┬──────┐
         Sol1    Sol2            Sol3    Sol4 
         sort...│ perl DB         awk     perl 
├────────┼────────┼────────────────┼────────┼──────┤
 case 1  6m15    6m17            0m28    0m28 
├────────┼────────┼────────────────┼────────┴──────┤
 case 2  11m15   81m44           out of memory 
├────────┼────────┼────────────────┼────────┬──────┤
 case 2          5m54 /cache=2G               
└────────┴────────┴────────────────┴────────┴──────┘

sol2 з кешем є:

use DB_File;
use Fcntl ;

$DB_HASH->{'cachesize'} = 2000_000_000;
tie %h, 'DB_File', "_my.db", O_RDWR|O_CREAT|O_TRUNC, 0640, $DB_HASH;

while(<>){ not $h{$_} and print and $h{$_}=1 }

Сортування також можна оптимізувати, додаючи параметр кешування (не зроблено).

Один швидкий висновок:

  • sort це фантастична команда!

1
sort -uk2і sort -nk1,1різні. Перший розглядає від 2cd ключа до кінця рядка, другий розглядає лише перший ключ. Ви повинні змінити своє sort -nk1там - це може бути навіть швидше таким чином, але воно, безумовно, буде більш надійним. До речі - це кілька симпатичних коробок.
mikeserv

@mikeserv, дякую за коментар. Оскільки K1,1 унікальний, сортування -nk1 та сортування -nk1,1 повертають деякий результат. Я пробував і те, і інше, результат був однаковий і час не відрізнявся.
JJoao

Це має сенс - все ж спасибі за спробу. Так cat -nробить вкладка ? Я не знаю, як працює ця команда.
mikeserv

1
@mikeserv, щасливо трансформуйте cat -nкожен lineу spaces + the number + \t + line- ідеальний формат для сортування та вирізання
JJoao

1

Я звик

awk -v BINMODE=rw '!($0 in a){a[$0];print}' infile >> outfile

BINMODE = rw: щоб задовольнити кінець термінальних рядків. (Я живу в змішаному середовищі ОС)

Логіка проста.

Якщо поточний рядок відсутній в асоціативному масиві, додайте його до асоціативного масиву та надрукуйте для виведення.

При такому підході можуть бути обмеження пам'яті. Для дуже великих файлів і наборів файлів я використовував варіанти, використовуючи сховище файлів, щоб подолати обмеження.


0

Семантика вашої проблеми, що зберігає порядок, має дивовижне властивість: ви можете поділити її. Ви можете зробити split -l 1000000на вхідному файлі; 1000000-лінійних творів, які він створює, мають лексично впорядковані назви, що добре; потім уніфікуйте шматки; а потім (як другий прохід) уніфікуйте результати цих даних.

Це вирішує проблему поза пам'яттю (обмежуючи потребу в пам'яті) за рахунок перетворення її на багатопропускне рішення.

Конкретно:

Створення вхідних даних:

$ cat make-uniqm-input.py
#!/usr/bin/env python
import random
n = 1000000
for i in xrange(0, n):
    print random.randint(1000, 2000)

$ python make-uniqm-input.py  > uniqm-input.txt

$ wc -l uniqm-input.txt
 1000000 uniqm-input.txt

Розбийте вхідні дані:

$ split -l 10000 uniqm-input.txt

$ ls x?? | head
xaa
xab
xac
xad
xae
xaf
xag
xah
xai
xaj

$ ls x?? | wc -l
     100

$ cat x?? | wc -l
 1000000

Запустіть uniqifier все відразу (зберігає всі унікальні лінії вводу в пам'яті):

# 'uniqm' is any order-preserving uniq implementation, such as
# gawk '!counts[$0]++'.
$ uniqm < uniqm-input.txt > output-no-splitting.txt

$ wc -l output-no-splitting.txt
    1001 output-no-splitting.txt

Запустіть уніфікатор на розділених фрагментах (зберігає лише унікальні вхідні рядки з кожного фрагмента в пам'яті), а потім зменшіть як другий прохід:

$ for x in x??; do uniqm < $x; done | uniqm > output-with-splitting.txt

$ wc -l output-with-splitting.txt
    1001 output-with-splitting.txt

Порівняйте:

$ diff output-no-splitting.txt output-with-splitting.txt

$ head uniqm-input.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

$ head output-with-splitting.txt
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

Я не знаю співвідношення унікальних до неповторних рядків у вашому введенні, а також наскільки добре змішані рядки введення - тому є певна настройка, що стосується кількості потрібних розділених файлів.


0

Інший підхід (варто опублікувати як окрему відповідь): замість підходу з розділеним файлом, який створює тимчасові файли, виконайте групування в рамках самого програмного забезпечення Uniqifier. Наприклад, використання реабілітаційної програми Ruby для пояснювальних цілей:

require 'set'
line_batch_count = 50000 # tunable parameter
lines_seen = Set.new
line_number = 0
ARGF.each do |line|
   line_number += 1
   if (line_number % line_batch_count) == 0
     lines_seen.clear
   end
   unless lines_seen.include? line
      puts line
      lines_seen << line
   end
end

Ідея полягає в тому, щоб очистити хеш-набори кожен так часто. Тоді це стає ітераційним:

$ cat uniqm-input.txt | ruby uniqm-capped.rb | wc -l
   20021

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | wc -l
    1001

$ cat uniqm-input.txt | ruby uniqm-capped.rb | ruby uniqm-capped.rb | head
1506
1054
1623
1002
1173
1400
1226
1340
1824
1091

Таким чином, ви можете запускати цю обмежену версію кілька разів, поки кількість рядків не зміниться від однієї ітерації до іншої.

Зауважте, що ця методика з обмеженою обробкою-uniqm не залежить від мови: ви можете очистити lines_seenмасив кожні N рядків, використовуючи awk, python, perl, C ++ тощо. Для всіх цих мов існують чіткі методи; Я вважаю awk, deleteце нестандартне, але поширене.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.