改进模拟数据生成

改进模拟数据生成

我正在尝试使用模拟数据生成 csv

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;

从 1 到百万循环并生成唯一的 id &随机的日期

但它运行速度非常慢,有什么单线可以使其平行吗?

答案1

最终结果请参见最后。

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;

Shell 循环很慢,有两个主要因素导致这个特定的循环特别慢:

  1. 在每次迭代中打开并附加到文件。
  2. 每次迭代中两次执行外部实用程序(shuf和)。dateecho可能内置于 shell 中,因此会产生更少的开销。

输出重定向最容易修复:

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"

这只会打开输出文件一次,并在循环期间保持打开状态。


其余的代码可以使用awkGNU更有效地完成date(因为您正在使用shuf我假设您使用的是 Linux 系统,这意味着很可能date实际上是 GNU date)。

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

这个东西会生成 100 行,例如

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.)

让我们将这些输入到 GNU 中date。 GNUdate有这个标志 ,-f它允许我们批量输入多个日期规范,例如我们的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'

现在我们得到

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

然后只需将唯一 ID(一个连续整数)添加到每一行即可:

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 }'

这给你

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.)

现在我们完成了。在这个过程中,我完全忘记了我们有一个 shell 循环。事实证明不需要。

只需将 调高100到您想要的任何值,然后调整随机数生成器以满足您的需求。 rand()返回一个浮点值,使得 0 <= number < 1。


显然,如果您只想要 8 月(有 31 天的月份)中的随机日期,您可以date完全绕过:

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

使用 GNUawk和 Mike 的awk( mawk),但不使用 BSD awk,您甚至可以直接在 中进行正确的日期处理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

现在我们处理的是 Unix 时间戳而不是天数。 1501545600 对应于“Tue Aug 1 00:00:00 UTC 2017”,31 天有 2678400 秒。

答案2

# 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 可能更快,让我们尝试一下

$ 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

嗯,是的,再快一点……

相关内容