Преобразовать диапазон номеров в другой диапазон, сохраняя соотношение - PullRequest
219 голосов
/ 30 мая 2009

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

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

Ответы [ 14 ]

439 голосов
/ 30 мая 2009
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

58 голосов
/ 30 мая 2009

Это простое линейное преобразование.

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
20 голосов
/ 21 марта 2013

На самом деле, есть некоторые случаи, когда вышеуказанные ответы будут нарушены. Например, неверно введенное значение, неверно введенный диапазон, отрицательные диапазоны ввода / вывода.

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 голосов
/ 10 июля 2012

Существует условие, когда все проверяемые значения совпадают, когда код @ jerryjvl будет возвращать NaN.

if (OldMin != OldMax && NewMin != NewMax):
    return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
    return (NewMax + NewMin) / 2
3 голосов
/ 23 апреля 2017

Я не выкопал BNF для этого, но в документации Arduino был отличный пример функции, и она разбита. Я смог использовать это в Python, просто добавив def переименование в remap (потому что map является встроенным) и удалив приведение типов и фигурные скобки (то есть просто удалил все '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;
}

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

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

2 голосов
/ 27 марта 2016

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;
}
2 голосов
/ 16 декабря 2014

В листинге, представленном 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.

1 голос
/ 07 февраля 2017

Вот несколько коротких функций 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 голос
/ 26 ноября 2014

C ++ вариант

Я нашел решение PenguinTD полезным, поэтому я перенес его на C ++, если кому-то это нужно:

повторное отображение числа с плавающей точкой (число с плавающей запятой x, число с плавающей запятой oMin, число с плавающей запятой oMax, число с плавающей запятой nMin, число с плавающей запятой 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 голос
/ 09 июля 2014

Я использовал это решение в проблеме, которую решал в 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...