mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-16 00:03:12 -04:00
[FEATURE][expressions] allow to seed random functions
useful to get deterministic random values
This commit is contained in:
parent
af1c087919
commit
65fed42213
@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "rand",
|
"name": "rand",
|
||||||
"type": "function",
|
"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"},
|
"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"}]
|
"examples": [ { "expression":"rand(1, 10)", "returns":"8"}]
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "randf",
|
"name": "randf",
|
||||||
"type": "function",
|
"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"},
|
"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"}],
|
{"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":"seed","optional":true,"default":"null","description":"any value to use as seed"}],
|
||||||
|
"examples": [ { "expression":"randf(1, 10)", "returns":"4.59258286403147"} ]
|
||||||
}
|
}
|
||||||
|
@ -368,22 +368,68 @@ static QVariant fcnRndF( const QVariantList &values, const QgsExpressionContext
|
|||||||
{
|
{
|
||||||
double min = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent );
|
double min = QgsExpressionUtils::getDoubleValue( values.at( 0 ), parent );
|
||||||
double max = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
|
double max = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
|
||||||
|
|
||||||
if ( max < min )
|
if ( max < min )
|
||||||
return QVariant();
|
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)
|
// Return a random double in the range [min, max] (inclusive)
|
||||||
double f = static_cast< double >( qrand() ) / RAND_MAX;
|
return QVariant( min + generator->generateDouble() * ( max - min ) );
|
||||||
return QVariant( min + f * ( max - min ) );
|
|
||||||
}
|
}
|
||||||
static QVariant fcnRnd( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
|
static QVariant fcnRnd( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
|
||||||
{
|
{
|
||||||
qlonglong min = QgsExpressionUtils::getIntValue( values.at( 0 ), parent );
|
quint32 min = QgsExpressionUtils::getIntValue( values.at( 0 ), parent );
|
||||||
qlonglong max = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );
|
quint32 max = QgsExpressionUtils::getIntValue( values.at( 1 ), parent );
|
||||||
if ( max < min )
|
if ( max < min )
|
||||||
return QVariant();
|
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 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 * )
|
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( "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" ) );
|
<< 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 );
|
randFunc->setIsStatic( false );
|
||||||
functions << randFunc;
|
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 );
|
randfFunc->setIsStatic( false );
|
||||||
functions << randfFunc;
|
functions << randfFunc;
|
||||||
|
|
||||||
|
@ -2160,6 +2160,34 @@ class TestQgsExpression: public QObject
|
|||||||
QgsExpression exp3( QStringLiteral( "rand(10,1)" ) );
|
QgsExpression exp3( QStringLiteral( "rand(10,1)" ) );
|
||||||
QVariant v3 = exp3.evaluate();
|
QVariant v3 = exp3.evaluate();
|
||||||
QCOMPARE( v3.type(), QVariant::Invalid );
|
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()
|
void eval_randf()
|
||||||
@ -2177,6 +2205,34 @@ class TestQgsExpression: public QObject
|
|||||||
QgsExpression exp3( QStringLiteral( "randf(9.3333,1.784)" ) );
|
QgsExpression exp3( QStringLiteral( "randf(9.3333,1.784)" ) );
|
||||||
QVariant v3 = exp3.evaluate();
|
QVariant v3 = exp3.evaluate();
|
||||||
QCOMPARE( v3.type(), QVariant::Invalid );
|
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()
|
void referenced_columns()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user