[FEATURE][expressions] allow to seed random functions

useful to get deterministic random values
This commit is contained in:
olivierdalang 2019-12-03 13:31:19 +01:00
parent af1c087919
commit 65fed42213
4 changed files with 116 additions and 12 deletions

View File

@ -1,8 +1,9 @@
{
"name": "rand",
"type": "function",
"description": "Returns a random integer within the range specified by the minimum and maximum argument (inclusive).",
"description": "Returns a random integer within the range specified by the minimum and maximum argument (inclusive). If a seed is provided, the returned will always be the same, depending on the seed.",
"arguments": [ {"arg":"min","description":"an integer representing the smallest possible random number desired"},
{"arg":"max","description":"an integer representing the largest possible random number desired"}],
{"arg":"max","description":"an integer representing the largest possible random number desired"},
{"arg":"seed","optional":true,"default":"null","description":"any value to use as seed"}],
"examples": [ { "expression":"rand(1, 10)", "returns":"8"}]
}

View File

@ -1,8 +1,9 @@
{
"name": "randf",
"type": "function",
"description": "Returns a random float within the range specified by the minimum and maximum argument (inclusive).",
"description": "Returns a random float within the range specified by the minimum and maximum argument (inclusive). If a seed is provided, the returned will always be the same, depending on the seed.",
"arguments": [ {"arg":"min","optional":true,"default":"0.0","description":"an float representing the smallest possible random number desired"},
{"arg":"max","optional":true,"default":"1.0","description":"an float representing the largest possible random number desired"}],
"examples": [ { "expression":"randf(1, 10)", "returns":"4.59258286403147"}]
{"arg":"max","optional":true,"default":"1.0","description":"an float representing the largest possible random number desired"},
{"arg":"seed","optional":true,"default":"null","description":"any value to use as seed"}],
"examples": [ { "expression":"randf(1, 10)", "returns":"4.59258286403147"} ]
}

View File

@ -368,22 +368,68 @@ static QVariant fcnRndF( const QVariantList &values, const QgsExpressionContext
{
double min = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent );
double max = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
if ( max < min )
return QVariant();
QRandomGenerator *generator;
if ( QgsExpressionUtils::isNull( values.at( 2 ) ) )
{
generator = QRandomGenerator::global();
}
else
{
quint32 seed;
if ( QgsExpressionUtils::isIntSafe( values.at( 2 ) ) )
{
// if seed can be converted to int, we use as is
seed = QgsExpressionUtils::getIntValue( values.at( 2 ), parent );
}
else
{
// if not, we hash string representation to int
QString seedStr = QgsExpressionUtils::getStringValue( values.at( 2 ), parent );
std::hash<std::string> hasher;
seed = hasher( seedStr.toStdString() );
}
generator = &QRandomGenerator::QRandomGenerator( seed );
}
// Return a random double in the range [min, max] (inclusive)
double f = static_cast< double >( qrand() ) / RAND_MAX;
return QVariant( min + f * ( max - min ) );
return QVariant( min + generator->generateDouble() * ( max - min ) );
}
static QVariant fcnRnd( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
qlonglong min = QgsExpressionUtils::getIntValue( values.at( 0 ), parent );
qlonglong max = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );
quint32 min = QgsExpressionUtils::getIntValue( values.at( 0 ), parent );
quint32 max = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );
if ( max < min )
return QVariant();
QRandomGenerator *generator;
if ( QgsExpressionUtils::isNull( values.at( 2 ) ) )
{
generator = QRandomGenerator::global();
}
else
{
quint32 seed;
if ( QgsExpressionUtils::isIntSafe( values.at( 2 ) ) )
{
// if seed can be converted to int, we use as is
seed = QgsExpressionUtils::getIntValue( values.at( 2 ), parent );
}
else
{
// if not, we hash string representation to int
QString seedStr = QgsExpressionUtils::getStringValue( values.at( 2 ), parent );
std::hash<std::string> hasher;
seed = hasher( seedStr.toStdString() );
}
generator = &QRandomGenerator::QRandomGenerator( seed );
}
// Return a random integer in the range [min, max] (inclusive)
return QVariant( min + ( qrand() % static_cast< qlonglong >( max - min + 1 ) ) );
return QVariant( generator->bounded( min, max + 1 ) );
}
static QVariant fcnLinearScale( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
@ -5216,11 +5262,11 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "log" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "base" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ), fcnLog, QStringLiteral( "Math" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "round" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "places" ), true, 0 ), fcnRound, QStringLiteral( "Math" ) );
QgsStaticExpressionFunction *randFunc = new QgsStaticExpressionFunction( QStringLiteral( "rand" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "min" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "max" ) ), fcnRnd, QStringLiteral( "Math" ) );
QgsStaticExpressionFunction *randFunc = new QgsStaticExpressionFunction( QStringLiteral( "rand" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "min" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "max" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "seed" ), true ), fcnRnd, QStringLiteral( "Math" ) );
randFunc->setIsStatic( false );
functions << randFunc;
QgsStaticExpressionFunction *randfFunc = new QgsStaticExpressionFunction( QStringLiteral( "randf" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "min" ), true, 0.0 ) << QgsExpressionFunction::Parameter( QStringLiteral( "max" ), true, 1.0 ), fcnRndF, QStringLiteral( "Math" ) );
QgsStaticExpressionFunction *randfFunc = new QgsStaticExpressionFunction( QStringLiteral( "randf" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "min" ), true, 0.0 ) << QgsExpressionFunction::Parameter( QStringLiteral( "max" ), true, 1.0 ) << QgsExpressionFunction::Parameter( QStringLiteral( "seed" ), true ), fcnRndF, QStringLiteral( "Math" ) );
randfFunc->setIsStatic( false );
functions << randfFunc;

