Как я могу ускорить этот алгоритм добавления грубой силы в Objective-C?

Я пытаюсь узнать немного больше об алгоритмах и построил простой, чтобы увидеть, используя грубую силу, можно ли сделать целевое число из сетки случайно созданных чисел. Я сделал достаточно, чтобы проверить, создадут ли вместе пять чисел сетки цель, которой должно быть достаточно для цели, которую я имел в виду, но процесс ОЧЕНЬ медленный, около 11 секунд на симуляторе iOS. Как я могу ускорить процесс здесь? Есть ли более эффективный способ сделать это, чем использовать все циклы, которые я использую? Вот весь мой код, GridNumber — это простой подкласс NSObject, который содержит два целых числа, number и tag.

- (void)viewDidLoad
 {
      [super viewDidLoad];

      // 0. Set up target number.
      int random = arc4random() % 100 + 3;
      NSNumber *target = [NSNumber numberWithInt: random];

      // 1. Set up array of available numbers.
      NSMutableArray *grid = [[NSMutableArray alloc] init];
      for (int i = 1; i < 48; i++) {
           GridNumber *number = [[GridNumber alloc] initWithRandomIntegerAndTag: i];
           [grid addObject: number];
      }

      if ([self canTarget: target BeMadeFromGrid: grid]) NSLog(@"--- SOLVEABLE!");
      else NSLog(@"--- UNSOLVEABLE!");
 }

 - (BOOL) canTarget: (NSNumber *) target BeMadeFromGrid: (NSArray *) grid
 {
      NSLog(@"TARGET NUMBER IS: %d", target.intValue);

      // 2. See if the target already exists first.
      for (GridNumber *firstValue in grid) {
           if (firstValue.number == target.intValue) {
                NSLog(@"SOLVEABLE IN 1: Grid already contains target!");
                return YES;
           }
      }

      // 3. Add elements once, see if any of those give the result.
      for (GridNumber *firstValue in grid) {
           for (GridNumber *secondValue in grid) {
                int result = firstValue.number + secondValue.number;
                if (result == target.intValue && firstValue.tag != secondValue.tag) {
                     NSLog(@"SOLVEABLE IN 2: Adding %d and %d creates target!", firstValue.number, secondValue.number);
                     return YES;
                }
           }
      }

      // 4. Add elements twice, see if any of those give the result.
      for (GridNumber *firstValue in grid) {
           for (GridNumber *secondValue in grid) {
                for (GridNumber *thirdValue in grid) {
                     int result = firstValue.number + secondValue.number + thirdValue.number;
                     if (result == target.intValue && firstValue.tag != secondValue.tag && firstValue.tag != thirdValue.tag && secondValue.tag != thirdValue.tag) {
                          NSLog(@"SOLVEABLE IN 3: Adding %d, %d and %d creates target!", firstValue.number, secondValue.number, thirdValue.number);
                          return YES;
                     }
                }
           }
      }

      // 5. And three times..
      for (GridNumber *firstValue in grid) {
           for (GridNumber *secondValue in grid) {
                for (GridNumber *thirdValue in grid) {
                     for (GridNumber *fourthValue in grid) {
                          int result = firstValue.number + secondValue.number + thirdValue.number + fourthValue.number;
                          if (result == target.intValue && firstValue.tag != secondValue.tag && firstValue.tag != thirdValue.tag && firstValue.tag != fourthValue.tag &&
                              secondValue.tag != thirdValue.tag && secondValue.tag != fourthValue.tag && thirdValue.tag != fourthValue.tag) {
                               NSLog(@"SOLVEABLE IN 4: Adding %d, %d, %d and %d creates target!", firstValue.number, secondValue.number, thirdValue.number, fourthValue.number);
                               return YES;
                          }
                     }
                }
           }
      }

      // 6. And four times..
      for (GridNumber *firstValue in grid) {
           for (GridNumber *secondValue in grid) {
                for (GridNumber *thirdValue in grid) {
                     for (GridNumber *fourthValue in grid) {
                          for (GridNumber *fifthValue in grid) {
                               int result = firstValue.number + secondValue.number + thirdValue.number + fourthValue.number + fifthValue.number;
                               if (result == target.intValue && firstValue.tag != secondValue.tag && firstValue.tag != thirdValue.tag && firstValue.tag != fourthValue.tag &&
                                   firstValue.tag != fifthValue.tag && secondValue.tag != thirdValue.tag && secondValue.tag != fourthValue.tag && secondValue.tag != fifthValue.tag &&
                                   thirdValue.tag != fourthValue.tag && thirdValue.tag != fifthValue.tag && fourthValue.tag != fifthValue.tag) {
                                    NSLog(@"SOLVEABLE IN 5: Adding %d, %d, %d, %d and %d creates target!", firstValue.number, secondValue.number, thirdValue.number, fourthValue.number, fifthValue.number);
                                    return YES;
                               }
                          }
                     }
                }
           }
      }

      // 7. This is if it can't be made.
      return NO;
 }

