Страница 1 из 1

Перетасовка объектов на форме

Добавлено: 28 май 2014, 16:27
jonyroww
Здравствуйте!
Пишу систему тестирования в QtCreator. Нужно чтобы при каждом запуске программы, три чекбокса меняли своё положение. Как можно это сделать?

Re: Перетасовка объектов на форме

Добавлено: 29 май 2014, 11:58
Romeo
Не писал под QT, но почему-то уверен, что никаких проблем в том, чтобы "подвинуть" чекбокс на форме нету. Делаю вывод, что вызывает затруднение алгоритм изменения их положения. Как на счёт того, чтобы выяснить порядок чекбоксов, два раза вызывав rand()? Подробности нужны?

Re: Перетасовка объектов на форме

Добавлено: 29 май 2014, 12:30
somewhere
Бездумно вызывать rand() - нельзя. Надо еще следить, чтобы чекбоксы не пересекались.

Re: Перетасовка объектов на форме

Добавлено: 29 май 2014, 15:39
Romeo
Само собой не бездумно. Сначала rand()%3, потом rand()%2, причём с выравниванием. Но это уже подробности. Я же как раз спросил человека нужны ли подбробности или дальше уже сам догадается.

Re: Перетасовка объектов на форме

Добавлено: 29 май 2014, 18:05
jonyroww
Romeo писал(а):Само собой не бездумно. Сначала rand()%3, потом rand()%2, причём с выравниванием. Но это уже подробности. Я же как раз спросил человека нужны ли подбробности или дальше уже сам догадается.
Поподробней пожалуйста)

Re: Перетасовка объектов на форме

Добавлено: 30 май 2014, 12:25
Romeo
Хорошо, подробней, так подробней.

Сначала на пальцах, перенося задачу из программирования в реальный мир. Так понятнее суть уловить. Задача в том, чтобы N пронумерованных шариков, находящихся в мешке, расположить в случайном порядке. Как мы будет действовать?

- Вытаскиваем из мешка произвольный шарик из N шариков, кладём его на первое место.
- Вытаскиваем из мешка произвольный шарик из N-1 шариков, кладём его на второе место.
...
- Вытаскиваем из мешка один оставшийся шарик, кладём его на N-ное место.

Теперь вернёмся обратно в мир программрования.

У нас есть функция rand(), которая возвращает при каждом вызове "большое" случайное число. Если мы хотим получить случайное число от 0 до N-1, то нужно вычислить остаток от деления этого большого числа на N. В формуле это будет выгляеть rand() % N.

Что дальше? С программной точки зрения удобнее не "вытаскивать шарики из мешка", а на каждом шагу в этом же мешке их просто отодвигать в сторонку. К примеру, если у нас пронумерованные шарики - это вектор целых чисел, то в этом векторе будет граница перемешанных шариков: изначально она будет прижата к левой границе вектора и не будет содержать ни одного числа слева от себя, а во время работы алгоритма будет двигаться к концу вектора.

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

От слов к делу:

Код: Выделить всё

#include <iostream>
#include <vector>
#include <algorithm>
#include <time.h>

void mix_vector(std::vector<int>& vec)
{
   if (vec.size() <= 1)
   {
      return; // вектор не нужно смешивать, если он пустой или содержит один элемент
   }

   srand(time(NULL)); // инициализация генератора случайных чисел

   int nBoundary = 0; // граница перемешанных шариков
   const int nRightBoundary = vec.size() - 1; // правая граница, до которой мы смешиваем.
   // Минус 1 потому, что последний шарик нет смысла выбирать, он и так остался один.

   while (nBoundary < nRightBoundary)
   {
      const int nNotMixedCount = vec.size() - nBoundary; // количество непемеремешанных шариков
      const int nSelectedNumber = rand() % nNotMixedCount; // номер шарика, который нужно отложить в сторону
      const int nSelectedIndex = nBoundary + nSelectedNumber; // индекс выбранного шарика в векторе
      std::swap(vec[nBoundary], vec[nSelectedIndex]); // отодвигаем выбранный шарик в сторонку
      ++nBoundary; // сдвигаем границу
   }
}

void print_vector(std::vector<int>& vec)
{
   std::vector<int>::const_iterator it = vec.begin();
   for (; it != vec.end(); ++it)
   {
      std::cout << *it << " ";
   }
   std::cout << std::endl;
}

int main()
{
   std::vector<int> vec;
   vec.push_back(0);
   vec.push_back(1);
   vec.push_back(2);

   print_vector(vec);
   mix_vector(vec);
   print_vector(vec);

   return 0;
}
Вывод программы:
0 1 2
2 0 1
Причём при повторных запусках, вторая строка меняется в произвольном порядке.

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

Re: Перетасовка объектов на форме

Добавлено: 02 июн 2014, 14:02
Romeo
Кому интересно, вот оптимизированный вариант функции:

Код: Выделить всё

template <class ElementType>
void mix_vector(std::vector<ElementType>& vec)
{
   if (vec.size() > 1)
   {
      srand(time(NULL));
      const int nRightBoundary = vec.size() - 1;
      for (int nBoundary = 0; nBoundary < nRightBoundary; ++nBoundary)
      {
         std::swap(vec[nBoundary],
                   vec[nBoundary + rand()%(vec.size() - nBoundary)]);
      }
   }
}

Re: Перетасовка объектов на форме

Добавлено: 04 июн 2014, 08:15
Сионист
Бездумно вызывать rand() - нельзя. Надо еще следить, чтобы чекбоксы не пересекались.
А как случайный порядок влияет на пересечения? Можно даже один раз вызвать rand() для генеринга индекса в массиве порядков, там всего то будет: 012, 021, 102, 120, 201 и 210, и того 6 вариантов. Для каждого варианта запомни заранее вычисленные координаты и присваивай их.

Re: Перетасовка объектов на форме

Добавлено: 09 июн 2014, 16:55
Romeo
Абсолютно верно, если нам известно, что всегда будет ровно три варианта ответа, то можно выбрать одну из шести возможных комбинаций, один раз вызвав rand() % 6.

Моё решение хоть и более сложное, но зато более общее. Как я понимаю, в программе-тесте, которую пишет топикстартер, скорее всего количество возможных вариантов ответов на вопрос будет варьироваться и зависеть от самого вопроса. Иными словами оно не всегда будет 3. Так что generic решение больше подойдёт.