J'essaie de convertir une plage de nombres en une autre, en maintenant le ratio. Les maths ne sont pas mon point fort.
J'ai un fichier image où les valeurs en points peuvent aller de -16000.00 à 16000.00 bien que la plage typique puisse être beaucoup moins grande. Ce que je veux faire, c'est compresser ces valeurs dans la plage entière comprise entre 0 et 100, où 0 est la valeur du plus petit point et 100 la valeur du plus grand. Tous les points entre les deux doivent conserver un rapport relatif, même si une précision est en train d'être perdue. J'aimerais le faire dans python, mais même un algorithme général devrait suffire. Je préférerais un algorithme où la plage min/max ou l'une ou l'autre peut être ajustée (la deuxième plage pourrait être comprise entre -50 et 800 au lieu de 0 à 100).
NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
Ou un peu plus lisible:
OldRange = (OldMax - OldMin)
NewRange = (NewMax - NewMin)
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
Ou si vous souhaitez protéger le cas où l'ancienne plage est 0 (OldMin = OldMax):
OldRange = (OldMax - OldMin)
if (OldRange == 0)
NewValue = NewMin
else
{
NewRange = (NewMax - NewMin)
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
}
Notez que dans ce cas, nous sommes obligés de choisir l'une des nouvelles valeurs de plage possibles de manière arbitraire. Selon le contexte, les choix judicieux peuvent être: NewMin
(voir exemple), NewMax
ou (NewMin + NewMax) / 2
C'est une simple conversion linéaire.
new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min
Convertir 10000 sur une échelle de -16000 à 16000 en une nouvelle échelle de 0 à 100 donne:
old_value = 10000
old_min = -16000
old_max = 16000
new_min = 0
new_max = 100
new_value = ( ( 10000 - -16000 ) / (16000 - -16000) ) * (100 - 0) + 0
= 81.25
En fait, il y a des cas où les réponses ci-dessus se briseraient. Tels que valeur entrée erronée, plage entrée erronée, plages entrée/sortie négatives.
def remap( x, oMin, oMax, nMin, nMax ):
#range check
if oMin == oMax:
print "Warning: Zero input range"
return None
if nMin == nMax:
print "Warning: Zero output range"
return None
#check reversed input range
reverseInput = False
oldMin = min( oMin, oMax )
oldMax = max( oMin, oMax )
if not oldMin == oMin:
reverseInput = True
#check reversed output range
reverseOutput = False
newMin = min( nMin, nMax )
newMax = max( nMin, nMax )
if not newMin == nMin :
reverseOutput = True
portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
if reverseInput:
portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin)
result = portion + newMin
if reverseOutput:
result = newMax - portion
return result
#test cases
print remap( 25.0, 0.0, 100.0, 1.0, -1.0 ), "==", 0.5
print remap( 25.0, 100.0, -100.0, -1.0, 1.0 ), "==", -0.25
print remap( -125.0, -100.0, -200.0, 1.0, -1.0 ), "==", 0.5
print remap( -125.0, -200.0, -100.0, -1.0, 1.0 ), "==", 0.5
#even when value is out of bound
print remap( -20.0, 0.0, 100.0, 0.0, 1.0 ), "==", -0.2
Il existe une condition, lorsque toutes les valeurs que vous vérifiez est identique, où le code de @ jerryjvl renverrait NaN.
if (OldMin != OldMax && NewMin != NewMax):
return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
return (NewMax + NewMin) / 2
Je n'ai pas creusé le BNF pour cela, mais la documentation d'Arduino avait un excellent exemple de la fonction et de sa panne. J'ai pu utiliser ceci dans Python en ajoutant simplement un changement de nom de fichier à remapper (car map est une commande intégrée) et en supprimant les caractères majuscules et les accolades (c'est-à-dire, en supprimant tout ce qui est long).
Original
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
Python
def remap(x, in_min, in_max, out_min, out_max):
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
Port PHP
La solution de PenguinTD a été utile, je l'ai donc portée en PHP. Aide-toi!
/**
* =====================================
* Remap Range
* =====================================
* - Convert one range to another. (including value)
*
* @param int $intValue The value in the old range you wish to convert
* @param int $oMin The minimum of the old range
* @param int $oMax The maximum of the old range
* @param int $nMin The minimum of the new range
* @param int $nMax The maximum of the new range
*
* @return float $fResult The old value converted to the new range
*/
function remapRange($intValue, $oMin, $oMax, $nMin, $nMax) {
// Range check
if ($oMin == $oMax) {
echo 'Warning: Zero input range';
return false;
}
if ($nMin == $nMax) {
echo 'Warning: Zero output range';
return false;
}
// Check reversed input range
$bReverseInput = false;
$intOldMin = min($oMin, $oMax);
$intOldMax = max($oMin, $oMax);
if ($intOldMin != $oMin) {
$bReverseInput = true;
}
// Check reversed output range
$bReverseOutput = false;
$intNewMin = min($nMin, $nMax);
$intNewMax = max($nMin, $nMax);
if ($intNewMin != $nMin) {
$bReverseOutput = true;
}
$fRatio = ($intValue - $intOldMin) * ($intNewMax - $intNewMin) / ($intOldMax - $intOldMin);
if ($bReverseInput) {
$fRatio = ($intOldMax - $intValue) * ($intNewMax - $intNewMin) / ($intOldMax - $intOldMin);
}
$fResult = $fRatio + $intNewMin;
if ($bReverseOutput) {
$fResult = $intNewMax - $fRatio;
}
return $fResult;
}
Dans la liste fournie par PenguinTD, je ne comprends pas pourquoi les plages sont inversées, cela fonctionne sans avoir à inverser les plages. La conversion de plage linéaire est basée sur l'équation linéaire Y=Xm+n
, où m
et n
sont dérivés des plages données. Plutôt que de faire référence aux plages comme min
et max
, il serait préférable de les désigner comme 1 et 2. La formule serait donc:
Y = (((X - x1) * (y2 - y1)) / (x2 - x1)) + y1
Où Y=y1
quand X=x1
et Y=y2
quand X=x2
. x1
, x2
, y1
& y2
peuvent recevoir n'importe quelle valeur positive
ou negative
. Définir l'expression dans une macro la rend plus utile, elle peut ensuite être utilisée avec n'importe quel nom d'argument.
#define RangeConv(X, x1, x2, y1, y2) (((float)((X - x1) * (y2 - y1)) / (x2 - x1)) + y1)
La conversion float
assurerait une division en virgule flottante dans le cas où tous les arguments sont des valeurs integer
. Selon l'application, il peut ne pas être nécessaire de vérifier les plages x1=x2
et y1==y2
.
Voici quelques courtes fonctions Python facilitant le copier-coller, notamment une fonction permettant de redimensionner toute une liste.
def scale_number(unscaled, to_min, to_max, from_min, from_max):
return (to_max-to_min)*(unscaled-from_min)/(from_max-from_min)+to_min
def scale_list(l, to_min, to_max):
return [scale_number(i, to_min, to_max, min(l), max(l)) for i in l]
Qui peut être utilisé comme suit:
scale_list([1,3,4,5], 0, 100)
[0,0, 50,0, 75,0, 100,0]
Dans mon cas, je voulais redimensionner une courbe logarithmique, comme ceci:
scale_list([math.log(i+1) for i in range(5)], 0, 50)
[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]
J'ai utilisé cette solution dans un problème que je résolvais en js, alors j'ai pensé partager la traduction. Merci pour l'explication et la solution.
function remap( x, oMin, oMax, nMin, nMax ){
//range check
if (oMin == oMax){
console.log("Warning: Zero input range");
return None;
};
if (nMin == nMax){
console.log("Warning: Zero output range");
return None
}
//check reversed input range
var reverseInput = false;
oldMin = Math.min( oMin, oMax );
oldMax = Math.max( oMin, oMax );
if (oldMin != oMin){
reverseInput = true;
}
//check reversed output range
var reverseOutput = false;
newMin = Math.min( nMin, nMax )
newMax = Math.max( nMin, nMax )
if (newMin != nMin){
reverseOutput = true;
};
var portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
if (reverseInput){
portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
};
var result = portion + newMin
if (reverseOutput){
result = newMax - portion;
}
return result;
}
Variante C++
J'ai trouvé la solution de Penguin ED utile, je l'ai donc portée en C++ si quelqu'un en avait besoin:
//range check if( oMin == oMax) { //std::cout<< "Warning: Zero input range"; return -1; } if( nMin == nMax){ //std::cout<<"Warning: Zero output range"; return -1; } //check reversed input range bool reverseInput = false; float oldMin = min( oMin, oMax ); float oldMax = max( oMin, oMax ); if (oldMin == oMin) reverseInput = true; //check reversed output range bool reverseOutput = false; float newMin = min( nMin, nMax ); float newMax = max( nMin, nMax ); if (newMin == nMin) reverseOutput = true; float portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin); if (reverseInput) portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin); float result = portion + newMin; if (reverseOutput) result = newMax - portion; return result; }
Cet exemple convertit la position actuelle d’un morceau dans une plage d’angle comprise entre 20 et 40.
/// <summary>
/// This test converts Current songtime to an angle in a range.
/// </summary>
[Fact]
public void ConvertRangeTests()
{
//Convert a songs time to an angle of a range 20 - 40
var result = ConvertAndGetCurrentValueOfRange(
TimeSpan.Zero, TimeSpan.FromMinutes(5.4),
20, 40,
2.7
);
Assert.True(result == 30);
}
/// <summary>
/// Gets the current value from the mixValue maxValue range.
/// </summary>
/// <param name="startTime">Start of the song</param>
/// <param name="duration"></param>
/// <param name="minValue"></param>
/// <param name="maxValue"></param>
/// <param name="value">Current time</param>
/// <returns></returns>
public double ConvertAndGetCurrentValueOfRange(
TimeSpan startTime,
TimeSpan duration,
double minValue,
double maxValue,
double value)
{
var timeRange = duration - startTime;
var newRange = maxValue - minValue;
var ratio = newRange / timeRange.TotalMinutes;
var newValue = value * ratio;
var currentValue= newValue + minValue;
return currentValue;
}
Voici une version Javascript qui renvoie une fonction qui redimensionne des plages source et cible prédéterminées, en minimisant la quantité de calculs à effectuer à chaque fois.
// This function returns a function bound to the
// min/max source & target ranges given.
// oMin, oMax = source
// nMin, nMax = dest.
function makeRangeMapper(oMin, oMax, nMin, nMax ){
//range check
if (oMin == oMax){
console.log("Warning: Zero input range");
return undefined;
};
if (nMin == nMax){
console.log("Warning: Zero output range");
return undefined
}
//check reversed input range
var reverseInput = false;
let oldMin = Math.min( oMin, oMax );
let oldMax = Math.max( oMin, oMax );
if (oldMin != oMin){
reverseInput = true;
}
//check reversed output range
var reverseOutput = false;
let newMin = Math.min( nMin, nMax )
let newMax = Math.max( nMin, nMax )
if (newMin != nMin){
reverseOutput = true;
}
// Hot-rod the most common case.
if (!reverseInput && !reverseOutput) {
let dNew = newMax-newMin;
let dOld = oldMax-oldMin;
return (x)=>{
return ((x-oldMin)* dNew / dOld) + newMin;
}
}
return (x)=>{
let portion;
if (reverseInput){
portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
} else {
portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin)
}
let result;
if (reverseOutput){
result = newMax - portion;
} else {
result = portion + newMin;
}
return result;
}
}
Voici un exemple d'utilisation de cette fonction pour mettre à l'échelle 0-1 en -0x80000000, 0x7FFFFFFF
let normTo32Fn = makeRangeMapper(0, 1, -0x80000000, 0x7FFFFFFF);
let fs = normTo32Fn(0.5);
let fs2 = normTo32Fn(0);
J'utilise personnellement la classe d'assistance qui prend en charge les génériques (compatible Swift 3)
struct Rescale<Type : BinaryFloatingPoint> {
typealias RescaleDomain = (lowerBound: Type, upperBound: Type)
var fromDomain: RescaleDomain
var toDomain: RescaleDomain
init(from: RescaleDomain, to: RescaleDomain) {
self.fromDomain = from
self.toDomain = to
}
func interpolate(_ x: Type ) -> Type {
return self.toDomain.lowerBound * (1 - x) + self.toDomain.upperBound * x;
}
func uninterpolate(_ x: Type) -> Type {
let b = (self.fromDomain.upperBound - self.fromDomain.lowerBound) != 0 ? self.fromDomain.upperBound - self.fromDomain.lowerBound : 1 / self.fromDomain.upperBound;
return (x - self.fromDomain.lowerBound) / b
}
func rescale(_ x: Type ) -> Type {
return interpolate( uninterpolate(x) )
}
}
color_array_new = [int((((x - min(node_sizes)) * 99) / (max(node_sizes) - min(node_sizes))) + 1) for x in node_sizes]
def colour_specter(waste_amount):
color_array = []
OldRange = max(waste_amount) - min(waste_amount)
NewRange = 99
for number_value in waste_amount:
NewValue = int((((number_value - min(waste_amount)) * NewRange) / OldRange) + 1)
color_array.append(NewValue)
print(color_array)
return color_array
Proposition simplifiée
NewRange/OldRange = Handy multiplicand or HM
Convert OldValue in OldRange to NewValue in NewRange =
(OldValue - OldMin x HM) + NewMin
wayne