Converter um intervalo de números de outro intervalo, mantendo proporção
Pergunta
Eu estou tentando converter um intervalo de números para outro, mantendo a relação. Matemática não é o meu ponto forte.
Eu tenho um arquivo de imagem, onde os valores dos pontos pode variar -16.000,00-16000,00 embora a gama típica pode ser muito menos. O que eu quero fazer é compactar esses valores para o intervalo inteiro 0-100, em que 0 é o valor do menor ponto, e 100 é o valor do maior. Todos os pontos entre eles deve manter uma proporção relativa, embora alguma precisão está sendo perdida Eu gostaria de fazer isso em python, mas mesmo um algoritmo geral deve ser suficiente. I preferir um algoritmo onde o min / max ou qualquer intervalo pode ser ajustado (ou seja, o segundo intervalo poderia ser de -50 a 800, em vez de 0 a 100).
Solução
NewValue = (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
Ou um pouco mais legível:
OldRange = (OldMax - OldMin)
NewRange = (NewMax - NewMin)
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
Ou se você deseja proteger para o caso onde o velho intervalo é de 0 ( OldMin = OldMax ):
OldRange = (OldMax - OldMin)
if (OldRange == 0)
NewValue = NewMin
else
{
NewRange = (NewMax - NewMin)
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
}
Note que, neste caso somos forçados a escolher um dos valores possíveis nova gama arbitrariamente. Dependendo do contexto, escolhas sensatas poderia ser: NewMin
( ver amostra ), NewMax
ou (NewMin + NewMax) / 2
Outras dicas
Isso é uma conversão linear simples.
new_value = ( (old_value - old_min) / (old_max - old_min) ) * (new_max - new_min) + new_min
Assim, convertendo 10000 na escala de -16.000-16000 a uma nova escala de 0 a 100 rendimentos:
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
Na verdade, existem alguns casos que as respostas acima iria quebrar. Tal como valor de entrada erradamente, erradamente gama de entrada, entrada negativa / faixas de saída.
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
Há uma condição, quando todos os valores que você está visitando são os mesmos, onde @ de jerryjvl código voltaria NaN.
if (OldMin != OldMax && NewMin != NewMax):
return (((OldValue - OldMin) * (NewMax - NewMin)) / (OldMax - OldMin)) + NewMin
else:
return (NewMax + NewMin) / 2
Eu não cavar o BNF para isso, mas a documentação Arduino teve um grande exemplo da função e é avaria. Eu era capaz de usar isso em Python, simplesmente adicionando um def renomeando para remapear (causa mapa é um built-in) e remover os moldes de tipo e chaves (ou seja, apenas remover todos os 'long' s).
Original
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
Na listagem fornecida pelo PenguinTD, eu não entendo por que os intervalos são invertidos, ele funciona sem a necessidade de reverter os intervalos. conversão gama linear baseia-se no Y=Xm+n
equação linear, onde m
e n
são derivados a partir das gamas indicadas. Ao invés de se referir às faixas como min
e max
, seria melhor para se referir a eles como 1 e 2. Assim, a fórmula seria:
Y = (((X - x1) * (y2 - y1)) / (x2 - x1)) + y1
Onde Y=y1
quando X=x1
e Y=y2
quando X=x2
. x1
, x2
, y1
& y2
pode ser dado qualquer valor positive
ou negative
. Definindo a expressão em uma macro faz com que seja mais útil, então ele pode ser usado com qualquer nomes de argumentos.
#define RangeConv(X, x1, x2, y1, y2) (((float)((X - x1) * (y2 - y1)) / (x2 - x1)) + y1)
O elenco float
garantiria flutuante divisão ponto no caso em que todos os argumentos são valores integer
.
Dependendo da aplicação, pode não ser necessário verificar a x1=x2
faixas e y1==y2
.
PHP Porto
Encontrado solução de PenguinTD útil para que eu portou para PHP. Ajuda você mesmo!
/**
* =====================================
* 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;
}
Eu usei esta solução em um problema que eu estava resolvendo em js, então eu pensei que eu iria compartilhar a tradução. Obrigado pela explicação e solução.
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;
}
C ++ Variant
Eu encontrei útil Solução de PenguinTD, então eu portado para C ++, se alguém precisa dele:
flutuador remapeamento (float x, float Omin, flutuar OMAX, flutuar nMin, flutuar 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; }
Aqui estão algumas funções Python curtas para a sua cópia e colar facilidade, incluindo uma função de escalar uma lista inteira.
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]
Que pode ser usado assim:
scale_list([1,3,4,5], 0, 100)
[0,0, 50,0, 75,0, 100,0]
No meu caso eu queria escalar uma curva logarítmica, assim:
scale_list([math.log(i+1) for i in range(5)], 0, 50)
[0.0, 21,533827903669653, 34,130309724299266, 43,06765580733931, 50,0]
Short-cut / proposta simplificada
NewRange/OldRange = Handy multiplicand or HM
Convert OldValue in OldRange to NewValue in NewRange =
(OldValue - OldMin x HM) + NewMin
wayne
Eu, pessoalmente usar a classe auxiliar que suporta genéricos (SWIFT 3 compatível)
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) )
}
}
Este exemplo converte um canções posição atual em uma faixa de ângulo de 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;
}
Aqui está uma versão JavaScript que retorna uma função que faz o redimensionamento para intervalos de origem e de destino predeterminados, minimizando a quantidade de computação que tem que ser feito cada vez.
// 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;
}
}
Aqui está um exemplo de como usar essa função para escala de 0-1 em -0x80000000, 0x7FFFFFFF
let normTo32Fn = makeRangeMapper(0, 1, -0x80000000, 0x7FFFFFFF);
let fs = normTo32Fn(0.5);
let fs2 = normTo32Fn(0);
Lista compreensão solução um forro
color_array_new = [int((((x - min(node_sizes)) * 99) / (max(node_sizes) - min(node_sizes))) + 1) for x in node_sizes]
versão mais longa
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