Üç sayıyı karşılaştırmanın basit ve temiz yolu


11

Ben ifs bir dizi var bazı kod var , ama sadece dağınık hissediyorum. Temel olarak, üç tamsayıdan en büyüğünü seçmek ve hangisinin seçildiğini söylemek için bir durum bayrağı ayarlamak istiyorum. Mevcut kodum şöyle:

a = countAs();
b = countBs();
c = countCs();

if (a > b && a > c)
    status = MOSTLY_A;
else if (b > a && b > c)
    status = MOSTLY_B;
else if (c > a && c > b)
    status = MOSTLY_C;
else
    status = DONT_KNOW;

Bu desen birkaç kez ortaya çıkar ve uzun değişken adları ile her ifbirinin doğru olduğunu görsel olarak doğrulamak biraz zorlaşır . Bunu yapmanın daha iyi ve daha açık bir yolu olabileceğini hissediyorum; kimse bir şey önerebilir mi?


Bazı potansiyel kopyalar var, ancak bu soruya tam olarak uymuyorlar.

Önerilen çift kopyada: Birden fazla koşulu kontrol etme yaklaşımları? önerilen çözümlerin tümü orijinal kod kadar eşit derecede sakar görünüyor, bu yüzden daha iyi bir çözüm sunmuyorlar.

Ve bu yazı (eğer varsa) sadece yuvalama seviyeleri ve asimetri ile ilgileniyorsa, buradaki sorun değil.


3
Bana göre tek sorun kod tekrarı. Burada ne var okumak son derece açık, neden bunu değiştireyim? Sadece a, b, c alan ve durum döndüren bir fonksiyona ayırın. Eğer kendinizi daha iyi hissettirirse, üzerine bir "satır içi" bırakın. Makro yok, karmaşıklık yok, sadece eski işlev çıkarma. Daha sonra çalışmam gerekirse açık kodunuz için teşekkür edeceğim.
J Trana



#Define öğelerinizin adının kötü olduğunu unutmayın. A = 40, b = 30, c = 30 diyelim. Sonuç MOSTLY_A. Ancak, çoğu şey aslında A değil, B veya C'dir. MAX_IS_A, ...? (bu soruya cevap olarak max (a, max (b, c)) anlamına gelir).
tony

Yanıtlar:


12

Çarpanlarına ayırın, erken dönün

Yorumlarda önerildiği gibi, mantığı basitçe bir fonksiyona sarmak ve returnşeyleri çok basitleştirmek için 's ile erken çıkmak yeterli olacaktır . Ayrıca, testleri başka bir işleve devrederek biraz işlevselliği çarpanlarına ayırabilirsiniz. Daha somut olarak:

bool mostly(max,u,v) {
   return max > u && max > v;
}

status_t strictly_max_3(a,b,c)
{
  if mostly(a,b,c) return MOSTLY_A;
  if mostly(b,a,c) return MOSTLY_B;
  if mostly(c,a,b) return MOSTLY_C;
  return DONT_KNOW;
}

Bu benim önceki denememden daha kısa:

status_t index_of_max_3(a,b,c)
{
  if (a > b) {
    if (a == c)
      return DONT_KNOW;
    if (a > c)
      return MOSTLY_A;
    else
      return MOSTLY_C;
  } else {
    if (a == b)
      return DONT_KNOW;
    if (b > c)
      return MOSTLY_B;
    else
      return MOSTLY_C;
  }
}

Yukarıdakiler biraz daha ayrıntılıdır, ancak IMHO'yu okumak kolaydır ve karşılaştırmaları birkaç kez yeniden hesaplamaz.

Görsel onay

In Cevabınız diyorsunuz:

benim sorunum çoğunlukla tüm karşılaştırmaların aynı değişkenleri kullandığının görsel olarak doğrulanmasıydı

... ayrıca, sorunuzda şunları söylersiniz:

Bu desen birkaç kez ortaya çıkar ve uzun değişken isimleriyle, her birinin doğru olup olmadığını görsel olarak doğrulamak biraz zorlaşır.

