Как избежать длинных операторов переключения?C ++ - PullRequest
8 голосов
/ 04 апреля 2011

Я работаю над «словарем» для моего класса. У меня есть массив int с именем NumOfWordsInFile[], где NumOfWordsInFile[0] соответствует количеству слов в A.txt, а NumOfWordsInFile[25] соответствует Z.txt

Как и сейчас, у меня есть огромный переключатель для 26 различных состояний букв. У меня есть функция под названием AddWord(string word). AddWord получает первую букву переданного ему слова и вставляет его в соответствующий файл .txt. Теперь вот проблема. Каждый раз, когда слово добавляется в A.txt, я должен увеличивать NumOfWordsInFile[0] на 1. Единственный способ, которым я могу придумать, - это использовать эти огромные переключатели. У меня также есть функция deleteWord, которая наоборот уменьшает NumOfWordsInFile[], если слово удалено. Сейчас я не хочу иметь два 26 случая переключения, но проблема в том, что я не знаю, как еще это сделать. Теперь я мог бы сделать то же самое для функции удаления, но я действительно не хочу иметь сотни и более строк кода для прохождения. Есть ли лучший способ сделать это?

Пример переключателя в функции AddWord:

case 'w':
    if (numOfWordsInFile[22] < maxWordsPerFile) {
        fout.open(fileName.data(), ios::app);
        fout << word << " " << endl;
        numOfWordsInFile[22]++;
        if (totalWordsInDict < maxWordsInDict) {
            totalWordsInDict++;
        }
        return(Dictionary::success);
    } else {
        return(Dictionary::failure);
    }

case 'x':
    if (numOfWordsInFile[23] < maxWordsPerFile) {
        fout.open(fileName.data(),ios::app);
        fout << word << " " << endl;
        numOfWordsInFile[23]++;
        if (totalWordsInDict < maxWordsInDict) {
            totalWordsInDict++;
        }
        return(Dictionary::success);
    } else {
        return(Dictionary::failure);
    }

Удалить функцию.

bool Dictionary::DeleteAWord(string word)
{
    ofstream fout;
    ifstream fin;
    string x;
    string fileName="#.txt";
    int count=0;
    vector <string> words;
    bool deleted=false;

    fileName[0]=toupper(word[0]);
    fin.open(fileName.data()); //makes the file depending on the first letter of the argument "word"

    while (fin >> x)
    {
        words.push_back(x);
        count++;//number of elements in vector
    }
    if (SearchForWord(x))
    {
        for ( ;count > 0; count--)
        {
            if (words[count-1] == word)
            {
                // cout << "Found word " << word << " during search, now deleting" << endl;
                words.erase(words.begin()+(count-1));
                deleted = true;

                /*
                    This clearly doesn't work and is what I need help with, I know why it
                    doesn't work but I don't know how to make it better than having another
                    huge switch.
                */
                numOfWordsInFile[toupper(word[0])]--;
                /*

                */

                totalWordsInDict--;
                fin.close();
            }
        }

        if (deleted)
        {
            fout.open(fileName.data());
            for (int i = 0; i < words.size(); i++)
                fout << words[i] << endl;
            return(Dictionary::success);
        }
        return(Dictionary::failure);
    }
    return(Dictionary::failure);
}

Ответы [ 8 ]

7 голосов
/ 04 апреля 2011

Просто сделав очень быстрый взгляд, кажется, что вы используете положение буквы в алфавите, чтобы что-то делать.

Вы можете заменить все свои операторы switch одним оператором, который выглядит следующим образом:

int letter = (int)(ActualLetter - 'a');

if(numOfWordsInFile[letter]<maxWordsPerFile){
 fout.open(fileName.data(),ios::app);
 fout<<word<<" "<<endl;
 numOfWordsInFile[letter]++;
 if(totalWordsInDict<maxWordsInDict){
   totalWordsInDict++;
 }
 return(Dictionary::success);
}else{
 return(Dictionary::failure);
}

ActualLetter - это что-то вроде 'a', например.

В связанной заметке, если в будущем у вас действительно будут большие операторы switch, рассмотрите возможность инкапсуляции кода в функции:

switch (letter)
{
    case 'a':
      LetterA();
      break;

    case 'b':
      LetterB();
      break;

    ...
}

Или, что еще лучше, вы можете использовать полиморфизм, чтобы C ++ отправляла требуемый метод на основе определенного производного класса:

class BaseLetter
{
   ...
public:
   virtual void DoStuff() = 0;
};

class LetterA : public BaseLetter
{
public:
   void DoStuff();
};

class LetterB : public BaseLetter
{
public:
    void DoStuff();
};

void Foo(BaseLetter *letter)
{
    // Use dynamic dispatch to figure out what to do
    letter->DoStuff();
}

Просто обратите внимание, что динамическая отправка имеет (незначительное) снижение производительности, и вышесказанное - очень плохое место для его фактического использования.Решение, которое я, RedX и другие опубликовали, намного лучше подходит для вашего конкретного примера.

