melhora a geração de dados simulados

3

Estou tentando gerar o csv com dados simulados

for i in {1..1000000..1}
do 
  echo "$i,$(date -d "2017-08-01 + $(shuf -i 1-31 -n 1) days" +'%Y-%m-%d')" >> $F
done;

fazendo um loop de 1 a um milhão e gerando id & data aleatória

mas funciona muito devagar, existe algum marcador para torná-lo paralelo?

    
por IddoE 27.08.2017 / 15:08

2 respostas

3

Veja o final para o resultado final.

for i in {1..1000000..1}
do 
  echo "$i,$(date -d "2017-08-01 + $(shuf -i 1-31 -n 1) days" +'%Y-%m-%d')" >> $F
done;

Os loops da shell são lentos e há duas coisas principais que tornam esse loop extra muito lento:

  1. Abrindo e anexando a um arquivo em cada iteração.
  2. Duas execuções de utilitários externos ( shuf e date ) em cada iteração. O echo provavelmente está embutido no shell, de modo que incorre em menos sobrecarga.

O redirecionamento de saída é mais facilmente corrigido:

for i in {1..1000000..1}
do 
  echo "$i,$(date -d "2017-08-01 + $(shuf -i 1-31 -n 1) days" +'%Y-%m-%d')" 
done >"$F"

Isso só abre o arquivo de saída uma vez e o mantém aberto pela duração do loop.

O resto do código pode ser feito de forma mais eficiente com awk e GNU date (desde que você esteja usando shuf , suponho que você esteja em um sistema Linux, o que significa que é muito provável que date é na verdade GNU date ).

awk 'END { for (i=0;i<100;++i) { printf("2017-08-01 + %d days\n", 1+int(31*rand())) }}' /dev/null

Essa coisa gera 100 linhas como

2017-08-01 + 22 days
2017-08-01 + 31 days
2017-08-01 + 11 days
2017-08-01 + 27 days
2017-08-01 + 27 days
2017-08-01 + 20 days
(etc.)

Vamos colocar isso no GNU date . O GNU date tem este sinalizador, -f , que nos permite alimentá-lo em lote com várias especificações de data, por exemplo, aquelas produzidas pelo nosso programa awk :

awk 'END { for (i=0;i<100;++i) { printf("2017-08-01 + %d days\n", 1+int(31*rand())) }}' /dev/null |
date -f - +'%Y-%m-%d'

Agora chegamos

2017-08-23
2017-08-27
2017-08-21
2017-08-29
2017-08-25
2017-08-17
2017-08-07
(etc.)

Então é só adicionar o ID único (um inteiro sequencial) a cada linha:

awk 'END { for (i=0;i<100;++i) { printf("2017-08-01 + %d days\n", 1+int(31*rand())) }}' /dev/null |
date -f - +'%Y-%m-%d' |
awk -vOFS=',' '{ print NR, $0 }'

Isso te dá

1,2017-08-06
2,2017-08-17
3,2017-08-25
4,2017-08-28
5,2017-08-14
6,2017-08-15
7,2017-08-17
8,2017-08-10
9,2017-08-16
10,2017-08-08
(etc.)

E agora terminamos. E no processo, eu esqueci completamente que tínhamos um loop de shell. Acontece que não é necessário.

Apenas aumente o 100 para qualquer valor desejado e ajuste o gerador de números aleatórios para atender às suas necessidades. rand() retorna um valor de ponto flutuante tal que 0 < = number < 1.

Obviamente, se você quer apenas datas aleatórias em agosto (um mês com 31 dias), você pode ignorar date ao todo:

awk 'END { for (i=1;i<=100;++i) { printf("%d,2017-08-%02d\n", i, 1+int(31*rand())) }}' /dev/null

Com o GNU awk e o awk do Mike ( mawk ), mas não com o BSD awk , você pode até mesmo manipular a data corretamente diretamente em awk :

awk 'END { for (i=1;i<=100;++i) { printf("%d,%s\n", i, strftime("%Y-%m-%d", 1501545600 + int(2678400*rand()),1 )) }}' /dev/null

Agora, estamos lidando com timestamps do Unix, e não com dias. 1501545600 corresponde a "terça-feira 1 de agosto às 00:00:00 UTC 2017" e há 2678400 segundos em 31 dias.

    
por 27.08.2017 / 16:57
1
# A "random" date between 2000-01-01 and 2025-12-28
# Only uses day 01 to 28 
rand_date() {
    printf "%4d-%02d-%02d" $((RANDOM%25+2000)) $((RANDOM%12+1)) $((RANDOM%28+1))
}

csv_data() {
    for ((i=1; i<="$1"; i++)); do printf "%d,%s\n" $i $(rand_date); done
}
$ time (csv_data 1000000 > data.csv)
real    7m26.683s
user    0m36.376s
sys 1m57.768s

Perl provavelmente é mais rápido, vamos tentar

$ cat data.pl
#!/usr/bin/perl
$, = ",";
$\ = "\n";

sub rand_date {
    sprintf "%4d-%02d-%02d", int(rand(25))+2000, int(rand(12))+1, int(rand(28))+1;
}

sub csv_data {
    my $n = shift;
    for ($i = 1; $i <= $n; $i++) {
        print $i, rand_date();
    }
}

csv_data(1_000_000);
$ time (perl data.pl > data.csv)

real    0m0.881s
user    0m0.876s
sys 0m0.004s

Hmm, sim, um pouco mais rápido ...

    
por 28.08.2017 / 23:23