Neyi başarmaya çalıştığınızı anlamayabilirim: Deseni ihtiyacınız olan her yere kopyalayıp yapıştırmak ister misiniz? Yukarıdaki gibi bir fonksiyonu ile, bir kez desen yakalamak ve tüm karşılaştırmalar kullandığını herkes için bir kez kontrol a, bve cgerektiği gibi. Ardından, işlevi çağırdığınızda artık endişelenmenize gerek yoktur. Tabii ki, belki de pratikte probleminiz tarif ettiğinizden biraz daha karmaşıktır: öyleyse, lütfen mümkünse bazı ayrıntılar ekleyin.


1
DONT_KNOW hakkındaki yorumunuzu anlamıyorum , c en küçük ve a ve b aynı ise ne olur? Algoritma b'nin en büyük olduğu, a'nın b ile aynı olduğu sonucunu verir.
Pieter B

@ PiietB ​​Ben ya MOSTLY_Ada MOSTLY_Cdurumda döndüğümüzde önemli olmadığını varsayarak yanlış ve ben a == cve a > b. Bu düzeltildi. Teşekkürler.
coredump

@DocBrown Verilen avantajların çoğu erken çıkış davranışından gelir.
coredump

1
+1, şimdi gerçekten OPs orijinal kodu üzerinde bir gelişme.
Doc Brown

9

TP: DR; Kodunuz zaten doğru ve "temiz".

Cevabın etrafında dolaşan birçok insan görüyorum ama herkes ağaçların arasından ormanı kaçırıyor. Bu soruyu tam olarak anlamak için tam bilgisayar bilimi ve matematiksel analiz yapalım.

İlk olarak, her biri 3 durumlu 3 değişkenimiz olduğunu not ediyoruz: <, = veya>. Toplam permütasyon sayısı, her bir durum için P # ile gösterilen benzersiz bir sayı atayacağım 3 ^ 3 = 27 durumdur. Bu P # numarası faktöriyel bir sayı sistemidir .

Sahip olduğumuz tüm permütasyonları numaralandırmak:

a ? b | a ? c | b ? c |P#| State
------+-------+-------+--+------------
a < b | a < c | b < c | 0| C
a = b | a < c | b < c | 1| C
a > b | a < c | b < c | 2| C
a < b | a = c | b < c | 3| impossible a<b b<a
a = b | a = c | b < c | 4| impossible a<a
a > b | a = c | b < c | 5| A=C > B
a < b | a > c | b < c | 6| impossible a<c a>c
a = b | a > c | b < c | 7| impossible a<c a>c
a > b | a > c | b < c | 8| A
a < b | a < c | b = c | 9| B=C > A
a = b | a < c | b = c |10| impossible a<a
a > b | a < c | b = c |11| impossible a<c a>c
a < b | a = c | b = c |12| impossible a<a
a = b | a = c | b = c |13| A=B=C
a > b | a = c | b = c |14| impossible a>a
a < b | a > c | b = c |15| impossible a<c a>c
a = b | a > c | b = c |16| impossible a>a
a > b | a > c | b = c |17| A
a < b | a < c | b > c |18| B
a = b | a < c | b > c |19| impossible b<c b>c
a > b | a < c | b > c |20| impossible a<c a>c
a < b | a = c | b > c |21| B
a = b | a = c | b > c |22| impossible a>a
a > b | a = c | b > c |23| impossible c>b b>c
a < b | a > c | b > c |24| B
a = b | a > c | b > c |25| A=B > C
a > b | a > c | b > c |26| A

Muayene ile elimizde:

  • 3, A'nın maks.
  • 3, B'nin maks.
  • 3, C'nin maks olduğu ve
  • 4, A = B veya B = C'yi belirtir.

Tüm bu permütasyonları A, B ve C değerleri ile numaralandırmak için bir program yazalım (dipnota bakın). P # ile kararlı sıralama:

