Oranı koruyarak bir sayı aralığını başka bir aralığa dönüştürme


258

Oranı koruyarak, bir sayı aralığını diğerine dönüştürmeye çalışıyorum. Matematik benim güçlü yanım değil.

Nokta değerleri -16000.00 16000.00 arasında değişebilir bir görüntü dosyası var, ancak tipik aralık çok daha az olabilir. Ne yapmak istiyorum bu değerleri 0-100 tamsayı aralığında sıkıştırmak, burada 0 en küçük noktanın değeri ve 100 en büyük değerin değeridir. Bazı hassasiyetler kaybolsa bile, aradaki tüm noktalar göreceli bir oran tutmalıdır Bunu python'da yapmak istiyorum, ancak genel bir algoritma bile yeterli olmalıdır. Min / maks veya her iki aralığın da ayarlanabileceği bir algoritmayı tercih ederim (yani, ikinci aralık 0 ila 100 yerine -50 ila 800 olabilir).


İkinize de teşekkür ederim, cletus'a cevap veriyorum çünkü ilk önce var ve takipçiliğimi cevapladığı için jerry'ye +1.
SpliFF

2
gerçek üzgünüm cletus, jerry'ye veriyorum çünkü yeni ve puanlara ihtiyacı var.
SpliFF

2
Hey bu yaşlanma! Heheh, j / k, endişelenme. :)
cletus

7
Bu soru stackoverflow soru kapan tugayı nasıl kaçtı? ;)
thanikkal

Yanıtlar:


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

Veya biraz daha okunabilir:

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

Veya eski aralığın 0 olduğu durumda korumak istiyorsanız ( OldMin = OldMax ):

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

Bu durumda, olası yeni aralık değerlerinden birini keyfi olarak seçmek zorunda kaldığımızı unutmayın. Bağlama bağlı olarak, makul seçenekler şunlar olabilir: NewMin( örneğe bakın ) NewMaxveya(NewMin + NewMax) / 2


oldMax'ın 16000 olması gerekiyor mu yoksa eski nokta kümesindeki en yüksek değer olabilir mi (örneğin, 15034.00) ayrım önemli mi?
SpliFF

5
İstediğiniz her şeyi yapabilirsiniz ... aralıklardan biri diğerine göre çok küçükse garip sonuçlar elde edebileceğinizi unutmayın (tam olarak emin değilim, ancak boyutu arasında 1000000'den fazla faktör farkı varsa aralıklar, aslında beklediğiniz gibi davrandığından emin olun ... veya kayan nokta yanlışlığı hakkında bilgi edinin)
jerryjvl

2
Bu cevabın popülaritesi göz önüne alındığında, daha genel bir durum için, sıfıra bölünmeye neden olabilecek OldMax == OldMin olasılığını göz önünde bulundurmalısınız.
kullanıcı

3
Bu harika. Bu dönüşüm için bir matematik adı var mı?
Tarık

2
Buna doğrusal dönüşüm denir, @Tarik
Rodrigo Borba

65

Bu basit bir doğrusal dönüşümdür.

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

Yani 10000'i -16000'den 16000'e dönüştürerek 0'dan 100'e kadar yeni bir ölçeğe dönüştürmek:

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
Bu yanlış. Bölmeden önce Eski Min'i Eski Değer'den çıkarmanız gerekir.
SPWorley

20

Aslında yukarıdaki cevapların kırılacağı bazı durumlar vardır. Yanlış giriş değeri, yanlış giriş aralığı, negatif giriş / çıkış aralıkları gibi.

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

Kontrol ettiğiniz tüm değerler aynı olduğunda, @ jerryjvl kodunun NaN döndüreceği bir koşul vardır.

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

5

Ben kazıp vermedi BNF , ancak Arduino belgelerinin işlev için harika bir örneği vardı ve arıza. Ben sadece yeniden tanımlamak için bir yeniden adlandırma (harita yerleşik bir neden olur) ve tür atmalarını ve kıvırcık parantez kaldırarak (yani sadece tüm 'uzun' kaldırmak) bir Python kullanabilirsiniz.

orijinal

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

piton

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 tarafından sağlanan listede, aralıkların neden ters çevrildiğini anlamıyorum, aralıkları tersine çevirmek zorunda kalmadan çalışır. Doğrusal aralığını dönüşüm lineer denklem üzerine dayanır Y=Xm+n, mve nverilen aralıklar elde edilir. Aralıkları minve maxolarak belirtmek yerine, bunlara 1 ve 2 olarak başvurmak daha iyi olur. Yani formül şöyle olur:

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

Nerede Y=y1ne zaman X=x1ve Y=y2ne zaman X=x2. x1, x2, y1Ve y2herhangi bir şekilde olabilir positiveya da negativedeğeri. İfadeyi bir makroda tanımlamak onu daha kullanışlı hale getirir, daha sonra herhangi bir argüman adıyla kullanılabilir.

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

floatDökme tüm argümanlar vardır durumunda noktası bölümü yüzen sağlayacak integerdeğerler. Uygulamaya bağlı olarak aralıkları kontrol etmek gerekli olmayabilir x1=x2ve y1==y2.


Teşekkürler! İşte C # dönüşümü: float RangeConv(float input, float x1, float x2, float y1, float y2) { return (((input - x1) * (y2 - y1)) / (x2 - x1)) + y1; }
Zunair

2

Tüm listeyi ölçeklendirme işlevi de dahil olmak üzere kopyalama ve yapıştırma kolaylığı için bazı kısa Python işlevleri.

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]

Hangi gibi kullanılabilir:

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

[0.0, 50.0, 75.0, 100.0]

Benim durumumda bir logaritmik eğriyi ölçeklendirmek istedim, şöyle:

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

[0.0, 21.533827903669653, 34.130309724299266, 43.06765580733931, 50.0]


1

Bu çözümü js'de çözdüğüm bir problemde kullandım, bu yüzden çeviriyi paylaşacağımı düşündüm. Açıklama ve çözüm için teşekkürler.

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

teşekkür ederim! harika bir çözüm ve gitmeye hazır bir işlev olarak ayarlandı!
Ateşle Ateşle Mücadele

1

C ++ Varyantı

PenguinTD's Solution yararlı buldum, bu yüzden herkes ihtiyacı varsa C + + taşındı:

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 Bağlantı Noktası

PHP için taşınan PenguinTD çözüm yararlı buldum. Kendine yardım et!

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

Burada, önceden belirlenmiş kaynak ve hedef aralıkları için yeniden ölçeklendirmeyi yapan ve her seferinde yapılması gereken hesaplama miktarını en aza indiren bir işlev döndüren bir Javascript sürümü bulunmaktadır.

// 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'i -0x80000000, 0x7FFFFFFF olarak ölçeklemek için bu işlevi kullanmanın bir örneği

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

0

Kısayol / basitleştirilmiş teklif

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

wayne


1
NewRange/OldRangeBurada ne var?
Zunair

0

Şahsen jenerikleri destekleyen yardımcı sınıfı kullanıyorum (Swift 3 uyumlu)

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

Bu örnek, bir şarkının geçerli konumunu 20 - 40 açı aralığına dönüştürür.

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

Liste anlama bir astar çözümü

color_array_new = [int((((x - min(node_sizes)) * 99) / (max(node_sizes) - min(node_sizes))) + 1) for x in node_sizes]

Daha uzun versiyon

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 Sürümü

Ne besleseniz de daima çalışır!

Öğrenmek için takip etmenin daha kolay olması için her şeyi genişletilmiş olarak bıraktım. Sonunda yuvarlama elbette isteğe bağlıdır.

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

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.