LINUX.ORG.RU

Проверка большого числа условий на си

 


2

2

Сейчас я пишу такой код:

	if( phonebookEntry->index < 0 && phonebookEntry->telNo && !phonebookEntry->name ){
		//только телефон
	}else if( phonebookEntry->index < 0 && phonebookEntry->telNo && phonebookEntry->name ){
		//телефон и имя
	}else if( phonebookEntry->index >= 0 && !phonebookEntry->telNo && !phonebookEntry->name ){
		//только индекс
	}else if( phonebookEntry->index >= 0 && phonebookEntry->telNo && !phonebookEntry->name ){
		//индекс и телефон
	}else if( phonebookEntry->index >= 0 && phonebookEntry->telNo && phonebookEntry->name ){
		//индекс, телефон и имя
	}else{
		//недопустимое сочетание
	}

И я заметил, что в каждом условном операторе сразу проверяется случая и подумал, а что если поступить вот так:

	unsigned int condition = 0;
	
	if( phonebookEntry->index >= 0 ) condition |= 1;
	if( phonebookEntry->telNo      ) condition |= 1 << 1;
	if( phonebookEntry->name       ) condition |= 1 << 2;

И дальше переменную condition запихнуть в switch или вообще использовать в качестве индекса в массиве указателей? Мой случай ещё пограничный, но если добавить ещё один параметр, то вариантов уже будет 16! Короче, степень двойки. Я один такой упоротый или такое где-нибудь применяется?

★★★
Ответ на: комментарий от u5er

пойдёт вswitch

не уверен, но я думаю switch работает как else ifпод капотом, просто запись для юзера выглядит читабельней. При этом у тебя генерируется квадратичная комбинация вариаций, то есть для 3-х полей у тебя выйдет 9 вариаций, для 4-х - 16 и т.д.

AZJIO
()
Ответ на: комментарий от AZJIO

генерируется квадратичная комбинация вариаций

Ага. Я ещё в шапке сазал про степень двойки.

для 3-х полей у тебя выйдет 9 вариаций

8 :)

u5er ★★★
() автор топика

Раз пошла такая пьянка, я тоже хочу со своим бредом поучаствовать. ;)

enum { IDX, TEL, NAME };

int cond = (phonebookEntry->index >= 0) << IDX |
	(!! phonebookEntry->telNo) << TEL |
	(!! phonebookEntry->name) << NAME;
	
switch (cond) {
case 1<<TEL:
	//только телефон
case 1<<TEL | 1<<NAME:
	//телефон и имя
case 1<<IDX:
	//только индекс
case 1<<IDX | 1<<TEL:
	//индекс и телефон
case 1<<IDX | 1<<TEL | 1<<NAME:
	//индекс, телефон и имя
default:
	//недопустимое сочетание
}
beastie ★★★★★
()
Ответ на: комментарий от beastie

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

Chiffchaff
()

Если условий много, то с первым способом вы гарантированно замучаетесь, а со вторым — запутаетесь (и замучаетесь ещё больше).

Я С не знаю, так что без кода, но я бы разбил задачу на два этапа:

  1. Строим что-то типа таблицы истинности, только в ячейках не 0/1, а nil/указатель на функцию, которую нужно вызвать в этих условиях.

  2. Обходчик, который проходит по таблице, находит нужную ячейку и вызывает функцию.

ugoday ★★★★★
()

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

AZJIO
()
Ответ на: комментарий от Chiffchaff

Так оно и так читабельное.

Повторюсь ещё раз: для читабельности того, что находится ВНУТРИ БЛОКА if:

if( условие ){
    //это блок кода
    //его нужно сделать наиболее читаемым
}

Блок кода станет более читаемым, если условие станет меньше. Прикинь, появится ещё и четвёртая переменная, тогда в каждом условии будет по 4 проверки. А если их превратить в двоичное число, то в условии будет всего одна переменная. Также это ещё и на производительность повлияет, потому что проверка будет всего одна.

u5er ★★★
() автор топика
Ответ на: комментарий от u5er

какой же мрак x_x

начни с того, чтобы сформировать формат (третий параметр у sprintf) отдельно, в зависимости от наличия индекса, телефонного номера, имени. Говорю сразу, это можно сделать без перебора всех вариантов (т.е. для этого не надо делать восемь if)

Как только это получится сделать, должно прийти понимание как реализовать весь остальной код без перебора всех вариантов.

Удачи.

anonymous
()

У тебя условий всего ничего. Пиши как тебе больше нравится, да и всё.

Вот если у тебя действительно много условий (десятки, а то и сотни), то есть как минимум 2 годных варианта:

  • либо устанавливаешь в соответствии с условиями флажки в битах, получаешь длинное целое число а потом масками и операцией «побитовое И», например, группируешь в что-то съедобное.

  • либо делаешь матрицу твоих хотелок и умножаешь на неё вектор своих условий. Получаешь вектор нужных действий.

Первый способ в общем-то подмножество второго, но иногда намного проще.

Stanson ★★★★★
()
Ответ на: комментарий от Chiffchaff

