Перетворити діапазон чисел в інший діапазон, зберігаючи відношення


256

Я намагаюся перетворити один діапазон чисел в інший, зберігаючи відношення. Математика не є моїм сильним моментом.

У мене є файл зображення, в якому значення точок можуть бути від -16000,00 до 16000,00, хоча типовий діапазон може бути набагато меншим. Що я хочу зробити, це стиснути ці значення в цілий діапазон 0-100, де 0 - найменша точка, а 100 - найбільша. Усі точки між ними повинні зберігати відносне співвідношення, хоча втрачається якась точність, я б хотів це зробити в python, але навіть загального алгоритму повинно вистачити. Я вважаю за краще алгоритм, в якому мінімальний / максимальний або будь-який діапазон можна регулювати (тобто другий діапазон може бути від -50 до 800 замість 0 до 100).


Дякую вам обом, я даю відповідь клету, бо він потрапив першим та +1 до Джеррі за відповідь на моє подання.
SpliFF

2
фактично вибачте клету, я даю його Джері, тому що він новий і потребує очок.
SpliFF

2
Гей, це вік! Хе-хе, д / к, турбот немає. :)
клетус

7
Як це питання вдалося уникнути бригади закривачів запитань стаквоверф? ;)
thanikkal

Відповіді:


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

Або трохи читабельніше:

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

Або якщо ви хочете захистити для випадку, коли старий діапазон дорівнює 0 ( OldMin = OldMax ):

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

Зауважте, що в цьому випадку ми змушені добирати одне з можливих нових значень діапазону довільно. Залежно від контексту, розумним вибором може бути: NewMin( див. Зразок ), NewMaxабо(NewMin + NewMax) / 2


чи має бути oldMax 16000 або це може бути найвищим значенням у старому наборі точок (скажімо, 15034,00, наприклад), важлива відмінність?
SpliFF

5
Ви можете зробити все, що завгодно ... майте на увазі, що ви можете отримати дивні результати, якщо один з діапазонів дуже малий по відношенню до іншого (не зовсім точно, але якщо між розмірами коефіцієнта 1000000 різниця більше діапазони, переконайтеся, що він насправді веде себе так, як ви очікували ... або дізнайтеся про неточність з плаваючою комою)
jerryjvl

2
Враховуючи популярність цієї відповіді, для більш загального випадку слід розглянути можливість OldMax == OldMin, що може призвести до поділу на нуль.
користувач

3
Це круто. Чи є математична назва для цієї конверсії?
Тарік

2
Це називається лінійним перетворенням, @Tarik
Родріго Борба

65

Це просте лінійне перетворення.

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

Отже, перетворення 10000 за шкалою від -16000 до 16000 до нової шкали від 0 до 100 врожайності:

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
Це неправильно. Перед діленням потрібно відняти Old Min від Old Value.
SPWorley

20

Насправді є деякі випадки, коли вищезгадані відповіді зламаються. Такі як неправильно введене значення, неправильно введений діапазон, негативні діапазони введення / виводу.

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

Існує умова, коли всі значення, які ви перевіряєте, однакові, де код @ jerryjvl повертав би NaN.

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

5

Я не копав BNF для цього, але в документації на Arduino був чудовий приклад функції, і це злом. Мені вдалося використати це в Python, просто додавши def перейменування до перенастроювання (причина карти є вбудованою) та видалення типів кастів та фігурних брекетів (тобто просто видалити всі 'long').

Оригінал

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

Пітон

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

У лістингу, наданому PenguinTD, я не розумію, чому діапазони обернені, він працює, не змінюючи діапазон. Перетворення лінійного діапазону базується на лінійному рівнянні Y=Xm+n, де mі nпоходять із заданих діапазонів. Замість того, щоб посилатися на діапазони як minі max, було б краще позначати їх як 1 і 2. Отже, формула буде такою:

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

Де Y=y1коли X=x1і Y=y2коли X=x2. x1, x2, y1& y2Може бути дано будь positiveабо negativeзначення. Визначення виразу в макросі робить його більш корисним, він може бути використаний з будь-якими іменами аргументів.

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

floatЛитий забезпечать точку ділення з плаваючим в тому випадку , якщо всі аргументи мають integerзначення. Залежно від програми, можливо, не потрібно перевіряти діапазони x1=x2та y1==y2.


Дякую! ось конверсія C #: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair

2

Ось кілька коротких функцій Python для зручності копіювання та вставки, включаючи функцію масштабування всього списку.

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]

Які можна використовувати так:

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

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

У моєму випадку я хотів масштабувати логарифмічну криву так:

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

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]


1

Я використовував це рішення у проблемі, яку я вирішував у js, тому думав, що поділюсь перекладом. Дякуємо за пояснення та рішення.

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

спасибі! приголомшливе рішення та встановлено як функцію, готову до роботи!
Боротьба з вогнем

1

C ++ варіант

Я знайшов рішення PenguinTD корисним, тому переніс його на C ++, якщо комусь це потрібно:

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

PHP-порт

Знайшов рішення PenguinTD корисним, тому я переніс його на PHP. Допоможи собі!

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

Ось версія Javascript, яка повертає функцію, яка робить масштабування заздалегідь визначеними діапазонами джерела та місця призначення, мінімізуючи кількість обчислень, які потрібно робити щоразу.

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

Ось приклад використання цієї функції для масштабування 0-1 до -0x80000000, 0x7FFFFFFF

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

0

Пропозиція для скорочення / спрощення

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

Вейн


1
Що NewRange/OldRangeтут?
Зунайр

0

Я особисто використовую клас помічників, який підтримує дженерики (сумісний з 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

Цей приклад перетворює поточне положення пісень у кутовий діапазон 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

Перерахуйте розуміння одного лайнерного рішення

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

0

Версія Java

Завжди працює незалежно від того, чим ви його годуєте!

Я залишив усе розширене, щоб легше було слідкувати за навчанням. Закруглення в кінці, звичайно, необов’язкове.

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

}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.