Doppelte Zeilen paarweise löschen?

16

Ich bin heute auf diesen Anwendungsfall gestoßen. Es scheint einfach auf den ersten Blick, aber das Hantieren mit sort, uniq, sedund awkgezeigt , dass es nicht trivial ist.

Wie kann ich alle doppelten Zeilenpaare löschen ? Mit anderen Worten, wenn es eine gerade Anzahl von Duplikaten einer bestimmten Zeile gibt, löschen Sie alle. Wenn es eine ungerade Anzahl doppelter Zeilen gibt, löschen Sie alle bis auf eine. (Eine sortierte Eingabe kann angenommen werden.)

Eine saubere, elegante Lösung ist vorzuziehen.

Beispiel Eingabe:

a
a
a
b
b
c
c
c
c
d
d
d
d
d
e

Beispielausgabe:

a
d
e
Platzhalter
quelle

Antworten:

6

Ich habe die sedAntwort nicht lange nach dem Posten dieser Frage gefunden. sedBisher hat noch niemand davon Gebrauch gemacht. Hier ist es:

sed '$!N;/^\(.*\)\n\1$/d;P;D'

Ein bisschen Herumspielen mit dem allgemeineren Problem (wie wäre es mit dem Löschen von Zeilen in Dreier- oder Vier- oder Fünfersätzen?) Ergab die folgende erweiterbare Lösung:

sed -e ':top' -e '$!{/\n/!{N;b top' -e '};};/^\(.*\)\n\1$/d;P;D' temp

Erweitert, um Dreifache von Zeilen zu entfernen:

sed -e ':top' -e '$!{/\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1$/d;P;D' temp

Oder um Quads von Zeilen zu entfernen:

sed -e ':top' -e '$!{/\n.*\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1\n\1$/d;P;D' temp

sed hat einen zusätzlichen Vorteil gegenüber den meisten anderen Optionen, nämlich die Fähigkeit, wirklich in einem Stream zu arbeiten, wobei nicht mehr Speicher benötigt wird als die tatsächliche Anzahl der auf Duplikate zu überprüfenden Zeilen.


Wie in den Kommentaren erwähnt , ist das Setzen des Gebietsschemas auf C erforderlich, um zu vermeiden, dass Zeilen, die Mehrbytezeichen enthalten, nicht ordnungsgemäß entfernt werden. So werden die obigen Befehle:

LC_ALL=C sed '$!N;/^\(.*\)\n\1$/d;P;D' temp
LC_ALL=C sed -e ':top' -e '$!{/\n/!{N;b top' -e '};};/^\(.*\)\n\1$/d;P;D' temp
LC_ALL=C sed -e ':top' -e '$!{/\n.*\n/!{N;b top' -e '};};/^\(.*\)\n\1\n\1$/d;P;D' temp
# Etc.
Platzhalter
quelle
2
@Wildcard: Möglicherweise möchten Sie das Gebietsschema auf ein Cungültiges Zeichen in diesem Gebietsschema festlegen , andernfalls schlägt der Befehl fehl.
Cuonglm
4

Es ist nicht sehr elegant, aber so einfach, wie ich es mir vorstellen kann:

uniq -c input | awk '{if ($1 % 2 == 1) { print substr($0, 9) }}'

Das substr () schneidet nur die uniqAusgabe ab. Das funktioniert, bis Sie mehr als 9.999.999 Duplikate einer Zeile haben (in diesem Fall kann die Ausgabe von uniq über 9 Zeichen hinausgehen).

Jeff Schaller
quelle
Ich habe es versucht uniq -c input | awk '{if ($1 %2 == 1) { print $2 } }'und es schien genauso gut zu funktionieren. Aus irgendeinem Grund ist die substrVersion besser?
Joseph R.
1
@JosephR. Wenn die Zeilen Leerzeichen enthalten, schlägt die Version in Ihrem Kommentar fehl.
Wildcard
Das ist wahr. In diesem Fall wäre keine Schleife , um die Felder zu drucken , $2um $NFrobuster zu sein?
Joseph R.
@ JosephR .: Warum glaubst du, dass deine Alternative robuster wäre? Wenn mehrere aufeinanderfolgende Leerzeichen vorhanden sind, kann es schwierig sein, das Problem zu beheben. zB foo   bar.
G-Man sagt, dass Monica am
@JosephR., Nein, da dies die Leerzeichenbegrenzung ändern / beseitigen würde. uniq(zumindest in GNU coreutils) scheint genau 9 Zeichen vor dem Text selbst zuverlässig zu verwenden; Ich kann dies jedoch nirgendwo dokumentieren und es ist nicht in den POSIX-Spezifikationen enthalten .
Wildcard
4