a ?? b | a ?? c | b ?? c |P#| State
1 <  2 | 1 <  3 | 2 <  3 | 0| C
1 == 1 | 1 <  2 | 1 <  2 | 1| C
1 == 1 | 1 <  3 | 1 <  3 | 1| C
2 == 2 | 2 <  3 | 2 <  3 | 1| C
2  > 1 | 2 <  3 | 1 <  3 | 2| C
2  > 1 | 2 == 2 | 1 <  2 | 5| ??
3  > 1 | 3 == 3 | 1 <  3 | 5| ??
3  > 2 | 3 == 3 | 2 <  3 | 5| ??
3  > 1 | 3  > 2 | 1 <  2 | 8| A
1 <  2 | 1 <  2 | 2 == 2 | 9| ??
1 <  3 | 1 <  3 | 3 == 3 | 9| ??
2 <  3 | 2 <  3 | 3 == 3 | 9| ??
1 == 1 | 1 == 1 | 1 == 1 |13| ??
2 == 2 | 2 == 2 | 2 == 2 |13| ??
3 == 3 | 3 == 3 | 3 == 3 |13| ??
2  > 1 | 2  > 1 | 1 == 1 |17| A
3  > 1 | 3  > 1 | 1 == 1 |17| A
3  > 2 | 3  > 2 | 2 == 2 |17| A
1 <  3 | 1 <  2 | 3  > 2 |18| B
1 <  2 | 1 == 1 | 2  > 1 |21| B
1 <  3 | 1 == 1 | 3  > 1 |21| B
2 <  3 | 2 == 2 | 3  > 2 |21| B
2 <  3 | 2  > 1 | 3  > 1 |24| B
2 == 2 | 2  > 1 | 2  > 1 |25| ??
3 == 3 | 3  > 1 | 3  > 1 |25| ??
3 == 3 | 3  > 2 | 3  > 2 |25| ??
3  > 2 | 3  > 1 | 2  > 1 |26| A

Hangi P # durumlarının imkansız olduğunu nasıl bildiğimi merak ediyorsan, şimdi biliyorsun. :-)

Siparişi belirlemek için minimum karşılaştırma sayısı:

Log2 (27) = Log (27) / Log (2) = ~ 4.75 = 5 karşılaştırma

yani coredump doğru 5 minimum sayıda karşılaştırma yaptı. Kodunu şu şekilde biçimlendiririm:

status_t index_of_max_3(a,b,c)
{
    if (a > b) {
        if (a == c) return DONT_KNOW; // max a or c
        if (a >  c) return MOSTLY_A ;
        else        return MOSTLY_C ;
    } else {
        if (a == b) return DONT_KNOW; // max a or b
        if (b >  c) return MOSTLY_B ;
        else        return MOSTLY_C ;
    }
}

Sorununuz için eşitlik testi yapmayı umursamıyoruz, böylece 2 testi atlayabiliriz.

Yanlış yanıtı alırsa kodun ne kadar temiz / kötü olduğu önemli değildir, bu yüzden tüm vakaları doğru bir şekilde ele aldığınızı gösteren iyi bir işarettir!

Daha sonra, basitliğe gelince, insanlar cevabı "geliştirmeye" devam ediyorlar, burada iyileştirmenin karşılaştırma sayısını "optimize etmek" anlamına geliyor, ancak bu tam olarak sorduğunuz şey değil. Herkesi “daha ​​iyi olabileceğini hissediyorum” diye sorduğunuz yerde karıştırdınız, ancak “daha ​​iyi” nin ne anlama geldiğini tanımlamadınız. Daha az karşılaştırma? Daha az kod mu? Optimal karşılaştırmalar?

Kod okunabilirliği hakkında soru sorduğunuzdan (doğruluk verildiğinde) okunabilirlik için kodunuzda yalnızca bir değişiklik yapacağım: İlk testi diğerleriyle hizalayın.

        if      (a > b && a > c)
            status = MOSTLY_A;
        else if (b > a && b > c)
            status = MOSTLY_B;
        else if (c > a && c > b)
            status = MOSTLY_C;
        else
            status = DONT_KNOW; // a=b or b=c, we don't care

Şahsen ben şu şekilde yazardım ama bu kodlama standartlarınız için çok alışılmadık olabilir:

        if      (a > b && a > c) status = MOSTLY_A ;
        else if (b > a && b > c) status = MOSTLY_B ;
        else if (c > a && c > b) status = MOSTLY_C ;
        else /*  a==b  || b ==c*/status = DONT_KNOW; // a=b or b=c, we don't care

