Como eu removerei caracteres ou strings duplicados em uma lista:?

3

Por exemplo, em a:bc:d:a:hi:p:a , remova o duplicado a e em apple:orange:apple:.:pear:mango:.:apple - remova o duplicado apple

    
por athomas 22.10.2017 / 04:01

6 respostas

5

Solução GNU awk (todos os separadores serão preservados):

s="apple:orange:apple:.:pear:mango:.:apple"
awk '{ len=split($0,a,/:|:\.:/,seps); 
       for(i=1;i<=len;i++) printf "%s%s",(!w[a[i]]++? a[i]:""),(i==len? "":seps[i]);
       print "" }' <<<"$s"
  • len=split($0,a,/:|:\.:/,seps) - divide a string em partes separadas pelo padrão de regex /:|:\.:/ e armazena as partes na matriz a e as sequências de separação na matriz seps .

  • len - contém o número de elementos / fragmentos criados como resultado da divisão

A saída:

apple:orange::.:pear:mango:.:
    
por 22.10.2017 / 08:40
5

Solução Python pedida

Se a ordem é importante, aqui está o que podemos fazer em python como uma linha:

$ python -c 'import sys;from collections import OrderedDict; d=OrderedDict( (i,True) for i in sys.argv[1].split(":") );print ":".join(d.keys())'  'apple:orange:apple:.:pear:mango:.:apple'
apple:orange:.:pear:mango

Um pouco longo, para podermos transformá-lo em um pequeno script:

#!/usr/bin/env python
from collections import OrderedDict
import sys
d=OrderedDict( (i,True) for i in sys.argv[1].split(":") )
print ":".join(d.keys())

E use-o da seguinte forma:

$ ./uniq_tokens.py 'apple:orange:apple:.:pear:mango:.:apple'                                                                                                           
apple:orange:.:pear:mango

A maneira como isso funciona:

  • nós damos a string que desejamos processar como argumento de linha de comando, por isso usamos sys module para nos referirmos a sys.argv[1]
  • % desys.argv[1] get é dividido em tokens usando : como separador
  • (i,True) for i in sys.argv[1].split(":") nos permite criar uma lista de tuplas com dois valores, onde True é apenas valor fictício
  • OrderedDict , em seguida, pega isso e cria um dicionário de pares de valores-chave. Esta é uma maneira "barata" de fazer o conjunto ordenado com compreensão de listas como alternativa à compreensão de dit. Se a string já existir como chave, ela permanecerá exclusiva ( a menos que alguém faça algo desnecessário )
  • ":".join() nos permitirá pegar todos os tokens que dividimos (por isso usamos d.keys() aqui), e converter de volta para uma string inteira legal associada a :
  • a impressão é autoexplicativa.

Solução Python não ordenada (mas mais curta)

Se o pedido não importa, podemos obter uma solução mais curta (mas isso é mais por diversão do que por aplicação prática - provavelmente 99% do tempo em que você deseja preservar a ordem dos tokens):

$ python -c  'import sys;print ":".join(set(sys.argv[1].split(":")))'  'a:bc:d:a:hi:p:a'                                                                               
a:p:hi:d:bc

A maneira como isso funciona é simples:

  • nós passamos a string desejada como argumento de linha de comando, portanto, precisamos que import sys se refira ao primeiro argumento de linha de comando como sys.argv[1]
  • agora, vamos desembrulhar essa segunda parte; sys.argv[1].split(":") nos dá uma lista de tokens do que originalmente era uma string inteira, e nós dividimos usando : como separador para tokens individuais
  • set() terá essa lista de strings mencionada acima e nos fornecerá valores exclusivos
  • agora, precisamos converter essa coleção de strings de volta em uma string inteira, e é por isso que usamos ":".join() para unir todos os tokens novamente usando : como separador.
  • print é auto-explicativo. Note que esta é a sintaxe do Python 2.7. Use print() para o Python 3

E aqui está o teste com outras strings:

$ python -c  'import sys;print ":".join(set(sys.argv[1].split(":")))'  'apple:orange:apple:.:pear:mango:.:apple'                                                       
orange:mango:pear:apple:.
    
por 22.10.2017 / 09:32
4

Você pode usar o tipo de dados hash do Perl para remover duplicatas:

$ cat ./remove_dup.pl
#!/usr/bin/perl -w

use strict;

my $input = shift;
my %seen;

my $order=1;
foreach my $dir ( split /:/, $input ) {
        $seen{$dir} = $order++ unless ($seen{$dir})  ;
}
my $output =  join( ':',  sort { $seen{$a} <=> $seen{$b} } keys(%seen));
print $output . "\n";

Demo:

$ ./remove_dup.pl a:bc:d:a:hi:p:a
a:bc:d:hi:p
$ ./remove_dup.pl apple:orange:apple:.:pear:mango:.:apple
apple:orange:.:pear:mango
    
por 22.10.2017 / 04:57
4

Uma solução de shell:

#!/bin/sh

rmdups () (
    IFS=':'
    for elem in $1; do
        # If $path already contains $elem, do nothing. Otherwise add
        # $elem to the end of $path (or set $path to $elem if $path was
        # empty).
        case "$path" in
            $elem|$elem:*|*:$elem:*|*:$elem) ;; # do nothing
            *) [ -n "$path" ] && path="$path:$elem" || path="$elem" ;;
        esac
    done
    printf '%s\n' "$path"
)

p='apple:apple:::orange:apple:.:pear:mango:.:apple'
rmdups "$p"

Esta é uma função do shell que removeu duplicatas de qualquer variável PATH . Usando (...) em vez de {...} para o corpo da função, garantimos que todas as variáveis dentro da função sejam locais (precisamos disso para garantir que não poluimos o ambiente do chamador com elem , path e a variável IFS modificada). Uma função bash poderia declarar essas variáveis com local ou typeset ( typeset funcionaria em bash e ksh93 ).

A saída do acima é

apple::orange:.:pear:mango
    
por 22.10.2017 / 10:59
3

Uma solução bash :

strjoin() { local IFS="$1"; echo "${*:2}"; }
dedup() {
    declare -A valbag
    IFS=: read -r -a vals <<<"$1"
    for ((i=0; i < ${#vals[@]}; i++)); do
        (( valbag[${vals[i]}]++ > 0 )) && vals[i]=''
    done
    strjoin : "${vals[@]}"
}

$ dedup apple:orange:apple:.:pear:mango:.:apple
apple:orange::.:pear:mango::
$ dedup 'a:bc:d:a:hi:p:a'
a:bc:d::hi:p:

Isso atende ao requisito de seu comentário de deixar todos os dois pontos no lugar e apenas esvaziar as duplicatas entre os dois-pontos.

    
por 22.10.2017 / 12:08
1

Solução de awk de uma linha simples.

awk -F\: '{for (i=1;i<=NF;i++) printf(!arr[$i]++)?(i<2?"":":")$i:""; print ""}' infile
a:bc:d:hi:p
apple:orange:.:pear:mango

Não, se você quiser preservar os dois pontos, então.

awk -F\: '{for (i=1;i<=NF;i++) printf(!arr[$i]++)?(i<2?"":":")$i:":"; print ""}'
a:bc:d::hi:p:
apple:orange::.:pear:mango::

Ou com loop while;

awk -F\: '{i=0;while (NF>=++i) printf(!arr[$i]++)?(i<2?"":":")$i:":"; print ""}'
    
por 22.10.2017 / 11:35