成对删除重复行?


16

我今天遇到了这个用例。乍一看似乎很简单,但是随便摆弄sortuniqsedawk透露,这是平凡的。

如何删除所有 成对的重复行?换句话说,如果给定行的重复数为偶数,则将其全部删除;如果重复行数为奇数,请删除除一行以外的所有行。(可以假设输入是排序的。)

干净优雅的解决方案是可取的。

输入示例:

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

输出示例:

a
d
e

Answers:


6

sed发布此问题后不久,我就得出了答案。sed到目前为止,没有人使用过:

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

稍作处理较一般的问题(删除三行或四行或五行中的行该怎么办?)提供了以下可扩展的解决方案:

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

扩展以删除三行:

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

或删除四边形:

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

sed 与大多数其他选项相比,它还有一个额外的优势,那就是它能够真正在流中运行,所需的存储空间不超过要检查重复项的实际行数。


正如cuonglm在评论中指出的那样,必须将语言环境设置为C,以避免无法正确删除包含多字节字符的行。因此,以上命令变为:

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.

2
@Wildcard:您可能希望将语言环境设置为C,否则在多字节语言环境中,该语言环境中的无效字符会导致命令失败。
cuonglm '16

4

它不是很优雅,但是很简单:

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

substr()只是修剪uniq输出。直到行的重复项超过9,999,999(在这种情况下,uniq的输出可能会溢出9个字符),这才起作用。


我尝试了一下uniq -c input | awk '{if ($1 %2 == 1) { print $2 } }',它似乎同样有效。有什么理由substr版本更好?
Joseph R.

1
@JosephR。,如果各行中有空格,则注释中的版本将失败。
通配符

那是真实的。在那种情况下,会不会循环打印字段$2$NF使其更健壮?
约瑟夫·R。

@JosephR .:为什么您认为您的选择会更强大?当有多个连续的空格时,您可能很难使它正常工作。例如foo   bar
G-Man说'Resstate Monica''Apr

@JosephR。,否,因为它将更改/消除空格定界。 uniq(至少在GNU coreutils中)似乎可靠地在文本本身之前使用了9个字符;我在任何地方都找不到此文档,并且不在POSIX规范中
通配符

4

请尝试以下awk脚本:

#!/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;
  }
}

假定lines.txt文件已排序。

考试:

$ chmod +x script.awk
$ ./script.awk lines.txt
a
d
e

4

对于pcregrep给定的样本:

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

或更一般地:

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

末端不应该有“线尾”锚吗?否则,除了尾随字符外,您将无法在与该行匹配的行上失败。
通配符

@Wildcard是的,那更好。更正,谢谢。
jimmij

很酷!(+1)
JJoao '16

4

如果输入已排序:

perl -0pe  'while(s/^(.*)\n\1\n//m){}'

您的锚定失败。尝试在eg上运行它,pineapple\napple\ncoconut输出为pinecoconut
通配符

@Wildcard:谢谢。你是对的。看看我的更新是否有意义...
JJoao

1
是的 我想知道为什么要使用\n而不是$给定/m修饰符,但是后来我意识到使用$会在删除行的位置留下空白行。现在看起来不错;我删除了不正确的版本,因为它只会增加噪音。:)
通配符

@wildcard,谢谢您的降噪效果
☺– JJoao

3

我喜欢python这个,例如python2.7+

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

2

据我了解,我使用每个记录的哈希选择awk的问题,在这种情况下,我假设RS = \ n,但是可以将其更改为考虑任何其他类型的安排,也可以将其考虑为带有参数或小对话框的代表次数为偶数,而不是奇数。每行都用作哈希,其行数增加,在文件末尾,对数组进行扫描并打印记录的每个偶数。我包括计数以进行检查,但是,删除a [x]足以解决该问题。

高温超导

计数线代码

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

样本数据:

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

样品运行:

countlines feed.txt
j 1
k 1
x 1
a 3
One Sunny Day 3
d 5
e 1

这是一段不错的awk代码,但是不幸的是,awk关联数组根本没有排序,也不保留顺序。
通配符

@Wildcard,我同意你的看法,如果您需要输入顺序而不是排序顺序,则可以通过一个额外的哈希键来实现,这样做的好处是您不必对输入进行排序,因为排序顺序可以在输出较小的时候结束;)
Moises Najar

@Wildcard如果您需要保留订单,请在问题中提及。这种方法也是我的第一个想法,除了说我们可以假定文件已排序之外,您没有提到顺序。当然,如果文件已排序,则始终可以通过传递此解决方案的输出sort
terdon

@terdon,你当然是正确的;输出可以再次进行排序。好点子。还值得注意的是,这!=0是如何awk将数字转换为真/假值所隐含的,这使其可简化为awk '{a[$0]++}END{for(x in a)if(a[x]%2)print x}'
Wildcard

1

如果对输入进行排序,那么awk

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

1

与Perl:

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

1

使用外壳结构

uniq -c file | while read a b; do if (( $a & 1 == 1 )); then echo $b; fi done

1
该行以空格开头或结尾(或更多,因为您忘记了引用$b)而中断。
吉尔(Gilles)'所以

1

有趣的难题!

在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

详细地在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)

在Haskell中:

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

0

一个版本:我使用“定界符”来简化内部循环(它假定第一行不是__unlikely_beginning__,并且假定文本不是以line:结尾__unlikely_ending__,并在输入行的末尾添加该特殊定界符。算法可以同时假设:)

{ 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; }
'

所以:

  • 我们记得当前正在查看的模式,每次发生时都将其增加一。[并且如果确实发生,我们将跳过接下来的2个操作,这是在模式更改时的情况]
  • 当模式改变时:
    • 如果不是2的倍数,我们将打印一次记忆模式
    • 且在每种情况下模式都已更改:新存储的模式是当前模式,我们只看到了一次。
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.