Dipnot: İşte permütasyonları oluşturmak için C ++ kodu:

#include <stdio.h>

char txt[]  = "< == > ";
enum cmp      { LESS, EQUAL, GREATER };
int  val[3] = { 1, 2, 3 };

enum state    { DONT_KNOW, MOSTLY_A, MOSTLY_B, MOSTLY_C };
char descr[]= "??A B C ";

cmp Compare( int x, int y ) {
    if( x < y ) return LESS;
    if( x > y ) return GREATER;
    /*  x==y */ return EQUAL;
}

int main() {
    int i, j, k;
    int a, b, c;

    printf( "a ?? b | a ?? c | b ?? c |P#| State\n" );
    for( i = 0; i < 3; i++ ) {
        a = val[ i ];
        for( j = 0; j < 3; j++ ) {
            b = val[ j ];
            for( k = 0; k < 3; k++ ) {
                c = val[ k ];

                int cmpAB = Compare( a, b );
                int cmpAC = Compare( a, c );
                int cmpBC = Compare( b, c );
                int n     = (cmpBC * 9) + (cmpAC * 3) + cmpAB; // Reconstruct unique P#

                printf( "%d %c%c %d | %d %c%c %d | %d %c%c %d |%2d| "
                    , a, txt[cmpAB*2+0], txt[cmpAB*2+1], b
                    , a, txt[cmpAC*2+0], txt[cmpAC*2+1], c
                    , b, txt[cmpBC*2+0], txt[cmpBC*2+1], c
                    , n
                );

                int status;
                if      (a > b && a > c) status = MOSTLY_A;
                else if (b > a && b > c) status = MOSTLY_B;
                else if (c > a && c > b) status = MOSTLY_C;
                else /*  a ==b || b== c*/status = DONT_KNOW; // a=b, or b=c

                printf( "%c%c\n", descr[status*2+0], descr[status*2+1] );
            }
        }
    }
    return 0;
}

Düzenlemeler: Geri bildirime dayanarak, TL: DR yukarı taşındı, sıralanmamış tablo kaldırıldı, 27 açıklandı, kod temizlendi, imkansız durumlar açıklandı.


-1: karar sayısını azaltmak daha basit kod yollarına ve daha okunabilir kodlara yol açmaz mı? Argüman açık değil: ilk önce herkesin yanlış olduğunu söylüyorsun; sonra bir ya da iki değil üç tablo koyarsınız ; Ben sonucu hesaplamak için daha basit bir yol yol umuyordu ama bunun yerine herkes zaten ne biliyordu doğruladı (OP kodu doğru olanı yapar). Elbette, soru okunabilirlikle ilgilidir, ancak okunabilirlik yalnızca kod düzenini değiştirerek elde edilmez (değişikliklerinizin mevcut kod standartlarına pek uymayacağını kabul edersiniz). Okunabilirliği optimize ederken mantığı basitleştirmek mantıklıdır.
coredump

Daha yapıcı: Bazı ayrıntıları bırakarak ve cevabınızın yapısını düşünerek cevabınızı basitleştirmenizi öneririm. Yazmak ve C ++ kodu üreten permütasyonlar sonrası için zaman ayırdığınız için teşekkür ederiz, ama belki ana sonuç ve sadece bir tablo verebilir: şimdi olduğu gibi, tüm işinizi olduğu gibi terk. TL'yi neredeyse fark edemedim; DR şey (bununla başlayabilirsiniz). Umarım yardımcı olur.
coredump

2
Yapıcı geri bildirim coredump için teşekkürler. Kolayca doğrulandığı için orta sıralanmamış tabloyu kaldırdım.
Michaelangel007

2
İsa Mesih! Üç sayıyı karşılaştırmak roket bilimi kadar karmaşık diyebilir ki?
Mandrill