Эту пьянку можно и продолжить:

struct entry {
	int index,
	char *telNo,
	char *name,
}

int caseTel(struct entry *e)		{ /* только телефон */ }
int caseTelName(struct entry *e)	{ /* телефон и имя */ }
int caseIdx(struct entry *e)		{ /* только индекс */ }
int caseIdxTel(struct entry *e)		{ /* индекс и телефон */ }
int caseIdxTelName(struct entry *e)	{ /* индекс, телефон и имя */ }

enum { IDX, TEL, NAME, MAX };

int (*cases)()[1<<MAX] = {
	[1<<TEL]:                   caseTel,
	[1<<TEL | 1<<NAME]:         caseTelName,
	[1<<IDX]:                   caseIdx,
	[1<<IDX | 1<<TEL]:          caseIdxTel,
	[1<<IDX | 1<<TEL | <<NAME]: caseIdxTelName,
};

int check(struct entry *e) {
	int cond = (e->index >= 0) << IDX |
		(!! e->telNo) << TEL |
		(!! e->name) << NAME;
	int (*f)() = cases[cond && ((1<<MAX) - 1)];
	if !f {
		//недопустимое сочетание
		return -1;
	}
	return f(e)
}
beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 1)
Ответ на: комментарий от u5er

Блок кода станет более читаемым, если условие станет меньше.

Дичайше странное утверждение. Вам буквы до и после мешают? Тогда нужно вынести код в отдельную фукнцию и всё, готово.

ugoday ★★★★★
()
Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от anonymous

вы вообще сообщение топикстартера, где он привёл свой текущий код, видели?

Так ты сам не читатель :)

он не осилил форматную строку сконкатеновать из трёх аргументов

Проверка большого числа условий на си (комментарий) Вкратце: я рассматривал этот вариант и он мне не понравился. Потому что одно дело, когда вся команда генерируется сразу в одном месте, а другое - когда по кусочкам и зависит от.

u5er ★★★
() автор топика
Ответ на: комментарий от anonymous

Топикстартера понесло в таблицы и флаги из-за того что он не осилил форматную строку сконкатеновать из трёх аргументов, а вы ведётесь и вообще дичь уже советуете

ТС привёл пример с небольшим количеством проверок, а иногда их бывает много больше.
Предложен был лишь один из вариантов для случая использования многих проверок.

anonymous
()
Ответ на: комментарий от u5er

согласен что не читатель, но по ссылке там идея такая же как и моя, просто реализация выглядит жутко. Я ожидал увидеть что-то вроде такого по смыслу (псевдокод на псевдокрестах)

  const string idxStr = getIndexStr(phonebookEntry); // возвращает либо строку с индексом либо пустую
  const string phoneStr = getPhoneStr(phonebookEntry); // возвращает либо строку с запятой и телефоном либо пустую
  const string typeFmt = getPhoneStr(phonebookEntry); // возвращает либо строку с запятой и типом либо пустую
  const string nameFmt = getNameStr(phonebookEntry); // возвращает либо строку с запятой и именем либо пустую
  const string result = std::format("AT+CPBW={}{}{}{}\r", idxFmt, phoneFmt, typeFmt, nameFmt);

anonymous
()
Ответ на: комментарий от u5er

Короче:

	const char *fmt;
	int mode = phonebookEntry->telNo ? 2 : 0;
	int index = phonebookEntry->index >= 0 ? mode |= 1, phonebookEntry->index : ',';

	static const char * const table[] = {
		NULL, // ничего
		"AT+CPBW=%i\r", // только индекс
		"AT+CPBW=%c\"%s\"\r", // только телефон
		"AT+CPBW=%i,\"%s\"\r", // индекс и телефон
		NULL, // только имя
		NULL, // индекс и имя
		"AT+CPBW=%c\"%s\",%i,\"%s\"\r", // телефон и имя
		"AT+CPBW=%i,\"%s\",%i,\"%s\"\r", // индекс, телефон и имя
	};

	if( !(fmt = table[mode]) ) return -1;

	if( phonebookEntry->name ) {
		type = phonebookEntry->telNo[0] == '+' ? 145 : 129;

		res = packUtf82Ucs2( phonebookEntry->name, gsmService->codecBuffer, CODEC_BUFFER_SIZE );
		if( res < 0 ) return -1;

		mode |= 4;
	}

	res = snprintf(
			gsmService->atCmdBuffer, AT_CMD_BUFFER_SIZE,
			fmt, index, phonebookEntry->telNo,
			type, gsmService->codecBuffer);

	if( res < 0 ) return -1;
jpegqs
()
Последнее исправление: jpegqs (всего исправлений: 4)
Ответ на: комментарий от AZJIO
  1. Программы гораздо чаще читаются, нежели пишутся.

  2. Закинуть узлов в кластер — простое действие с просчитываемыми последствиями. Нанять линейного инженера — тут запросто можно и пол года потратить. А если умного инженера пытаться нанимать, тут вообще результат не гарантирован.