View File

@ -2160,6 +2160,34 @@ class TestQgsExpression: public QObject
QgsExpression exp3( QStringLiteral( "rand(10,1)" ) );
QVariant v3 = exp3.evaluate();
QCOMPARE( v3.type(), QVariant::Invalid );
// Supports multiple type of seeds
QgsExpression exp4( QStringLiteral( "rand(1,100,123)" ) );
QVariant v4 = exp4.evaluate();
QCOMPARE( v4.type(), QVariant::Int );
QgsExpression exp5( QStringLiteral( "rand(1,100,1.23)" ) );
QVariant v5 = exp5.evaluate();
QCOMPARE( v5.type(), QVariant::Int );
QgsExpression exp6( QStringLiteral( "rand(1,100,'123')" ) );
QVariant v6 = exp6.evaluate();
QCOMPARE( v6.type(), QVariant::Int );
QgsExpression exp7( QStringLiteral( "rand(1,100,'abc')" ) );
QVariant v7 = exp7.evaluate();
QCOMPARE( v7.type(), QVariant::Int );
// Two calls with the same seed return the same numer
QgsExpression exp8( QStringLiteral( "rand(1,100000000000,1)" ) );
QVariant v8 = exp8.evaluate();
QgsExpression exp9( QStringLiteral( "rand(1,100000000000,1)" ) );
QVariant v9 = exp9.evaluate();
QCOMPARE( v8.toInt() == v9.toInt(), true );
// Two calls with a different seed return a different number
QgsExpression exp10( QStringLiteral( "rand(1,100000000000,1)" ) );
QVariant v10 = exp10.evaluate();
QgsExpression exp11( QStringLiteral( "rand(1,100000000000,2)" ) );
QVariant v11 = exp11.evaluate();
QCOMPARE( v10.toInt() != v11.toInt(), true );
}
void eval_randf()
@ -2177,6 +2205,34 @@ class TestQgsExpression: public QObject
QgsExpression exp3( QStringLiteral( "randf(9.3333,1.784)" ) );
QVariant v3 = exp3.evaluate();
QCOMPARE( v3.type(), QVariant::Invalid );
// Supports multiple type of seeds
QgsExpression exp4( QStringLiteral( "randf(1,100,123)" ) );
QVariant v4 = exp4.evaluate();
QCOMPARE( v4.type(), QVariant::Float );
QgsExpression exp5( QStringLiteral( "randf(1,100,1.23)" ) );
QVariant v5 = exp5.evaluate();
QCOMPARE( v5.type(), QVariant::Float );
QgsExpression exp6( QStringLiteral( "randf(1,100,'123')" ) );
QVariant v6 = exp6.evaluate();
QCOMPARE( v6.type(), QVariant::Float );
QgsExpression exp7( QStringLiteral( "randf(1,100,'abc')" ) );
QVariant v7 = exp7.evaluate();
QCOMPARE( v7.type(), QVariant::Float );
// Two calls with the same seed return the same numer
QgsExpression exp8( QStringLiteral( "randf(seed:=1)" ) );
QVariant v8 = exp8.evaluate();
QgsExpression exp9( QStringLiteral( "randf(seed:=1)" ) );
QVariant v9 = exp9.evaluate();
QCOMPARE( v8.toFloat() == v9.toFloat(), true );
// Two calls with a different seed return a different number
QgsExpression exp10( QStringLiteral( "randf(seed:=1)" ) );
QVariant v10 = exp10.evaluate();
QgsExpression exp11( QStringLiteral( "randf(seed:=2)" ) );
QVariant v11 = exp11.evaluate();
QCOMPARE( v10.toFloat() != v11.toFloat(), true );
}
void referenced_columns()