Probieren Sie dieses awkSkript aus:

#!/usr/bin/awk -f
{
  if ((NR!=1) && (previous!=$0) && (count%2==1)) {
    print previous;
    count=0;
  }
  previous=$0;
  count++;
}
END {
  if (count%2==1) {
    print previous;
  }
}

Es wird davon ausgegangen, dass die lines.txtDatei sortiert ist.

Der Test:

$ chmod +x script.awk
$ ./script.awk lines.txt
a
d
e
Jay Jargot
quelle
4

Mit pcregrepfür eine gegebene Probe:

pcregrep -Mv '(.)\n\1$' file

oder allgemeiner:

pcregrep -Mv '(^.*)\n\1$' file
jimmij
quelle
Sollte es am Ende keinen Anker für das Zeilenende geben? Andernfalls schlagen Sie in einer Zeile fehl, die mit der vorherigen Zeile übereinstimmt und keine abschließenden Zeichen enthält.
Wildcard
@Wildcard ja, das ist besser. korrigiert, thx.
Jimmy
Sehr cool! (+1)
JJoao
4

Wenn die Eingabe sortiert ist:

perl -0pe  'while(s/^(.*)\n\1\n//m){}'
Joao
quelle
Sie haben hier einen Verankerungsfehler. Versuchen Sie es auf zB pineapple\napple\ncoconutund die Ausgabe ist pinecoconut.
Wildcard
@Wildcard: Danke. Du hast recht. Sehen Sie, ob mein Update Sinn macht ...
JJoao
1
Ja. Ich habe mich gefragt, warum Sie \nanstelle des $angegebenen /mModifikators verwenden, aber dann wurde mir klar, dass bei Verwendung $eine leere Zeile anstelle von gelöschten Zeilen verbleibt. Sieht jetzt gut aus; Ich habe die falsche Version entfernt, da sie nur Rauschen hinzufügt. :)
Wildcard
@wildcard, danke für die Rauschunterdrückung ☺
JJoao
3

Das gefällt mir pythonzum Beispiel mit python2.7+

from itertools import groupby
with open('input') as f:
    for k, g in groupby(f):
            if len(list(g)) % 2:
                    print(k),
iruvar
quelle
2

Da ich die Frage, für die ich mich entschieden habe, unter Verwendung eines Hashs für jeden Datensatz für awk verstanden habe, gehe ich in diesem Fall davon aus, dass RS = \ n ist gerade Anzahl von Wiederholungen anstelle der ungeraden mit einem Parameter oder einem kleinen Dialog. Jede Zeile wird als Hash verwendet und ihre Anzahl erhöht. Am Ende der Datei wird das Array gescannt und jede gerade Anzahl der Datensätze gedruckt. Ich beziehe die Anzahl ein, um zu überprüfen, aber das Entfernen eines [x] reicht aus, um dieses Problem zu lösen.

HTH

countlines code

#!/usr/bin/nawk -f
{a[$0]++}
END{for (x in a) if (a[x]%2!=0) print x,a[x] }

Beispieldaten:

a
One Sunny Day
a
a
b
my best friend
my best friend
b
c
c
c
One Sunny Day
c
d
my best friend
my best friend
d
d
d
One Sunny Day
d
e
x
k
j
my best friend
my best friend

Probelauf:

countlines feed.txt
j 1
k 1
x 1
a 3
One Sunny Day 3
d 5
e 1
Moises Najar
quelle
Es ist ein schönes Stück awkCode, aber leider sind awkassoziative Arrays überhaupt nicht geordnet, noch sind sie ordnungserhaltend.
Wildcard
@Wildcard, ich stimme Ihnen zu, wenn Sie die Eingabereihenfolge anstelle einer Sortierreihenfolge benötigen, kann diese durch einen zusätzlichen Hash-Schlüssel implementiert werden. Dies hat den Vorteil, dass Sie die Eingabe nicht sortieren müssen, da die Sortierreihenfolge kann am Ende mit einer kleineren Ausgabe gemacht werden;)
Moises Najar
@Wildcard Wenn Sie die Bestellung aufbewahren möchten, geben Sie dies bitte in der Frage an. Dieser Ansatz war auch mein erster Gedanke und Sie erwähnen keine andere Reihenfolge als zu sagen, dass wir davon ausgehen können, dass die Datei sortiert ist. Wenn die Datei sortiert ist, können Sie die Ausgabe dieser Lösung natürlich immer weiterleiten sort.
Terdon
@terdon, natürlich hast du recht; Die Ausgabe kann einfach erneut sortiert werden. Guter Punkt. Es ist auch erwähnenswert, dass die !=0impliziert wird, wie awkZahlen in wahre / falsche Werte konvertiert werden, wodurch diese aufawk '{a[$0]++}END{for(x in a)if(a[x]%2)print x}'
Wildcard
1

Wenn die Eingabe sortiert ist, wie sieht es damit aus awk:

awk '{ x[$0]++; if (prev != $0 && x[prev] % 2 == 1) { print prev; } prev = $0; } END { if (x[prev] % 2 == 1) print prev; }' sorted
Taliezin
quelle
1

mit perl:

uniq -c file | perl -lne 'if (m(^\s*(\d+) (.*)$)) {print $2 if $1 % 2 == 1}'
xx4h
quelle
1

Shell-Konstrukte verwenden,

uniq -c file | while read a b; do if (( $a & 1 == 1 )); then echo $b; fi done
Guido
quelle
1
Das bricht mit Zeilen, die mit Leerzeichen beginnen oder enden (oder mehr, weil Sie vergessen haben, zu zitieren $b).
Gilles 'SO- hör auf böse zu sein'
1

Fun Puzzle!

In Perl:

#! /usr/bin/env perl

use strict;
use warnings;

my $prev;
while (<>) {
  $prev = $_, next unless defined $prev;  # prime the pump

  if ($prev ne $_) {
    print $prev;
    $prev = $_;                           # first half of a new pair
  }
  else {
    undef $prev;                          # discard and unprime the pump
  }
}

print $prev if defined $prev;             # possible trailing odd line

Ausführlich in Haskell:

main :: IO ()
main = interact removePairs
  where removePairs = unlines . go . lines
        go [] = []
        go [a] = [a]
        go (a:b:rest)
          | a == b = go rest
          | otherwise = a : go (b:rest)

Knapp in Haskell:

import Data.List (group)
main = interact $ unlines . map head . filter (odd . length) . group . lines
Greg Bacon
quelle
0

a version: Ich verwende "Begrenzer", um die innere Schleife zu vereinfachen (es wird davon ausgegangen, dass die erste Zeile nicht __unlikely_beginning__mit der Zeile: endet __unlikely_ending__, und füge diese spezielle Begrenzerzeile am Ende der eingegebenen Zeilen hinzu Algorithmus kann beides annehmen:)

{ cat INPUTFILE_or_just_-  ; echo "__unlikely_ending__" ; } | awk '
  BEGIN {mem="__unlikely_beginning__"; occured=0; }  

    ($0 == mem)            { occured++ ; next } 

    ( occured%2 )           { print mem ;} 
                            { mem=$0; occured=1; }
'

So :

  • Wir erinnern uns an das Muster, das wir gerade betrachten, und erhöhen es jedes Mal um eins, wenn es erneut auftritt. [und wenn es wieder vorkommt, überspringen wir die nächsten 2 Aktionen, die für den Fall sind, wenn sich das Muster ändert]
  • Wenn sich das Muster ändert:
    • Wenn dies nicht ein Vielfaches von 2 ist, drucken wir ein Vorkommen des gespeicherten Musters
    • und in jedem Fall, wenn sich das Muster geändert hat: Das neu gespeicherte Muster ist das aktuelle Muster, und wir haben es nur einmal gesehen.
Olivier Dulac
quelle