środa, 7 grudnia 2022

C - Mappery, usuwanie dużej liczby instrukcji warunkowych

W tym poście chciałbym opisać sposób zredukowania długich instrukcji warunkowych w języku C. 

Poniżej znajduje się fragment kodu stosującego wiele instrukcji warunkowych w celu przypisania zmiany wartości znaku jednego kodowania na inne. Taki mechanizm można zastosować np. w przypadku wyświetlaczy gdzie jest ograniczona przestrzeń na znaki (w tym przypadku polskie litery), które są zakodowane pod innymi pozycjami niż w przypadku standardowego kodowania. 

  1. void ChangePLSigns(uint8_t *arr, uint8_t arrSize)
  2. {
  3.     for(uint8_t i = 0; i<arrSize; i++) {
  4.         if (arr[i] == SMALL_S_CP_1250)          { arr[v] = PL_SMALL_S; }
  5.         else if (arr[i] == SMALL_ZI_CP_1250)    { arr[v] = PL_SMALL_ZI; }
  6.         else if (arr[i] == SMALL_L_CP_1250)     { arr[v] = PL_SMALL_L; }
  7.         else if (arr[i] == SMALL_A_CP_1250)     { arr[v] = PL_SMALL_A; }
  8.         else if (arr[i] == SMALL_ZY_CP_1250)    { arr[v] = PL_SMALL_ZY; }
  9.         else if (arr[i] == SMALL_C_CP_1250)     { arr[v] = PL_SMALL_C; }
  10.         else if (arr[i] == SMALL_E_CP_1250)     { arr[v] = PL_SMALL_E; }
  11.         else if (arr[i] == SMALL_N_CP_1250)     { arr[v] = PL_SMALL_N; }
  12.         else if (arr[i] == SMALL_O_CP_1250)     { arr[v] = PL_SMALL_O; }
  13.         // DUŻE LITERY
  14.         else if (arr[i] == BIG_S_CP_1250)   { arr[v] = PL_BIG_S; }
  15.         else if (arr[i] == BIG_ZI_CP_1250)  { arr[v] = PL_BIG_ZI; }
  16.         else if (arr[i] == BIG_L_CP_1250)   { arr[v] = PL_BIG_L; }
  17.         else if (arr[i] == BIG_A_CP_1250)   { arr[v] = PL_BIG_A; }
  18.         else if (arr[i] == BIG_ZY_CP_1250)  { arr[v] = PL_BIG_ZY; }
  19.         else if (arr[i] == BIG_C_CP_1250)   { arr[v] = PL_BIG_C; }
  20.         else if (arr[i] == BIG_E_CP_1250)   { arr[v] = PL_BIG_E; }
  21.         else if (arr[i] == BIG_N_CP_1250)   { arr[v] = PL_BIG_N; }
  22.         else if (arr[i] == BIG_O_CP_1250)   { arr[v] = PL_BIG_O; }
  23.         //spacja
  24.         else if (arr[i] == VALUE_ZERO)  { arr[v] = 0x20; }
  25.     }
  26. }

Jak można zaobserwować powyżej instrukcje warunkowe wykonują prostą operację przez zamianę jednego znaku w formacie Windows CP1250 na inny. Funkcja przechodzi przez każdy znak w kodzie i porównuje go z wszystkimi dostępnymi znakami.

Jeśli chcielibyśmy przetestować tą funkcję to mogłoby to wyglądać następująco:

  1. bool checkIfArrayIsSame(uint8_t preparedArray[], uint8_t referenceArray[], uint8_t tableSize)
  2. {
  3.     for(uint8_t i=0; i<tableSize; i++) {
  4.         if(preparedArray[i] != referenceArray[i]){
  5.             return false;
  6.         }
  7.     }
  8.     return true;
  9. }
  10.  
  11. void Mapper_Test::ChangePLSigns_Test_ProperValues()
  12. {
  13.     uint8_t testArray[] = {
  14.             SMALL_S_CP_1250, SMALL_ZI_CP_1250, SMALL_L_CP_1250, SMALL_A_CP_1250, SMALL_ZY_CP_1250,
  15.             SMALL_C_CP_1250, SMALL_E_CP_1250, SMALL_N_CP_1250, SMALL_O_CP_1250,
  16.             BIG_S_CP_1250, BIG_ZI_CP_1250, BIG_L_CP_1250, BIG_A_CP_1250, BIG_ZY_CP_1250,
  17.             BIG_C_CP_1250, BIG_E_CP_1250, BIG_N_CP_1250, BIG_O_CP_1250, VALUE_ZERO
  18.     };
  19.     uint8_t referenceArray[] = {
  20.                 PL_SMALL_S, PL_SMALL_ZI, PL_SMALL_L, PL_SMALL_A, PL_SMALL_ZY,
  21.                 PL_SMALL_C, PL_SMALL_E, PL_SMALL_N, PL_SMALL_O,
  22.                 PL_BIG_S, PL_BIG_ZI, PL_BIG_L, PL_BIG_A, PL_BIG_ZY,
  23.                 PL_BIG_C, PL_BIG_E, PL_BIG_N, PL_BIG_O, SPACE_VAL,
  24.     };
  25.  
  26.     ChangePLSigns(&testArray[0], sizeof(testArray)/sizeof(testArray[0]));
  27.     CPPUNIT_ASSERT(checkIfArrayIsSame(testArray, referenceArray, sizeof(testArray)/sizeof(testArray[0])));
  28. }

Teraz gdy testy są przygotowane można spokojnie zmieniać funkcje i nie wprowadzić niepożądanych błędów. Jeśli nie jesteśmy pewni przygotowanego testu to należy przygotować takie rozwiązanie, który się nie powiedzie przez zmianę danych w testowej nie ruszając tablicy porównawczej (referenceArray).