person Luke    schedule 16.05.2013    source источник
comment
Разделите задачи на несколько потоков.   -  person Shark    schedule 16.05.2013
comment
@Shark Я больше думал об эффективности самого алгоритма.   -  person Luke    schedule 16.05.2013
comment
В таком случае просто перепишите его, а грубо говоря - это просто фигня. вы делаете O (n), затем O (n ^ 2), затем O (n ^ 3), затем O (n ^ 4) только для того, чтобы получить O (n ^ 5) ... Вы сэкономите время, если вы пробовали только последний вариант, но похоже, что вы пытаетесь найти комбинацию из набора, которая может составить число, верно? Почему бы не изучить математическую подоплеку проблемы, прежде чем приступать к коду? Если бы вы это сделали, вы бы обнаружили, что эта задача о сумме подмножеств является NP-сложной и ни в коем случае не является чем-то, что нужно пытаться «при изучении алгоритмов».   -  person Shark    schedule 16.05.2013
comment
@Shark Я понятия не имею, что означает O (n), я новичок в разработке алгоритмов, и мне бы хотелось, чтобы некоторые конкретные советы по поводу того, как переписать алгоритм, а не что-то концептуальное, чего я не понимаю.   -  person Luke    schedule 16.05.2013
comment
В этом случае почитайте об алгоритмах, так как нет смысла обсуждать дизайн и эффективность алгоритмов, если вы еще не усвоили базовые понятия, такие как the big O (en.wikipedia.org/wiki/Big_O_notation)   -  person Shark    schedule 16.05.2013
comment
Проще говоря, при задании grid of 5 elements первый цикл, равный O(n), выполняется до 5 раз. Во-вторых, O(n^2) выполняется до 5*5 = 25 раз... Читателю остается выполнить упражнение, чтобы понять, почему O(n^5) — это плохо.   -  person Shark    schedule 16.05.2013
comment
@Shark Честно говоря, это совсем не полезный ответ. Я пытаюсь подойти к этому с точки зрения программирования, а не с точки зрения студента-информатика, изучающего теоретически, и я пытаюсь понять особенности проблем, возникающих в моем коде. Теоретические концепции кажутся мне за тысячу миль от того, чем я здесь занимаюсь в данный момент.   -  person Luke    schedule 16.05.2013
comment
Как программист программисту, вы должны знать, сколько итераций ожидать, прежде чем писать это, и как их сократить. Если вы считаете, что сократили количество итераций до минимально возможного минимума, и это все еще очень медленно, пересмотрите свой проект.   -  person Shark    schedule 16.05.2013
comment
@Shark Я знаю, что может произойти много итераций, но я спрашиваю, как бы я ограничил это число, это было самой основой вопроса. Какие другие подходы доступны для этой проблемы? пересмотр моего дизайна - именно поэтому я задал этот вопрос здесь.   -  person Luke    schedule 16.05.2013
comment
Вы выбрали не ту проблему, чтобы попытаться найти тривиальные решения, это все, что я хочу сказать :D   -  person Shark    schedule 16.05.2013
comment
@Shark Ты говоришь это в «Никогда не беспокойся». путь. С чего ты взял, что я ищу тривиальное решение? Я прошу совета о том, как действовать программно, и все, что вы, кажется, говорите мне, это то, что вы не можете этого сделать. Это несколько расстраивает.   -  person Luke    schedule 16.05.2013
comment
Люкеч, я сказал вам изучить предысторию проблемы, и вы хотите сразу перейти к коду. Если вы действительно чувствуете себя уверенно, вы можете попробовать написать эвристику или выполнить некоторую гимнастику динамического программирования ИЛИ даже просто попробовать решение O (N ^ 5) без предыдущих четырех случаев, и все это сократит ваше время выполнения. Тем не менее, попробуйте это на бумаге вручную. Вы увидите, что это довольно сложно сделать даже на тестовом примере. Я не говорю, что не беспокойтесь — я говорю узнайте своего врага, прежде чем атаковать.   -  person Shark    schedule 16.05.2013
comment
Но ваше отношение к тому, что я не знаю, что означает O (n), и я хочу решить это с помощью кода, мне не нужна теория, также очень обесценивает мое предложение. Зачем разговаривать с плотной стеной. Если вы хотите кодировать и просто кодировать — вперед, кодируйте. Если вы хотите понять, что кодирование будет совсем не простым, вы можете изучить комбинаторные решения, которые могут быть основаны на моделировании Монте-Карло. По сути, «пробуй, пока не получится» какое-то время, как ты делаешь сейчас. Если вам это удастся, вы сможете доказать гипотезу Гольдбаха как побочный продукт.   -  person Shark    schedule 16.05.2013
comment
@Shark: Этот простой мусор также называется итеративным углублением и (в более общей форме, где рекурсия используется для добавления любого количества элементов вместо ограничения до 5) является хорошей стратегией для поиска в больших пространствах. Перестаньте грубить, и прежде чем обвинять кого-то в глупости, убедитесь, что это действительно глупо.   -  person j_random_hacker    schedule 17.05.2013


