Acelerar o script que determina se todas as colunas em uma linha são as mesmas ou não

4

Eu preciso acelerar um script que essencialmente determina se todas as "colunas" de cada linha são iguais e, em seguida, grava um novo arquivo contendo um dos elementos idênticos ou um "no_match". O arquivo é delimitado por vírgula, consiste em cerca de 15.000 linhas e contém vários números de "colunas".

Por exemplo:

1-69
4-59,4-59,4-59,4-61,4-61,4-61
1-46,1-46
4-59,4-59,4-59,4-61,4-61,4-61
6-1,6-1
5-51,5-51
4-59,4-59

Grava um novo arquivo:

1-69
no_match
1-46
no_match
6-1
5-51
4-59

Excluindo a segunda e a quarta linhas porque elas contêm colunas não idênticas.

Aqui está meu roteiro longe do elegante:

#!/bin/bash

ind=$1 #file in
num='wc -l "$ind"|cut -d' ' -f1' #number of lines in 'file in'
echo "alleles" > same_alleles.txt #new file to write to

#loop over every line of 'file in'
for (( i =2; i <= "$num"; i++));do
    #take first column of row being looped over (string to check match of other columns with)
    match='awk "FNR=="$i" {print}" "$ind"|cut -d, -f1'
    #counts how many matches there are in the looped row
    match_num='awk "FNR=="$i" {print}" "$ind"|grep -o "$match"|wc -l|cut -d' ' -f1'
    #counts number of commas in each looped row
    comma_num='awk "FNR=="$i" {print}" "$ind"|grep -o ","|wc -l|cut -d' ' -f1'
    #number of columns in each row
    tot_num=$((comma_num + 1))
    #writes one of the identical elements if all contents of row are identical, or writes "no_match" otherwise
    if [ "$tot_num" == "$match_num" ]; then
            echo $match >> same_alleles.txt
    else
            echo "no_match" >> same_alleles.txt
    fi
done

#END

Atualmente, o script leva cerca de 11 min para fazer todas as ~ 15.000 linhas. Eu não tenho certeza de como acelerar isso (estou sinceramente surpreso que eu poderia até fazê-lo funcionar). Qualquer hora derrubada seria fantástica. Abaixo está um trecho menor de 100 linhas que podem ser usadas:

allele
4-39
1-46,1-46,1-46
4-39
4-4,4-4,4-4,4-4
3-23,3-23,3-23
3-21,3-21
4-34,4-34
3-33
4-4,4-4,4-4
4-59,4-59
3-23,3-23,3-23
1-45
1-46,1-46
3-23,3-23,3-23
4-61
1-8
3-7
4-4
4-59,4-59,4-59
1-18,1-18
3-21,3-21
3-23,3-23,3-23
3-23,3-23,3-23
3-30,3-30-3
4-39,4-39
4-61
2-70
4-38-2,4-38-2
1-69,1-69,1-69,1-69,1-69
1-69
4-59,4-59,4-59,4-61,4-61,4-61
1-46,1-46
4-59,4-59,4-59,4-61,4-61,4-61
6-1,6-1
5-51,5-51
4-59,4-59
1-18
3-7
1-69
4-30-4
4-39
1-69
1-69
4-39
3-23,3-23,3-23
4-39
2-5
3-30-3
4-59,4-59,4-59
3-21,3-21
4-59,4-59
3-9
4-59,4-59,4-59
4-31,4-31
1-46,1-46
1-46,1-46,1-46
5-51,5-51
3-48
4-31,4-31
3-7
4-61
4-59,4-59,4-59,4-61,4-61,4-61
4-38-2,4-38-2
3-21,3-21
1-69,1-69,1-69
3-23,3-23,3-23
4-59,4-59
3-48
3-48
1-46,1-46
3-23,3-23,3-23
3-30-3,3-30-3
1-46,1-46,1-46
3-64
3-73,3-73
4-4
1-18
3-7
1-46,1-46
1-3
4-61
2-70
4-59,4-59
5-51,5-51
3-49,3-49
4-4,4-4,4-4
4-31,4-31
1-69
1-69,1-69,1-69
4-39
3-21,3-21
3-33
3-9
3-48
4-59,4-59
4-59,4-59
4-39,4-39
3-21,3-21
1-18

