Intervalo explícito entre valores

1

Existe alguma maneira limpa de calcular o intervalo entre os elementos da segunda coluna de um arquivo de texto usando alguma magia bash? (Atualmente estou fazendo isso usando Python).

Entrada: Arquivo 1

A   1-5
A   17-19
B   1-5
B   4-6

Saída esperada: Arquivo 2

A   1,2,3,4,5,17,18,19
B   1,2,3,4,5,6    

EDIT @Anthon: para acumular elementos Estou usando algo assim (então calcule os intervalos usando um loop for)

d_pos= {} 
for row in open('File.txt'): 
    x, y = [ value.strip() for value in row.split('\t')] 
    if x in d_pos:        
        d_pos[x].append(y)    
    else:        
        d_pos[x] = [y]
    
por dovah 16.07.2014 / 09:44

3 respostas

1

Straight bash, desde que você perguntou (embora note que estou usando matrizes associativas, exigindo o bash 4.0).

O truque está na expansão da sequência de chaves expressão {x..y} que, para inteiros x any y, expande para o intervalo inclusivo de valores (isto é, [x, y]) como uma lista textual. Precisamos lançar um eval também, já que a expansão de contraventamento acontece antes da expansão da variável.

declare -A data seen  # explicit associative arrays
while read col range; do
   data[$col]="${data[$col]} $(eval echo {${range/-/..}})"
done <<DATA
A   1-5
A   17-19
B   1-5
B   4-6
DATA

# dump array
#declare -p data

for ii in ${!data[@]}; do
    seen=();  datum=""
    # build list of unique values
    for dd in ${data[$ii]}; do
        (( ${seen[$dd]:-0} )) || datum="$datum $dd"
        let seen[$dd]++
    done

    datum=${datum# }     # drop leading space
    datum=${datum// /,}  # spaces to commas
    printf "%-4s %s\n" "$ii" "$datum"
done

Uma variação na expansão da sequência é a{x..y}b onde "a" é prefixada e "b" é anexado a cada termo da expansão: você pode usar isso para acrescentar um "," e alterar a variável de referência mexendo se desejar . A expansão de sequência processa incrementos de 1 ou -1 se x > y

Você também pode precisar ordenar a saída: iterar as chaves de um array associativo não tem uma ordem bem definida, e você não afirmou se os intervalos de entrada são pré-classificados (então eu não compliquei demais o código).

    
por 16.07.2014 / 11:00
1

Seu código Python chega perto, mas, por exemplo não pode lidar com a sobreposição de 4 e 5 para o item B.

O seguinte manipula corretamente usando um set() para evitar a sobreposição, setdefault para eliminar o teste explícito, a chave já existe em d_pos e .split() na linha de entrada para ser menos dependente do \t caractere e eliminando o explícito .strip() :

d_pos= {}
for row in open('File.txt'):
    x, y = [ value for value in row.split()]
    y1, y2 = map(int, y.split('-'))
    d_pos.setdefault(x, set()).update(range (y1, y2+1))
for x in sorted(d_pos):
    print '{}\t{}'.format(x, ','.join(map(str, d_pos[x])))
    
por 16.07.2014 / 10:50
1

Se você puder usar perl :

$ perl -MList::MoreUtils=uniq -anle '
    ($s,$e) = split "-", $F[1];
    push @{$h{$F[0]}}, $s..$e; 
    END {
        $" = ",";
        print "$_   @{[uniq@{$h{$_}}]}" for keys %h;
    }
' file
A   1,2,3,4,5,17,18,19
B   1,2,3,4,5,6

Se você não quiser usar List::MoreUtils , desde quando ele não está no núcleo, você pode fazer:

$ perl -anle '
    ($s,$e) = split "-", $F[1];
    push @{$h{$F[0]}}, $s..$e; 
    END {
        $" = ",";
        for $k (keys %h) {
            %u=();
            print "$k   @{[grep {!$u{$_}++} @{$h{$k}}]}";
        }
    }
' file
    
por 16.07.2014 / 09:56