@Mandrill Bilgisayar bilimcileri olarak bir problemi tam olarak anlamak bizim işimiz . Sadece 3 yönlü karşılaştırma için 27 olası permütasyonun tümünü numaralandırarak , çözümümüzün TÜM durumlarda çalıştığını doğrulayabiliriz . Programcı olarak istediğimiz son şey gizli hatalar ve hesaplanmayan uç durumlar. Ayrıntı, doğruluk için ödenen fiyattır.
Michaelangel007

5

@msw, a, b, c yerine bir dizi kullanmanızı söyledi ve @Basile, "max" mantığını bir işleve yeniden yansıttığını söyledi. Bu iki fikri birleştirmek

val[0] = countAs();    // in the real code, one should probably define 
val[1] = countBs();    // some enum for the indexes 0,1,2 here
val[2] = countCs();

 int result[]={DONT_KNOW, MOSTLY_A, MOSTLY_B, MOSTLY_C};

ardından rastgele bir dizinin maksimum dizinini hesaplayan bir işlev sağlayın:

// returns the index of the strict maximum, and -1 when the maximum is not strict
int FindStrictMaxIndex(int *values,int arraysize)
{
    int maxVal=INT_MIN;
    int maxIndex=-1;
    for(int i=0;i<arraysize;++i)
    {
       if(values[i]>maxVal)
       {
         maxVal=values[i];
         maxIndex=i;
       }
       else if (values[i]==maxVal)
       {
         maxIndex=-1;
       }
    }
    return maxIndex;
}

ve şöyle deyin

 return result[FindStrictMaxIndex(val,3)+1];

Toplam LOC sayısı orijinal olana göre artmış gibi görünüyor, ancak şimdi yeniden kullanılabilir bir fonksiyonda çekirdek mantığa sahipsiniz ve fonksiyonu birden çok kez tekrar kullanabiliyorsanız, ödeme yapmaya başlar. Dahası, FindStrictMaxIndexişlev artık "iş gereksinimlerinizle" iç içe geçmez (endişelerin ayrılması), bu nedenle daha sonra değiştirmek zorunda kalacağınız risk orijinal sürümünüzden çok daha düşüktür (açık-kapalı prensibi). Örneğin, bağımsız değişken sayısı değişse veya MOSTLY_ABC dışında başka bir dönüş değeri kullanmanız gerekiyorsa veya a, b, c dışında başka değişkenler işliyor olsanız bile bu işlevin değiştirilmesi gerekmez. Ayrıca, 3 farklı a, b, c değeri yerine bir dizinin kullanılması, kodunuzu başka yerlerde de basitleştirebilir.

