Converti un intervallo di numeri in un altro intervallo, mantenendo il rapporto


257

Sto cercando di convertire un intervallo di numeri in un altro, mantenendo il rapporto. La matematica non è il mio punto di forza.

Ho un file di immagine in cui i valori dei punti possono variare da -16000,00 a 16000,00 sebbene l'intervallo tipico possa essere molto inferiore. Quello che voglio fare è comprimere questi valori nell'intervallo intero 0-100, dove 0 è il valore del punto più piccolo e 100 è il valore del più grande. Tutti i punti in mezzo dovrebbero mantenere un rapporto relativo anche se si sta perdendo un po 'di precisione, mi piacerebbe farlo in Python ma anche un algoritmo generale dovrebbe essere sufficiente. Preferirei un algoritmo in cui è possibile regolare il min / max o entrambi gli intervalli (ovvero, il secondo intervallo potrebbe essere compreso tra -50 e 800 anziché tra 0 e 100).


Grazie ad entrambi, sto dando la risposta a Cletus perché è arrivato primo e ha fatto +1 su Jerry per aver risposto al mio follow-up.
SpliFF,

2
vero scusa cletus, lo sto dando a Jerry perché è nuovo e ha bisogno di punti.
SpliFF,

2
Ehi, questo è età! Heheh, j / k, nessuna preoccupazione. :)
cletus,

7
In che modo questa domanda è sfuggita alla brigata di chiuditori di domande di StackOverflow? ;)
thanikkal,

Risposte:


537
NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin

O un po 'più leggibile:

OldRange = (OldMax - OldMin)  
NewRange = (NewMax - NewMin)  
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin

O se vuoi proteggere il caso in cui il vecchio intervallo è 0 ( OldMin = OldMax ):

OldRange = (OldMax - OldMin)
if (OldRange == 0)
    NewValue = NewMin
else
{
    NewRange = (NewMax - NewMin)  
    NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
}

Si noti che in questo caso siamo costretti a scegliere arbitrariamente uno dei possibili nuovi valori di intervallo. A seconda del contesto, le scelte sensate potrebbero essere: NewMin( vedi esempio ) NewMaxo(NewMin + NewMax) / 2


oldMax deve essere 16000 o può essere il valore più alto nel set di punti precedente (diciamo, ad esempio, 15034,00) la distinzione è importante?
SpliFF,

5
Puoi farlo tutto quello che vuoi ... tieni presente che potresti ottenere strani risultati se uno degli intervalli è molto piccolo rispetto all'altro (non esattamente sicuro, ma se c'è più di una differenza di fattore 1000000 tra le dimensioni di le gamme, assicurati che si comporti davvero come ti aspetti ... o impari sull'imprecisione in virgola mobile)
jerryjvl,

2
Considerando la popolarità di questa risposta, per un caso più generale dovresti considerare la possibilità di OldMax == OldMin, che potrebbe comportare una divisione per zero.
utente

3
Questo e spettacolare. C'è un nome matematico per questa conversione?
Tarik,

2
Si chiama conversione lineare, @Tarik
Rodrigo Borba

65

Questa è una semplice conversione lineare.

new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min

Quindi convertendo 10000 sulla scala da -16000 a 16000 in una nuova scala da 0 a 100 rendimenti:

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

2
Questo è sbagliato. È necessario sottrarre Old Min da Old Value prima della divisione.
SPWorley,

20

In realtà ci sono alcuni casi in cui le risposte di cui sopra si infrangono. Come valore di input errato, intervallo di input errato, intervalli di input / output negativi.

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

9

C'è una condizione, quando tutti i valori che stai controllando sono gli stessi, in cui il codice di @ jerryjvl restituisce NaN.

if (OldMin != OldMax && NewMin != NewMax):
    return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
    return (NewMax + NewMin) / 2

5

Non ho scavato il BNF per questo, ma la documentazione di Arduino aveva un ottimo esempio della funzione e del suo guasto. Sono stato in grado di usarlo in Python semplicemente aggiungendo una ridenominazione def per rimappare (perché la mappa è un built-in) e rimuovendo i cast di tipo e le parentesi graffe (cioè semplicemente rimuovendo tutti i "long").

Originale

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

Pitone

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

https://www.arduino.cc/en/reference/map


