Преобразовать диапазон номеров в другой диапазон, сохраняя соотношение


257

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

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


Спасибо вам обоим, я даю ответ Клетусу, потому что он вошел первым и +1 к Джерри за ответы на мои вопросы.
SpliFF

2
На самом деле извините, я отдаю это Джерри, потому что он новичок и нуждается в очках.
SpliFF

2
Эй, это эйджизм! Хехех, J / K, не беспокойтесь. :)
Клет

7
Как этот вопрос избежал бригады доводчиков вопросов, связанных со стеком потока? ;)
Таниккал

Ответы:


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

питон

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 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здесь?
Zunair

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.