Tabii ki, tüm programınızda bu işlevi çağırmak için sadece bir veya iki yer varsa ve bir dizideki değerleri tutmak için başka uygulamalarınız yoksa, muhtemelen orijinal kodu olduğu gibi bırakırım (veya @ coredump'ın gelişimi).


Bunu beğendim - bağırsakları FindStrictMaxIndex()çok temiz olmayabilir, ancak arayan bakış açısından neyin başarılmaya çalışıldığı oldukça açıktır.
Ken YN

Veya iki diziyi tutmak yerine, bir dizi anahtar / değer çifti tutun: {MOSTLY_A, countAs ()}, değere göre sıralanan ilk öğeyi alın ve anahtarı okuyun.
Julia Hayward

@JuliaHayward: Böyle bir çözüm önermememin ana nedeni, sorunun "C" etiketi idi - C'de, anahtar / değer çiftleriyle uğraşmak ve KVP'ler cinsinden yazılmış bir işlev oluşturmak için daha fazla kaynak koduna ihtiyaç duyulacak büyük olasılıkla basit bir intyazılan fonksiyondan farklı bağlamlarda tekrar kullanılamaz . Ancak Python veya Perl gibi farklı bir dil kullanıyorsa yorumunuzu kabul ediyorum.
Doc Brown

1
@ gnasher729: orjinal kodda kaç tane "kopya" olduğuna, ne kadar benzer olduklarına ve FindStrictMaxIndexişlevin ne sıklıkta yeniden kullanılabileceğine bağlıdır. Bir veya iki kez yeniden kullanım için, elbette bu işe yaramayacak, ancak cevabımda zaten yazdığım şey bu. Gelecekte yapacağım değişikliklerle ilgili olarak yukarıda bahsettiğim diğer avantajlara da dikkat edin.
Doc Brown

1
... ve orijinal 8 satırın, return result[FindStrictMaxIndex(val,3)]; koddaki orijinal 8 satırın yerleştirildiği noktada basit bir astarla değiştirilebileceğini unutmayın . Diğer kısımlar, özellikle de FindStrictMaxIndexkendisi, onları değişen iş gereksinimlerinin odağının dışına çıkaran "iş mantığından" tamamen ayrılmıştır.
Doc Brown

-1

Muhtemelen MAX en fazla iki sayı veren bir makro veya işlev kullanmalısınız .

O zaman sadece istersiniz:

 status = MAX(a,MAX(b,c));

Tanımlamış olabilirsiniz

 #define MAX(X,Y) (((X)>(Y))?(X):(Y))

ancak makro kullanırken (özellikle yan etkiler konusunda) dikkatli olun ( MAX(i++,j--) garip davranacağı için)

Bir işlevi daha iyi tanımlayın

 static inline int max2ints(int x, int y) {
    return (x>y)?x:y;
 }

ve kullanın (veya en azından #define MAX(X,Y) max2ints((X),(Y))....)

Eğer MAX kökenini anlamak gerekiyorsa gibi uzun bir makro olabilir #define COMPUTE_MAX_WITH_CAUSE(Status,Result,X,Y,Z) uzun olan do{... }while(0) belki makro

#define COMPUTE_MAX_WITH_CAUSE(Status,Result,X,Y,Z) do { \
  int x= (X), y= (Y), z=(Z); \
  if (x > y && y > z) \
    { Status = MOSTLY_FIRST; Result = x; } \
  else if (y > x && y > z) \
    { Status = MOSTLY_SECOND; Result = y; } \
  else if (z > x && z > y) \
    { Status = MOSTLY_THIRD; Result = z; } \
  /* etc */ \
  else { Status = UNKNOWN; Result = ... } \
} while(0)

Sonra COMPUTE_MAX_WITH_CAUSE(status,res,a,b,c) birkaç yerde çağırabilirsiniz . Biraz çirkin. Yerel değişkenler tanımlanmış x, y, z kötü yan etkileri azaltmak için ....


2
Ortak mantığın bir fonksiyona yeniden uyarlanması doğru yaklaşımdır, ancak aslında burada iki şeyden kaçınırdım: 1. Yeni gereksinimleri "icat etmem" (OP maksimum değeri hesaplamak istemedi). Ve 2.: ortaya çıkan kod daha KURU hale gelse bile, bu karmaşık bir makroyu haklı çıkarsa çok tartışmalıdır.
Doc Brown

1
Makrolar son çare aracı olmalıdır. Kesinlikle bu sorun için sınırların dışında.
kevin cline

-1

Ben bu konuda bir düşünce daha vardı, bu yüzden benim sorun çoğunlukla tüm karşılaştırmalar aynı değişkenleri kullandığını görsel onay olduğundan, bu yararlı bir yaklaşım olabilir düşünüyorum:

a = countAs();
b = countBs();
c = countCs();

if (FIRST_IS_LARGEST(a, b, c))
    status = MOSTLY_A;
else if (SECOND_IS_LARGEST(a, b, c))
    status = MOSTLY_B;
else if (THIRD_IS_LARGEST(a, b, c))
    status = MOSTLY_C;
else
    status = DONT_KNOW; /* NO_SINGLE_LARGEST is a better name? */

Her makro alır a, bve caynı sırada onaylayın kolaydır ve makro adı bana bütün karşılaştırmalar ve ANDs ne yaptığını çalışmak zorunda kaydeder.


1
(1) fonksiyonlar yerine neden yardımcı makrolar? (2) burada neden görsel onay gerekiyor? bu gerçekten sizin temel probleminiz mi, yoksa görsel teyit ihtiyacı kod kopyalamanın bir sonucu mu? En iyi seçeneğiniz, kodunuzu herkes için bir kez kontrol ettiğiniz tek ve basit bir işlevde çarpanlarına ayırmaktır .
coredump
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.