Wracając do kodu funkcji głównej. Najprostszym scenariuszem jest przygotowanie dwóch tablic, gdzie na tych samych pozycjach będą znaki odpowiadające konkretnej literze. 

  1. void ChangePLSigns(uint8_t *arr, uint8_t arrSize)
  2. {
  3.     uint8_t CP_1250_Signs[] = {
  4.             SMALL_S_CP_1250, SMALL_ZI_CP_1250, SMALL_L_CP_1250, SMALL_A_CP_1250, SMALL_ZY_CP_1250,
  5.             SMALL_C_CP_1250, SMALL_E_CP_1250, SMALL_N_CP_1250, SMALL_O_CP_1250,
  6.             BIG_S_CP_1250, BIG_ZI_CP_1250, BIG_L_CP_1250, BIG_A_CP_1250, BIG_ZY_CP_1250,
  7.             BIG_C_CP_1250, BIG_E_CP_1250, BIG_N_CP_1250, BIG_O_CP_1250, VALUE_ZERO
  8.     };
  9.     uint8_t PolishMappedSigns[] = {
  10.             PL_SMALL_S, PL_SMALL_ZI, PL_SMALL_L, PL_SMALL_A, PL_SMALL_ZY,
  11.             PL_SMALL_C, PL_SMALL_E, PL_SMALL_N, PL_SMALL_O,
  12.             PL_BIG_S, PL_BIG_ZI, PL_BIG_L, PL_BIG_A, PL_BIG_ZY,
  13.             PL_BIG_C, PL_BIG_E, PL_BIG_N, PL_BIG_O, SPACE_VAL,
  14.     };
  15.  
  16.     for(uint8_t i = 0; i<arrSize; i++) {
  17.         for(uint8_t j = 0; j<(sizeof(CP_1250_Signs)/sizeof(CP_1250_Signs[0])); j++)
  18.         {
  19.             if(CP_1250_Signs[j] == arr[i])
  20.             {
  21.                 arr[i] = PolishMappedSigns[j];
  22.                 break;
  23.             }
  24.         }
  25.     }
  26. }
 
W dodatkowej pętli for porównywalne są znaki z tablicy, jeśli są takie same to następuje przypisanie zmapowanego znaku. Dodatkowo do takiej funkcji można wprowadzić kilka zabezpieczeń w postaci dodatkowych instrukcji warunkowych.

Sprawdzenie czy przekazana tablica ma rozmiar większy niż 0 oraz czy wskaźnik nie jest pusty:

  1. if(NULL != arr)
  2. if(0 == arrSize) { return; }

Kolejnym elementem jest ograniczenie sprawdzanych znaków. Każda z zdefiniowanych liter w formacie CP_1250 ma przypisaną jakąś wartość liczbową. Wobec tego, że do funkcji nie będą podawane tylko polskie znaki, ale także standardowe litery, których nie musimy sprawdzać. Można ograniczyć testowanie znaków tylko do przedziału polskich liter z tablicy. Aby to osiągnąć najprościej znaleźć wartość minimalną i maksymalną.

  1. void ChangePLSigns(uint8_t *arr, uint8_t arrSize)
  2. {
  3.     uint8_t CP_1250_Signs[] = {
  4.             SMALL_S_CP_1250, SMALL_ZI_CP_1250, SMALL_L_CP_1250, SMALL_A_CP_1250, SMALL_ZY_CP_1250,
  5.             SMALL_C_CP_1250, SMALL_E_CP_1250, SMALL_N_CP_1250, SMALL_O_CP_1250,
  6.             BIG_S_CP_1250, BIG_ZI_CP_1250, BIG_L_CP_1250, BIG_A_CP_1250, BIG_ZY_CP_1250,
  7.             BIG_C_CP_1250, BIG_E_CP_1250, BIG_N_CP_1250, BIG_O_CP_1250, VALUE_ZERO
  8.     };
  9.     uint8_t PolishMappedSigns[] = {
  10.             PL_SMALL_S, PL_SMALL_ZI, PL_SMALL_L, PL_SMALL_A, PL_SMALL_ZY,
  11.             PL_SMALL_C, PL_SMALL_E, PL_SMALL_N, PL_SMALL_O,
  12.             PL_BIG_S, PL_BIG_ZI, PL_BIG_L, PL_BIG_A, PL_BIG_ZY,
  13.             PL_BIG_C, PL_BIG_E, PL_BIG_N, PL_BIG_O, SPACE_VAL,
  14.     };
  15.  
  16.     if(NULL != arr)
  17.     if(0 == arrSize) { return; }
  18.  
  19.     for(uint8_t i = 0; i<arrSize; i++) {
  20.         if(arr[i] >= (BIG_S_CP_1250) && arr[i] <= (SMALL_O_CP_1250))
  21.         {
  22.             for(uint8_t j = 0; j<(sizeof(CP_1250_Signs)/sizeof(CP_1250_Signs[0])); j++)
  23.             {
  24.                 if(CP_1250_Signs[j] == arr[i])
  25.                 {
  26.                     arr[i] = PolishMappedSigns[j];
  27.                     break;
  28.                 }
  29.             }
  30.         }
  31.         else
  32.         {
  33.             if(arr[i] == VALUE_ZERO)
  34.             {
  35.                 arr[i] = SPACE_VAL;
  36.             }
  37.         }
  38.     }
  39. }
 
W bardzo podobny sposób można wykonać bardziej zaawansowane wyszukiwanie. Gdzie jako elemen wykonywalny będziemy przekazywać wskaźnik na funkcję. Taki przypadek opisałem dla przycisków zdefiniowanych w panelu dotykowym na tym blogu (C - Mapper panel dotykowy).