6 голосов
/ 04 апреля 2011
struct FileInfo {
  int NumWords;
  std::string Filename;
};

std::map<char, FileInfo> TheFiles; 

FileInfo & FI = TheFiles[letter];
// Work with FI.NumWords and FI.Filename

В качестве альтернативы:

std::vector<FileInfo> TheFiles;
FileInfo & FI = TheFiles[std::tolower(Letter) - 'a'];
6 голосов
/ 04 апреля 2011

В большинстве практических кодировок символов, с которыми вы, вероятно, столкнетесь при использовании C или C ++, 'a' - 'z' являются смежными, поэтому вы можете получить индекс массива, просто используя (c - 'a'), где c это char вы смотрите.

3 голосов
/ 04 апреля 2011

Символы в основном числа.«а» - 97, «б» - 98 и т. д.Самый простой способ - просто заменить все numOfWordsInFile[n] на numOfWordsInFile[current_char - 'a'], и весь код, повторяемый для каждого случая, может находиться в функции, например:

   int AddWord(char current_char) {
    if(numOfWordsInFile[current_char - 'a']<maxWordsPerFile){
     fout.open(fileName.data(),ios::app);
     fout<<word<<" "<<endl;
     numOfWordsInFile[current_char - 'a']++;
      if(totalWordsInDict<maxWordsInDict){
       totalWordsInDict++;
     }
     return(Dictionary::success);
    }else{
     return(Dictionary::failure);
    }
   }

Для более общих решений прочитайте о картах хешуказатели на функции (когда, например, для каждого символа вы можете назначить другую функцию.

3 голосов
/ 04 апреля 2011
if(numOfWordsInFile[letter - 'A']<maxWordsPerFile){
 fout.open(fileName.data(),ios::app);
 fout<<word<<" "<<endl;
 numOfWordsInFile[letter - 'A']++;
 if(totalWordsInDict<maxWordsInDict){
   totalWordsInDict++;
 }
 return(Dictionary::success);
}else{
 return(Dictionary::failure);
}

Это будет работать, только если в вашем сценарии использования есть только английская буква.

2 голосов
/ 04 апреля 2011

Отдельные символы в C ++ - это просто числа, соответствующие их значениям ASCII. Вы можете вычитать буквы друг от друга, чтобы получить числовые значения. Таким образом, если word[0] содержит букву A, тогда word[0] - 'A' будет 0.

Таким образом, вы можете индексировать массив numOfWordsInFile напрямую, и вам вообще не понадобится переключатель: numOfWordsInFiled[word[0] - 'A'].

Обратите внимание, что 'A' and 'a' имеют разные числовые значения, поэтому вам придется проделать дополнительную работу, если вы смешиваете верхний и нижний регистр.

1 голос
/ 04 апреля 2011

Это зависит от того, насколько портативным вы хотите быть или как интернационализированы. Если вы можете позволить себе игнорировать возможность что первая буква может быть символом с акцентом, и предположить, что вы никогда не собираетесь работать на мэйнфрейме или где-либо еще в противном случае используется EBCDIC, тогда вы можете преобразовать первую букву в конкретный случай, и вычесть «а» или «А» (в зависимости от случая) из него получить индекс. Стандарт C ++ не гарантирует что буквы являются смежными, и они не находятся в EBCDIC, ни в какой из кодировок, которые поддерживают ударение персонажи. По крайней мере, вам придется проверить, что Первый символ, конечно, буква.

Решение проблемы интернационализации сложно, так как нет ни одного обычно используемого кодирования, а некоторые из кодировки многобайтовые. Для однобайтовых кодировок это довольно прямо вперед, чтобы использовать таблицу сопоставления; стол с 256 записей, проиндексированных по первой букве (приведено к неподписанному char), которая возвращает индекс в вашу таблицу. Для многобайтовых Кодировки, как UTF-8, проблема сложнее: вы можете преобразовать начальный символ в последовательности UTF-8 в int, но вы можете в конечном итоге с ценностями около миллиона или более, и вы не хочу таблицу с миллионом записей (большинство из которых совершенно не имеет значения. Одним из простых решений может быть добавление 27-я запись для «другого». (Это также будет ловить "слова", как "Второй".)

Очень удобный способ сделать это:

int mappingTable[256];

std::fill_n(mappingTable, 256, 26);
static char const upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ;
static char const lower[] = "abcdefghijklmnopqrstuvwxyz;
for (int i = 0; i < 26; ++ i) {
    mappingTable[upper[i]] = i;
    mappingTable[lower[i]] = i;
}

Только не забудьте привести начальный символ к неподписанному символу. до индексации.

1 голос
/ 04 апреля 2011

Если ваш файл - A.txt, пусть индекс вашего массива будет 'A' - 'A' (= 0), если файл - B.txt, пусть индекс массива будет 'B' - 'A' (= 1) и т. Д.

...