如何获得线性分布的随机日期?

时间:2017-02-24 13:10:06

标签: java date random distribution

我想从(反向)线性分布值得到一个随机的毫秒值(如果我得到正确的术语)。

本质上,我希望在两个时间点tDate之间有一个随机的时间点early(在我的情况下为late),其中t 1}}朝向early的概率要大于late的概率。 late本身的概率可能为0.0

我当前的java代码只使用统一分布,因此我打算将其修改为(反向)线性分布

public Date getRandomDate(Date early, Date late) {
  long diff = late.getTime() - early.getTime();
  final int randVal = rand.nextInt((int) diff);
  Calendar cal = Calendar.getInstance();
  cal.setTime(early);
  cal.add(Calendar.MILLISECOND, randVal);
  return cal.getTime();
}

3 个答案:

答案 0 :(得分:3)

this answer的小猪退回到类似的问题,您可以至少接听两个兰特电话:

final int randVal = Math.min(rand.nextInt((int) diff), rand.nextInt((int) diff));

最后,这是使用cumulative distribution functionx^2)解决x的另一种更复杂的方法:

int randVal = (int) Math.floor(diff * (1.0 - Math.sqrt(rand.nextDouble())));
if(randVal >= diff) randVal = 0; // handle the edge case

为了满足您的指定要求,已从1.0中减去平方根以反转分布,即将更大的密度放在范围的底部。

答案 1 :(得分:1)

accepted Answer by Parker似乎是正确的并且做得很好。

使用java.time

该问题使用过时的麻烦的日期时间类,这些类现在是遗留的,由java.time类取代。下面是相同类型的代码,以及Parker的解决方案,在java.time中重写。

Instant

首先,如果您必须使用java.util.Date个对象,请转换为InstantInstant类代表UTC中时间轴上的一个时刻,分辨率为nanoseconds(小数部分最多九(9)位)。要转换,请查看添加到旧类的新方法。

Instant instant = myJavaUtilDate.toInstant();  // From legacy to modern class.
java.util.Date myJavaUtilDate = java.util.Date.from( instant ) ;  // From modern class to legacy.

让我们重写方法签名,但传递并返回Instant个对象。

public Instant getRandomDate( Instant early , Instant late) {

验证early参数确实早于later参数。或者,断言下面看到的Duration不是负数(! duration.isNegative())。

    if( early.isAfter( late) ) { … }  // Assert `early` is not after `late`.

半开

计算最早和最晚时刻之间的差值。这是通过常用于定义时间跨度的半开放方法完成的,其中开头是包含,结尾是独占

Duration

Duration类以总秒数加上小数秒(以纳秒为单位)表示这样的跨度。

    Duration duration = Duration.between( early , late ) ;

要进行随机数学运算,我们需要一个整数。要处理纳秒分辨率,我们需要64-bit long而不是32-bit int

ThreadLocalRandom

提示:如果跨线程生成这些值,请使用类ThreadLocalRandom。引用文档:

  

如果适用,在并发程序中使用ThreadLocalRandom而不是共享Random对象通常会遇到更少的开销和争用。

我们可以通过调用ThreadLocalRandom::nextLong( origin , bound )以半开放式样式指定范围,其中原点为包含,界限为独占

    long bound = duration.toNanos() ;
    long nanos1 = ThreadLocalRandom.current().nextLong( 0 , bound ); 
    long nanos2 = ThreadLocalRandom.current().nextLong( 0 , bound ); 
    long nanos = Math.min( nanos1 , nanos2 );  // Select the lesser number.
    Instant instant = early.plusNanos( nanos );
    return instant ;
}

实例

请参阅code below run live at IdeOne.com

我们提取为每个仅限日期(LocalDate)生成的日期时间值的数量,作为调查结果的随意方式,以验证我们希望的结果偏向早期日期。

测试工具向您展示如何为ZoneId分配时区(Instant)以获取ZonedDateTime对象,并从中提取LocalDate。如果您希望通过特定区域wall-clock time而不是UTC的镜头查看Instant个对象,请将其用作指南。

/* package whatever; // don't place package name! */

import java.util.*;
import java.lang.*;
import java.io.*;

import java.util.concurrent.ThreadLocalRandom ;
import java.util.TreeMap ;

import java.time.*;
import java.time.format.*;
import java.time.temporal.*;

/* Name of the class has to be "Main" only if the class is public. */
class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Ideone app = new Ideone();
        app.doIt();
    }

    public void doIt() {
        ZoneId z = ZoneId.of( "America/Montreal" ) ;
        int count = 10 ;
        LocalDate today = LocalDate.now( z );
        LocalDate laterDate = today.plusDays( count );
        Instant start = today.atStartOfDay( z ).toInstant();
        Instant stop = laterDate.atStartOfDay( z ).toInstant();

        // Collect the frequency of each date. We want to see bias towards earlier dates.
        List<LocalDate> dates = new ArrayList<>( count );
        Map<LocalDate , Integer > map = new TreeMap<LocalDate , Integer >();
        for( int i = 0 ; i <= count ; i ++ ) {
            LocalDate localDate = today.plusDays( i ) ; 
            dates.add( localDate );  // Increment to next date and remember.
            map.put( localDate , new Integer( 0 ) ); // Prepopulate the map with all dates.
        }
        for( int i = 1 ; i <= 10_000 ; i ++ ) {
            Instant instant = this.getRandomInstantBetween( start , stop );
            LocalDate localDate = instant.atZone( z ).toLocalDate();
            Integer integer = map.get( localDate );
            map.put( localDate , integer +  1);  // Increment to count each time get a hit on this date.
        }
        System.out.println( map );
    }

    public Instant getRandomInstantBetween( Instant early , Instant late) {

        Duration duration = Duration.between( early , late ) ;
        // Assert the duration is positive or zero: ( ! duration.isNegative() )

        long bound = duration.toNanos() ;
        ThreadLocalRandom random = ThreadLocalRandom.current() ;
        long nanos1 = random.nextLong( 0 , bound ); // Zero means the `early` date is inclusive, while `bound` here is exclusive.
        long nanos2 = random.nextLong( 0 , bound ); 
        long nanos = Math.min( nanos1 , nanos2 );  // Select the lesser number.
        Instant instant = early.plusNanos( nanos );

        return instant;
    }
}

以下是一些示例结果。这对我来说很好看,但我不是统计学家。使用风险自负。

  

{2017-02-24 = 1853,2017-02-25 = 1697,2017-02-26 = 1548,2017-02-27 = 1255,2017-02-28 = 1130,2017-03-01 = 926,2017-03-02 = 706,2017-03-03 = 485,2017-03-04 = 299,2017-03-05 = 101,2017-03-06 = 0}

     

{2017-02-25 = 930,2017-02-26 = 799,2017-02-27 = 760,2017-02-28 = 657,2017-03-01 = 589,2017-03-02 = 470,2017-03-03 = 342,2017-03-04 = 241,2017-03-05 = 163,2017-03-06 = 49,2017-03-07 = 0}

     

{2017-02-25 = 878,2017-02-26 = 875,2017-02-27 = 786,2017-02-28 = 676,2017-03-01 = 558,2017-03-02 = 440,2017-03-03 = 370,2017-03-04 = 236,2017-03-05 = 140,2017-03-06 = 41,2017-03-07 = 0}

关于java.time

java.time框架内置于Java 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.DateCalendar和&amp; SimpleDateFormat

现在位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuarter和fz more

答案 2 :(得分:0)

也许您可以将类比应用于日期,如本答案中所示。

Java: random integer with non-uniform distribution