Отсюда вывод, выжимать последние такты из процессора — это очень-очень редкая задача, которую нужно делать только когда нужно, когда край, когда иначе никак. В остальных же случаях (т.е. почти всегда) — читаемость, понимаемость, тестируемость кода средним инженером является самой важной метрикой.

P.S. В виду вышесказанного предлагаю следующее решение:

  1. Тут очевидно строится конечный автомат. Вот через него задачу и будем решать.

  2. Конечный автомат будем делать на строках, потому как а) пятница и б) см. выше.

Сначала нам нужно перевести состояние в строковое представление

(defn has-idx? [idx]
        (format "%s%s"
                (if (or (not idx) (< idx 0))
                  "not " "")
                "idx"))

(defn has-telNo? [telNo]
        (format "%s%s"
                (if (not telNo) "not " "")
                "telNo"))


(defn has-name? [name]
        (format "%s%s"
                (if (not name) "not " "")
                "name"))

(defn state [{:keys [idx telNo name]}]
  (format "%s %s %s"
          (has-idx? idx)
          (has-telNo? telNo)
          (has-name? name)))
;;;;
user> (state {:name "ugoday" :telNo "+7444" :idx -15})
"not idx telNo name"
user> (state {:idx -15})
"not idx not telNo not name"
user> (state {})
"not idx not telNo not name"

Затем записать таблицу правил. Пояснение #".*" — запись регулярного выражения, #(do-something) — анонимная функция без аргументов (если интересно, #(do something %1 %2) — лямбда с двумя параметрами.

(def rules
        [[#"not idx telNo not name" #(println "только телефон")]
         [#"not idx telNo name" #(println "телефон и имя")]
         [#"idx not telNo not name" #(println "только индекс")]
         [#"idx telNo not name" #(println "индекс и телефон")]
         [#"idx telNo name" #(println "индекс, телефон и имя")]])

Тут всё очевидно, крайне сложно налажать, можно в любой момент расширить любым количеством правил и не запутаться. После чего всё элементарно

(defn run [address]
  (let [state (state address)]
    (doseq [[rule action] rules]
        (when (re-matches rule state)
          (action)))))
;;;
user> (run {:idx -7 :telNo 444 :name "ugoday"})
телефон и имя
nil
user> (run {:idx -7 :name "ugoday"})
nil
user> (state {:idx -7 :name "ugoday"})
"not idx not telNo name"
user> (run {:idx 7 :name "ugoday"})
nil
user> (run {:idx 7 :telNo "ugoday"})
индекс и телефон
nil
user> (run {:telNo "ugoday"})
только телефон
nil
user> (run {:name "ugoday" :telNo "+7444"})
телефон и имя
nil
user> (run {:name "ugoday" :telNo "+7444" :idx 15})
индекс, телефон и имя
nil
user> (run {:name "ugoday" :telNo "+7444" :idx -15})
телефон и имя
nil

Осталось код с кложи на С перевести и можно пользоваться.

ugoday ★★★★★
()

А там не получится по мере углубления в условие присоединять параметры, типа

if x=1
  string + x
    if y=2
        string + y

И почему обязательно snprintf? Разве в Си проблема конвертации числа в строку?

AZJIO
()
Ответ на: комментарий от AZJIO

Нет, каждый следующий else if проверяется, только если ложно значение предыдущего, и проверки прекращаются, как только какое-то условие будет истинным. В отличие от switch, который может «провалиться», из-за этой особенности в каждом case нужно писать break;.

yars068 ★★★★★
()

Сейчас я пишу такой код

В моих тестах GCC это ловко разворачивает в эквивалент

if (cond1) {
   if (cond2) {
      if (cond3) {
         // do something
      }
      else {
         // do something
      }
   }
   else {
      if (cond3) {
         // do something
      }
      else {
         // do something
      } 
   }
}
else {
   if (cond2) {
      if (cond3) {
         // do something
      }
      else {
         // do something
      }
   }
   else {
      if (cond3) {
         // do something
      }
      else {
         // do something
      } 
   }
}

Не изобретайте новый способ усложнить себе жизнь.

bugfixer ★★★★★
()
Ответ на: комментарий от u5er

Меня интересует «законность» использования второго метода.

Чтобы что? Если матрица состояний полная - будет так-же, если часть состояний исключается по бизнес логике - вы проиграете (asm будет хуже) решению «в лоб».

bugfixer ★★★★★
()
Ответ на: комментарий от u5er

Чтобы в том случае, если каждый вариант уникален, то проверка была одна, а не равна количеству переменных.

Вот только заплатите вы за это заранее, лишними conditional branches пока будете state набирать. Как по мне - овчинка выделки не стОит.

bugfixer ★★★★★
()

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

C это такой расширенный ассемблер

uint mask=0;
if (cond1) mask|=COND1_OK;
if (cond2) mask|=COND2_OK;
...
if ((mask & VARIANT1)!=VARIANT1 &&
   (mask & VARIANT2)!=VARIANT2) {
   // no variants !
   return ERROR_USER_DUDAK;
}
..
do_processing();

MKuznetsov ★★★★★
()