Konwertuj zakres liczb na inny, zachowując współczynnik


256

Próbuję przekonwertować jeden zakres liczb na inny, zachowując stosunek. Matematyka nie jest moją mocną stroną.

Mam plik obrazu, w którym wartości punktowe mogą wynosić od -16000,00 do 16000,00, chociaż typowy zakres może być znacznie mniejszy. Chcę skompresować te wartości do zakresu liczb całkowitych 0-100, gdzie 0 to wartość najmniejszego punktu, a 100 to wartość największego. Wszystkie punkty pośrednie powinny zachować względny stosunek, nawet pomimo utraty pewnej precyzji. Chciałbym to zrobić w Pythonie, ale nawet ogólny algorytm powinien wystarczyć. Wolałbym algorytm, w którym można dostosować min / maks lub dowolny zakres (tj. Drugi zakres może wynosić od -50 do 800 zamiast od 0 do 100).


Dziękuję obojgu, daję odpowiedź Cletusowi, ponieważ on wszedł pierwszy i +1, aby się rozerwać za odpowiedź na moje dalsze działania.
SpliFF

2
Naprawdę przepraszam Cletus, daję to Jerry, ponieważ jest nowy i potrzebuje punktów.
SpliFF

2
Hej, to jest ageizm! Heheh, j / k, nie martw się. :)
cletus

7
W jaki sposób to pytanie umknęło brygadzie osób zamykających stosy pytań? ;)
thanikkal

Odpowiedzi:


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

Lub trochę bardziej czytelny:

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

Lub jeśli chcesz chronić w przypadku, gdy stary zakres wynosi 0 ( OldMin = OldMax ):

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

Zauważ, że w tym przypadku jesteśmy zmuszeni arbitralnie wybrać jedną z możliwych nowych wartości zakresu. W zależności od kontekstu rozsądnymi wyborami mogą być: NewMin( patrz próbka ) NewMaxlub(NewMin + NewMax) / 2


czy oldMax musi mieć wartość 16000, czy może być najwyższą wartością w starym zestawie punktów (np. 15034.00), czy rozróżnienie jest ważne?
SpliFF

5
Możesz zrobić wszystko, co chcesz ... pamiętaj, że możesz uzyskać dziwne wyniki, jeśli jeden z zakresów jest bardzo mały w stosunku do drugiego (nie do końca pewny, ale jeśli różnica między wielkością wynoszącą 1000000 jest większa zakresy, upewnij się, że faktycznie zachowuje się tak, jak się spodziewasz ... lub dowiedz się o niedokładności zmiennoprzecinkowej)
jerryjvl

2
Biorąc pod uwagę popularność tej odpowiedzi, w bardziej ogólnym przypadku należy rozważyć możliwość OldMax == OldMin, która może skutkować dzieleniem przez zero.
użytkownik

3
To jest niesamowite. Czy istnieje nazwa matematyczna tej konwersji?
Tarik

2
Nazywa się to konwersją liniową @Tarik
Rodrigo Borba

65

To prosta konwersja liniowa.

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

Tak więc konwersja 10000 w skali od -16000 do 16000 do nowej skali od 0 do 100 daje:

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
To jest źle. Musisz odjąć Old Min od Old Value przed podziałem.
SPWorley

20

W rzeczywistości istnieją przypadki, w których powyższe odpowiedzi byłyby zepsute. Takich jak błędnie wprowadzona wartość, błędnie wprowadzony zakres, ujemne zakresy wejścia / wyjścia.

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

Istnieje warunek, gdy wszystkie wartości, które sprawdzasz, są takie same, a kod @ jerryjvl zwróci NaN.

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

5

Nie wykopałem w tym celu BNF , ale dokumentacja Arduino miała świetny przykład tej funkcji i jej awarii. Byłem w stanie użyć tego w Pythonie, po prostu dodając zmianę nazwy def do mapowania (ponieważ mapa jest wbudowana) i usuwając rzutowania typu i nawiasy klamrowe (tj. Po prostu usuwam wszystkie długie).

Oryginalny

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

Pyton

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

W zestawieniu dostarczonym przez PenguinTD nie rozumiem, dlaczego zakresy są odwrócone, działa bez konieczności odwracania zakresów. Liniowa konwersja zakresu opiera się na równaniu liniowym Y=Xm+n, gdzie mi nsą uzyskiwane z podanych zakresów. Zamiast odwoływać się do zakresów jako mini max, lepiej byłoby odwoływać się do nich jako 1 i 2. Zatem wzór byłby następujący:

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

Gdzie, Y=y1kiedy X=x1i Y=y2kiedy X=x2. x1, x2, y1I y2można nadać dowolny positivelub negativewartość. Zdefiniowanie wyrażenia w makrze sprawia, że ​​jest ono bardziej użyteczne, można go zatem używać z dowolnymi nazwami argumentów.

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

floatObsada zapewniłoby zmiennoprzecinkowych podział w przypadku, gdy wszystkie argumenty są integerwartości. W zależności od zastosowania może nie być konieczne sprawdzenie zakresów x1=x2i y1==y2.


Dzięki! tutaj jest konwersja C #: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair

2

Oto kilka krótkich funkcji Pythona dla ułatwienia kopiowania i wklejania, w tym funkcja skalowania całej listy.

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]

Które można wykorzystać tak:

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

[0,0, 50,0, 75,0, 100,0]

W moim przypadku chciałem przeskalować krzywą logarytmiczną, tak:

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

[0,0, 21,533827903669653, 34.130309724299266, 43.06765580733931, 50,0]


1

Użyłem tego rozwiązania w problemie, który rozwiązałem w js, więc pomyślałem, że podzielę się tłumaczeniem. Dzięki za wyjaśnienie i rozwiązanie.

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

Dziękuję Ci! niesamowite rozwiązanie i gotowe do pracy jako funkcja!
Fight Fire With Fire

1

Wariant C ++

Uważam, że rozwiązanie PenguinTD jest przydatne, więc przeniosłem je do C ++, jeśli ktoś tego potrzebuje:

float remap (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

Port PHP

Uznałem, że rozwiązanie PenguinTD jest pomocne, więc przeniosłem je na PHP. Pomóż sobie!

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

Oto wersja Javascript, która zwraca funkcję, która wykonuje przeskalowanie dla z góry określonych zakresów źródłowych i docelowych, minimalizując ilość obliczeń, które należy wykonać za każdym razem.

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

Oto przykład użycia tej funkcji do skalowania 0-1 do -0x80000000, 0x7FFFFFFF

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

0

Wniosek skrócony / uproszczony

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

Wayne


1
Co NewRange/OldRangetu jest
Zunair,

0

Osobiście używam klasy pomocnika, która obsługuje generyczne (kompatybilny z 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

Ten przykład przekształca bieżącą pozycję utworu w zakres kątów 20–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

Zrozumienie listy jedno rozwiązanie liniowe

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

Dłuższa wersja

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

Wersja Java

Zawsze działa bez względu na to, co go karmisz!

Zostawiłem wszystko rozszerzone, aby łatwiej było podążać za nauką. Zaokrąglanie na końcu jest oczywiście opcjonalne.

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

}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.