Ответы (3)


Основываясь на идее @torquestomp, вот некоторый код C, который мне удалось быстро собрать. Для сетки из 48 чисел (в диапазоне от 1 до 21) при поиске цели меньше 203 выполнение почти никогда не занимает больше нескольких сотых секунды. Время выполнения будет увеличиваться, если вы допускаете более длинные решения (более 5 или 6 чисел). Обратите внимание, что я не полностью протестировал эту функцию. Сообщаемое время указано на Mac. На iPhone они будут медленнее.

Редактировать: если вы сортируете список чисел в порядке убывания, вы должны сначала найти «оптимальные» (меньшее количество чисел в сумме) решения.

typedef void(^execution_block_t)(void);

double time_execution(execution_block_t aBlock);
double time_execution(execution_block_t aBlock)
{
    uint64_t time0 = mach_absolute_time();
    aBlock();
    uint64_t time1 = mach_absolute_time();
    return (double)(time1 - time0)/NSEC_PER_SEC;
}

static int totalTests = 0;

int findPartialSum(int target, int *grid, int gridCount, int startIndex, int *solution, int depth, int maxDepth)
{
    for (int i=startIndex;  i<gridCount;  i++) {

        int newTarget = target - grid[i];
        totalTests++;

        if (newTarget == 0) {
            solution[depth-1] = i;
            return 1;
        }

        if (newTarget > 0 && depth < maxDepth) {
            int found = findPartialSum(newTarget, grid, gridCount, i+1, solution, depth+1, maxDepth);
            if (found > 0) {
                solution[depth-1] = i;
                return found + 1;
            }
        }
    }
    return 0;
}


int main (int argc, const char * argv[])
{
    @autoreleasepool {

        static const int gridSize = 48;
        static const int solutionSize = 5;

        int *solution = calloc(sizeof(int), solutionSize);
        int *grid     = calloc(sizeof(int), gridSize);
        int  target   = arc4random_uniform(200) + 3;

        for (int i=0;  i<gridSize;  i++)
            grid[i] = arc4random_uniform(20) + 1;

        NSMutableArray *numbers = [NSMutableArray array];
        for (int i=0;  i<gridSize;  i++)
            [numbers addObject:@(grid[i])];

        NSLog(@"\nTarget = %d\nGrid = %@", target, [numbers componentsJoinedByString:@","]);

        __block int count = 0;
        double elapsedTime = time_execution(^(void) {
            count = findPartialSum(target, grid, gridSize, 0, solution, 1, solutionSize);
        });

        NSLog(@"Looking for solution with up to %d numbers", solutionSize);
        if (count > 0) {

            [numbers removeAllObjects];
            for (int i=0;  i<count;  i++)
                [numbers addObject:@(grid[solution[i]])];

            NSLog(@"Found solution with %d numbers: %@", count, [numbers componentsJoinedByString:@","]);

        } else {
            NSLog(@"No solution found");
        }

        NSLog(@"After looking at %d possible sums",totalTests);
        NSLog(@"Elapsed time was %fs", elapsedTime);

        free(solution);
        free(grid);
    }
    return 0;
}

