Преобразовать диапазон чисел в другой диапазон, поддерживая коэффициент
Я пытаюсь преобразовать один диапазон чисел в другой, поддерживая коэффициент. Математика не моя сильная сторона.
У меня есть файл изображения, где значения точек могут варьироваться от -16000.00 до 16000.00, хотя типичный диапазон может быть намного меньше. Я хочу сжать эти значения в целочисленный диапазон 0-100, где 0 - значение наименьшей точки, а 100 - значение самого большого. Все точки между ними должны поддерживать относительное соотношение, даже если теряется некоторая точность. Я бы хотел сделать это на питоне, но достаточно общего алгоритма. Я бы предпочел алгоритм, в котором можно отрегулировать мин/макс или любой диапазон (т.е. Второй диапазон может быть от -50 до 800 вместо 0 до 100).
Ответы
Ответ 1
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
Ответ 2
Это простое линейное преобразование.
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
Ответ 3
На самом деле есть некоторые случаи, когда выше ответы сломались.
Например, неправильное входное значение, неправильный диапазон ввода, отрицательные диапазоны ввода/вывода.
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
Ответ 4
Существует условие, когда все значения, которые вы проверяете, одинаковы, где код @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 renaming для переназначения (потому что карта является встроенной) и удаление типов бросков и фигурных скобок (т.е. просто удалить все "long's" ).
Оригинал
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
Ответ 6
В листинге, представленном 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
.
Ответ 7
Порт 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;
}
Ответ 8
Я использовал это решение в задаче, которую я решал в 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;
}
Ответ 9
Вариант С++
Я нашел решение PenguinTD полезным, поэтому я портировал его на С++, если кому-то это нужно:
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; }
Ответ 10
Здесь есть несколько коротких функций 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]
Ответ 11
Сокращенное/упрощенное предложение
NewRange/OldRange = Handy multiplicand or HM
Convert OldValue in OldRange to NewValue in NewRange =
(OldValue - OldMin x HM) + NewMin
уэйн
Ответ 12
Я лично использую класс-помощник, который поддерживает generics (совместимый с 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) )
}
}
Ответ 13
Этот пример преобразует текущую позицию песни в диапазон углов 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;
}
Ответ 14
Вот версия 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);
Ответ 15
Понимание списка одним вкладышем решения
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