Meu script leva ~ 7 segundos para concluir isso.

Desculpe pelo longo post e agradeço antecipadamente!

    
por Johnny 06.08.2018 / 21:11

4 respostas

5
$ awk -F, '{ for (i=2; i<=NF; ++i) if ($i != $1) { print "no_match"; next } print $1 }' file
1-69
no_match
1-46
no_match
6-1
5-51
4-59

Sinto muito, mas nem olhei para o seu código, havia muita coisa acontecendo. Quando você se encontrar chamando awk três vezes no corpo de um loop nos mesmos dados, terá que procurar outras maneiras de fazê-lo com mais eficiência. Além disso, se você envolver awk , não precisará de grep e cut , já que awk poderia facilmente executar suas tarefas (que, no entanto, não são necessárias).

O script awk acima lê uma linha delimitada por vírgula por vez e compara cada campo com o primeiro campo. Se algum dos testes falhar, a string no_match será impressa e o script continuará com a próxima linha. Se o loop terminar (sem encontrar uma incompatibilidade), o primeiro campo será impresso.

Como um script:

#!/usr/bin/awk -f

BEGIN { FS = "," }

{
    for (i=2; i<=NF; ++i)
        if ($i != $1) {
            print "no_match"
            next
        }

    print $1
}
  • FS é o separador de campo de entrada, também configurável com a opção -F na linha de comando. awk dividirá cada linha nesse caractere para criar os campos.
  • NF é o número de campos no registro atual ("colunas na linha").
  • $i refere-se ao campo i: th no registro atual, em que i pode ser uma variável ou uma constante (como em $1 ).

Relacionados:

Variação DRY :

#!/usr/bin/awk -f

BEGIN { FS = "," }

{
    output = $1

    for (i=2; i<=NF; ++i)
        if ($i != output) {
            output = "no_match"
            break
        }

    print output
}
    
por 06.08.2018 / 21:15
1

O awk é uma linguagem de programação completa. Você já usa. Mas não use apenas para tarefas simples com várias invocações por linha, use-a para toda a tarefa. Use o delimitador de campo no awk, não use cut. Faça o processamento completo no awk.

awk -F',' '
{ 
  eq=1; 
  for (i = 2; i <= NF; i++)
    if ($1 != $i)
      eq=0;
  print eq ? $1 : "no_match";
}
' $1

    
por 06.08.2018 / 21:21
1

Com perl List::MoreUtils , avaliando os elementos distinct / uniq no contexto escalar:

perl -MList::MoreUtils=distinct -F, -lne '
  print( (distinct @F) > 1 ? "no_match" : $F[0])
' example 
1-69
no_match
1-46
no_match
6-1
5-51
4-59
    
por 06.08.2018 / 22:29
1

Você também pode fazer isso usando o editor sed , como mostrado abaixo:

sed -e '
    s/^\([^,]*\)\(,\)*$//;t
    s/.*/NOMATCH/
' input.csv

Aqui contamos com o regex para se multiplicar e chegar ao fim da linha. Se for capaz de fazer isso, então termine com o primeiro campo, caso contrário, o flash NOMATCH .

Explicação:

Isto é o que se passa na minha cabeça quando vejo este pbm:
Pense no comma-separated fields as stones de cores diferentes. E imagine-os se eles podem ser organizados em uma linha como uma repetição da primeira pedra, com uma vírgula prefixando-os.

Algo como:

STONEA ,STONEA ,STONEA ,STONEA ... all the way to end of line

Agora, em termos de terminologia de regex, torna-se:

^ (STONEA) (,) (,) (,) ... all the way to end of line

^ (STONEA) (,)* $

Saída:

1-69
NOMATCH
1-46
NOMATCH
6-1
5-51
4-59
    
por 07.08.2018 / 04:55