Некоторые примеры результатов:

Target = 159
Grid = 16,18,19,6,18,5,12,7,7,4,18,3,7,13,10,19,7,14,19,6,16,4,8,4,3,17,11,16,5,8,18,9,4,13,14,8,17,18,13,5,20,14,4,5,13,20,17,1
Looking for solution with up to 5 numbers
No solution found
After looking at 1925356 possible sums
Elapsed time was 0.014727s


Target = 64
Grid = 4,6,1,1,13,12,15,10,11,6,18,6,8,2,15,3,18,5,20,1,3,12,20,3,18,5,1,12,15,14,2,20,9,1,14,9,6,1,2,10,12,7,7,4,2,12,20,6
Looking for solution with up to 5 numbers
Found solution with 5 numbers: 4,6,18,18,18
After looking at 7271 possible sums
Elapsed time was 0.000048s
person aLevelOfIndirection    schedule 16.05.2013

Это известно как проблема с суммой подмножеств, и известно, что она является NP-сложной, поэтому вам не повезло, если вы ищете действительно эффективное решение. NP-сложный означает, что не существует известного полиномиального (то есть быстрого) решения относительно размера входных данных (количества чисел в сетке). Понимание нотации «большое О» является рудиментарным для того, чтобы воспользоваться этим знанием, так что кажется, что это хорошее место для начала.

Однако, поскольку вы используете только положительные целые числа, а целевое число всегда находится в диапазоне [3,102], с помощью динамического программирования доступно решение с псевдополиномиальным временем. Как уже упоминал @Shark, это, вероятно, то, на чем вы действительно хотите сосредоточиться сейчас - если вы не понимаете основ рекурсивного решения проблем, сразу же решать известную NP-сложную проблему - не лучшая идея. ;)

В псевдокоде вы хотите сделать что-то вроде этого:

Define array on [0,102] representing reachable numbers.  Set 0 to be reachable
for each NSNumber in grid:
    Looping upwards, for every reachable target in [3,102], set target + NSNumber to be reachable too
    If the sum exceeds 102, cut the loop
Return true if, after checking all numbers, target is reachable

Этот обобщенный алгоритм выполняется за время O(N*M) для положительных целых чисел, где N — количество чисел в сетке, а M — максимально возможная цель. Для N = 48 и M = 102 это должно работать лучше, чем алгоритм O (N ^ 5), который вы сейчас используете.

person torquestomp    schedule 16.05.2013
comment
Я изо всех сил пытаюсь понять многое из того, что вы здесь сказали. Псевдокод не имеет для меня большого смысла, и я не уверен, что такое динамическое программирование. В моем коде должно быть указано, что повторное использование чисел НЕ разрешено, для чего нужны все && и теги, гарантирующие, что одно и то же число в сетке не используется дважды. - person Luke; 16.05.2013
comment
палец вверх за упоминание NP-твердости :) - person Shark; 16.05.2013

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

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

person Shark    schedule 16.05.2013
comment
идея неплохая, но может стать непомерно дорогой для вычислений с точки зрения памяти. Для решения из 5 чисел существует около 200 миллионов возможностей. Даже если сократить это число на 4 путем усечения ветвей, вы все равно получите 50 миллионов записей в наборе. - person aLevelOfIndirection; 17.05.2013
comment
Хотя вы нападаете на ОП за то, что пишете простой мусор и невежественны, вы, по-видимому, не знаете, что подход ОП называется итеративным углублением и является не только действительной, но и разумной стратегией решения сложных задач поиска (например, шахматы). Тот факт, что, например. каждая комбинация из 4 чисел регенерируется при попытке каждой комбинации из 5 чисел не имеет значения ни в теории, ни на практике, потому что предыдущий цикл (и все предшествующие ему циклы) в любом случае занимает только O (1/n), пока последний . Это так же плохо, как добавление дополнительного шага O (1) к алгоритму O (n). - person j_random_hacker; 17.05.2013