Como ordenar um arquivo por coluna de duração?

2

Como classificar um arquivo contendo abaixo? (s = segundo, h = hora, d = dia m = minuto)

1s
2s
1h
2h
1m
2m
2s
1d
1m
    
por mert inan 15.10.2017 / 11:22

5 respostas

5
awk '{ unitvalue=$1; }; 
    /s/ { m=1 }; /m/ { m=60 }; /h/ { m=3600 }; /d/ { m=86400 }; 
    { sub("[smhd]","",unitvalue); unitvalue=unitvalue*m; 
    print unitvalue " " $1; }' input |
        sort -n | awk '{ print $2 }'
1s
2s
2s
1m
1m
2m
1h
2h
1d
    
por 15.10.2017 / 13:03
4

Primeira versão - FPAT é usado

gawk '
BEGIN {
    FPAT="[0-9]+|[smhd]";
}
/s/ { factor = 1 }
/m/ { factor = 60 }
/h/ { factor = 3600 }
/d/ { factor = 86400 }
{
    print $1 * factor, $0;
}' input.txt | sort -n | awk '{print $2}'

FPAT - A regular expression describing the contents of the fields in a record. When set, gawk parses the input into fields, where the fields match the regular expression, instead of using the value of the FS variable as the field separator.

Segunda versão

Fiquei surpreso ao descobrir que, sem FPAT , também funciona. É causado o mecanismo de conversão numérica de awk - Como o awk converte entre strings e números , a saber:

A string is converted to a number by interpreting any numeric prefix of the string as numerals: "2.5" converts to 2.5, "1e3" converts to 1,000, and "25fix" has a numeric value of 25. Strings that can’t be interpreted as valid numbers convert to zero.

gawk '
/s/ { factor = 1 }
/m/ { factor = 60 }
/h/ { factor = 3600 }
/d/ { factor = 86400 }
{
    print $0 * factor, $0;
}' input.txt | sort -n | awk '{print $2}'

Entrada (alterada um pouco)

1s
122s
1h
2h
1m
2m
2s
1d
1m

Saída

Nota: 122 segundos a mais do que 2 minutos, por isso é ordenado após 2m.

1s
2s
1m
1m
2m
122s
1h
2h
1d
    
por 15.10.2017 / 16:44
2

Se você tiver apenas algumas vezes no formato da sua pergunta:

sort -k 1.2,1.2 -k 1.1,1.1 <file>

Em que <file> é o arquivo em que seus dados residem. Esse comando classifica na segunda letra (ascendente) e, em seguida, classifica na primeira letra (crescente). Isso funciona porque é tão improvável que a ordenação das letras para as unidades de tempo (d > m > s) é exatamente a ordem que queremos (dia > horas > minutos > segundos).

    
por 15.10.2017 / 12:58
2

Essa é uma extensão da resposta do MiniMax que pode manipular um intervalo mais amplo de valor de duração, como 1d3h10m40s .

Programa GNU Awk (armazenado em parse-times.awk por causa desta resposta):

#!/usr/bin/gawk -f
BEGIN{
  FPAT = "[0-9]+[dhms]";
  duration["s"] = 1;
  duration["m"] = 60;
  duration["h"] = duration["m"] * 60;
  duration["d"] = duration["h"] * 24;
}

{
  t=0;
  for (i=1; i<=NF; i++)
    t += $i * duration[substr($i, length($i))];
  print(t, $0);
}

Invocação:

gawk -f parse-times.awk input.txt | sort -n -k 1,1 | cut -d ' ' -f 2
    
por 15.10.2017 / 19:04
1

Solução no Python 3:

#!/usr/bin/python3
import re, fileinput

class RegexMatchIterator:
    def __init__(self, regex, string, error_on_incomplete=False):
        self.regex = regex
        self.string = string
        self.error_on_incomplete = error_on_incomplete
        self.pos = 0

    def __iter__(self):
        return self

    def __next__(self):
        match = self.regex.match(self.string, self.pos)
        if match is not None:
            if match.end() > self.pos:
                self.pos = match.end()
                return match
            else:
                fmt = '{0!s} returns an empty match at position {1:d} for "{3!r}"'

        elif self.error_on_incomplete and self.pos < len(self.string):
            if isinstance(self.error_on_incomplete, str):
                fmt = self.error_on_incomplete
            else:
                fmt = '{0!s} didn\'t match the suffix {3!r} at position {1:d} of {2!r}'

        else:
            raise StopIteration(self.pos)

        raise ValueError(fmt.format(
            self.regex, self.pos, self.string, self.string[self.pos:]))


DURATION_SUFFIXES = { 's': 1, 'm': 60, 'h': 3600, 'd': 24*3600 }
DURATION_PATTERN = re.compile(
    '(\d+)(' + '|'.join(map(re.escape, DURATION_SUFFIXES.keys())) + ')')

def parse_duration(s):
    return sum(
        int(m.group(1)) * DURATION_SUFFIXES[m.group(2)]
        for m in RegexMatchIterator(DURATION_PATTERN, s,
            'Illegal duration string {3!r} at position {1:d}'))


if __name__ == '__main__':
    with fileinput.input() as f:
        result = sorted((l.rstrip('\n') for l in f), key=parse_duration)
    for item in result:
        print(item)

Como você pode ver, passei cerca de ⅔ da contagem de linhas em direção a um iterador útil em regex.match() resulta porque regex.finditer() não amarra corresponde ao início da região atual e não há outras maneiras adequadas para iterar resultados de correspondência. * grrr *

    
por 15.10.2017 / 20:56