2

Nell'elenco fornito da PenguinTD, non capisco perché gli intervalli sono invertiti, funziona senza dover invertire gli intervalli. La conversione dell'intervallo lineare si basa sull'equazione lineare Y=Xm+n, dove me nsono derivati ​​dagli intervalli dati. Invece di fare riferimento agli intervalli come mine max, sarebbe meglio fare riferimento a loro come 1 e 2. Quindi la formula sarebbe:

Y = (((X - x1) * (y2 - y1)) / (x2 - x1)) + y1

Dove Y=y1quando X=x1e Y=y2quando X=x2. x1, x2, y1E y2può essere dato qualsiasi positiveo di negativevalore. La definizione dell'espressione in una macro la rende più utile, quindi può essere utilizzata con qualsiasi nome di argomento.

#define RangeConv(X, x1, x2, y1, y2) (((float)((X - x1) * (y2 - y1)) / (x2 - x1)) + y1)

Il floatcast assicurerebbe la divisione in virgola mobile nel caso in cui tutti gli argomenti siano integervalori. A seconda dell'applicazione potrebbe non essere necessario controllare gli intervalli x1=x2e y1==y2.


Grazie! ecco la conversione C #: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair,

2

Ecco alcune brevi funzioni di Python per la facilità di copia e incolla, tra cui una funzione per ridimensionare un intero elenco.

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]

Quale può essere usato così:

scale_list([1,3,4,5], 0, 100)

[0.0, 50.0, 75.0, 100.0]

Nel mio caso volevo ridimensionare una curva logaritmica, in questo modo:

scale_list([math.log(i+1) for i in range(5)], 0, 50)

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]


1

Ho usato questa soluzione in un problema che stavo risolvendo in js, quindi ho pensato di condividere la traduzione. Grazie per la spiegazione e la soluzione.

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

grazie! soluzione fantastica e impostata come funzione pronta all'uso!
Combatti Fire With Fire il

1

Variante C ++

Ho trovato utile la soluzione di PenguinTD, quindi l'ho portata su C ++ se qualcuno ne ha bisogno:

rimappa float (float x, float oMin, float oMax, float nMin, float nMax) {

//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; }

1

Porta PHP

Ho trovato utile la soluzione di PenguinTD, quindi l'ho portata su PHP. Aiuta te stesso!

/**
* =====================================
*              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;
}

1

Ecco una versione Javascript che restituisce una funzione che esegue il riscaling per intervalli di origine e destinazione predeterminati, riducendo al minimo la quantità di calcolo che deve essere eseguito ogni volta.

// 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;
    }   
}

Ecco un esempio dell'uso di questa funzione per ridimensionare 0-1 in -0x80000000, 0x7FFFFFFF

let normTo32Fn = makeRangeMapper(0, 1, -0x80000000, 0x7FFFFFFF);
let fs = normTo32Fn(0.5);
let fs2 = normTo32Fn(0);

0

Scorciatoia / proposta semplificata

 NewRange/OldRange = Handy multiplicand or HM
 Convert OldValue in OldRange to NewValue in NewRange = 
 (OldValue - OldMin x HM) + NewMin

Wayne


1
Cosa c'è NewRange/OldRangequi?
Zunair,

0

Personalmente uso la classe helper che supporta generics (compatibile con 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) )
    }
}

0

In questo esempio viene convertita la posizione corrente di un brano in un intervallo angolare compreso tra 20 e 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;
    }

0

Elenco comprensione una soluzione di rivestimento

color_array_new = [int((((x - min(node_sizes)) * 99) / (max(node_sizes) - min(node_sizes))) + 1) for x in node_sizes]

Versione più lunga

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

0

Versione Java

Funziona sempre, non importa cosa lo dai da mangiare!

Ho lasciato tutto espanso in modo che sia più facile da seguire per l'apprendimento. L'arrotondamento alla fine, ovviamente, è facoltativo.

    private long remap(long p, long Amin, long Amax, long Bmin, long Bmax ) {

    double deltaA = Amax - Amin;
    double deltaB = Bmax - Bmin;
    double scale  = deltaB / deltaA;
    double negA   = -1 * Amin;
    double offset = (negA * scale) + Bmin;
    double q      = (p * scale) + offset;
    return Math.round(q);

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.