2016-07-05 4 views
2

Что было бы лучшим aproach для сравнения 2 комбинаций?сравнение комбинаций

Моя ситуация:

Я в настоящее время string string1 = "xxxxxx";. Длина - 6 символов. Каждое значение char равно 0 или 1 или x. Мне нужно сравнить это string к другому string, который имеет Eact одинаковое количество символов, но значения являются либо 1 или 0

  • символ x в первом string означает, что символьное значение во втором string может быть что угодно

  • символ 0 в первом string - принимает только 0 во втором string

  • символ 1 в первом string - принимает только 1 в второй string

Вот краткий пример:

string pattern = 'xxxxxx'; 
string test1 = '010101'; 
// pass 

string pattern = '1xxxxx'; 
string test2 = '010101'; 
// not pass 

string pattern = '0xxxxx'; 
string test3 = '010101'; 
// pass 

Я сделал функцию для этого:

public bool passCombination(string pattern, string combination) 
    { 
     bool combination_passed = true; 
     for (int i = 0; i < pattern.Length; i++) 
     { 
      char test_char = pattern[i]; 
      if (test_char != 'x' && combination[i] != test_char) 
      { 
       combination_passed = false; 
       break; 
      } 
     } 

     return combination_passed; 
    } 

Это довольно просто. В основном я повторяю char после char. Если это x, тогда меня не интересует значение во второй строке. Если это другой символ, то сравните.

Так как это строка, основанная на aproach, я думал о другом решении, возможно? В моем реальном сценарии я должен выполнить примерно (~ 700 тыс. Таких проверок * ~ 1,5 млн раз). И это очень оптимистичное число :)

Я думал о regex сравнении или сохранение всех комбинаций в массивы int[] и их сравнение. Или, может быть, может быть сделано какое-то волшебство, используя hashes?

Значит, это как минимум 3 других варианта, которые может увеличить производительность. Может ли кто-нибудь предложить более эффективное решение?

Edit:

Я запускать тесты сравнения. Со старым aproach я получал 2,5 минуты времени исполнения и с новым предложенным ниже предложением (принятый ответ) - около 2 минут. Это примерно 20% увеличение производительности.

+0

Я не понимаю, почему вы сравниваете все символы. Если первый символ в вашем patterm уже решает успех вашей логики, вы можете пропустить другую 5 – lokusking

+1

, поэтому существует функция 'break' – Alex

+0

Упрощение для возврата' false' в первый несоответствие, а затем возврат ' true' в конце. Не спасет вас никаких циклов, хотя, по крайней мере, недостаточно, чтобы заботиться. –

ответ

4

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

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

Предостережение: Если вы никогда не будете или редко будете сравнивать любую заданную строку «значение» более одного раза, нет никакого преимущества для этого подхода, поскольку компиляция включает в себя циклическое перемещение по строке в любом случае.

Если у вас есть проблемы с производительностью, можно «собрать» картину в два целых числа: одна модель с 1 для каждого 1 и 0 для каждого 0 или x; другая - маска с 0 за каждые x и 1 за каждые 0 или 1. Вы тратите 26 бит на целое число, но я никому не скажу.

Затем компилировать значения в Интс: 1 для 1, 0 для 0.

Напишите класс, который имеет эти шаблоны/маски ints и метод для их сравнения со значением int. Вы бы «прекомпилировали» «значения» и сохранили их как целые числа вместо строк, или, может быть, класс, который имеет свойство int и свойство string, если вам нужно будет отображать их (или вы могли бы написать функцию для преобразования эти ints обратно в строку).

public class PatternMatcher 
{ 
    public PatternMatcher(String pattern) 
    { 
     Pattern = CompilePattern(pattern); 
     Mask = CompileMask(pattern); 
    } 

    #region Fields 
    // Could we save any cycles by making these fields instead of properties? 
    // I think the optimizer is smarter than that. 
    public int Pattern { get; private set; } 
    public int Mask { get; private set; } 
    #endregion Fields 

    public bool CheckValue(String value) 
    { 
     return CheckValue(CompileValue(value)); 
    } 

    public bool CheckValue(int value) 
    { 
     // a & b: Bitwise And 
     //  Any bit that's "true" in both numbers is "true" in the result. 
     //  Any bit that's "false" in EITHER number is "false" in the result. 

     //  11 & 11 == 11 
     //  11 & 01 == 01 
     //  11 & 10 == 10 
     //  11 & 00 == 00 

     //  01 & 11 == 01 
     //  01 & 01 == 01 
     //  01 & 10 == 00 
     //  01 & 00 == 00 

     // So xx0011 -> 
     //  Pattern: 000011 
     //  Mask: 001111 
     //  Value 110011 

     // (110011 & 001111) == 000011 
     // (000011 & 001111) == 000011 
     // 
     // 000011 == 000011, so these two match. 

     return (value & Mask) == (Pattern & Mask); 
    } 

    public static int CompileMask(string patternString) 
    { 
     int mask = 0; 
     int bitoffset = 0; 

     // For each character in patternString, set one bit in mask. 
     // Start with bit zero and move left one bit for each character. 
     // On x86, these bits are in reverse order to the characters in 
     // the strings, but that doesn't matter. 
     foreach (var ch in patternString) 
     { 
      switch (ch) 
      { 
       // If the pattern has a '0' or a '0', we'll be examining that 
       // character in the value, so put a 1 at that spot in the mask. 
       case '1': 
       case '0': 
        // a | b: Bitwise OR: If a bit is "true" in EITHER number, it's 
        // true in the result. So 0110 | 1000 == 1110. 
        // a << b: Bitwise left shift: Take all the bits in a and move 
        // them leftward by 1 bit, so 0010 << 1 == 0100. 
        // 
        // So here we shift 1 to the left by some number of bits, and 
        // then set that bit in mask to 1. 
        mask |= 1 << bitoffset; 
        break; 

       // If it's an 'x', we'll ignore that character in the value by 
       // putting a 0 at that spot in the mask. 
       // All the bits are zero already. 
       case 'x': 
        break; 

       default: 
        throw new ArgumentOutOfRangeException("Invalid pattern character: " + ch); 
      } 

      ++bitoffset; 
     } 

     return mask; 
    } 

    public static int CompilePattern(string patternString) 
    { 
     int pattern = 0; 
     int bitoffset = 0; 

     foreach (var ch in patternString) 
     { 
      // For each character in patternString, set one bit in pattern. 
      // Start with bit zero and move left one bit for each character. 
      switch (ch) 
      { 
       // If the pattern has a 1, require 1 in the result. 
       case '1': 
        pattern |= 1 << bitoffset; 
        break; 

       // For 0, require 0 in the result. 
       case '0': 
        // All the bits were zero already so don't waste time setting 
        // it to zero. 
        break; 

       // Doesn't matter what we do for 'x', since it'll be masked out. 
       // Just don't throw an exception on it. 
       case 'x': 
        break; 

       default: 
        throw new ArgumentOutOfRangeException("Invalid pattern character: " + ch); 
      } 

      ++bitoffset; 
     } 

     return pattern; 
    } 

    public static int CompileValue(string valueString) 
    { 
     int value = 0; 
     int bitoffset = 0; 

     // For each character in patternString, set one bit in mask. 
     // Start with bit zero and move left one bit for each character. 
     foreach (var ch in valueString) 
     { 
      switch (ch) 
      { 
       // If the value has a '1', have a 1 for that bit 
       case '1': 
        value |= 1 << bitoffset; 
        break; 

       // If the value has a '0', leave a 0 for that bit 
       // All the bits were zero already. 
       case '0': 
        break; 

       default: 
        throw new ArgumentOutOfRangeException("Invalid pattern character: " + ch); 
      } 

      ++bitoffset; 
     } 

     return value; 
    } 
} 

Очевидно, что вы тратите свое время здесь, если вы не можете перекомпилировать свои ценности и хранить их как целые числа (и это большое «если»). Но если вы можете, вы создаете один из них для каждого шаблона и используете его 700k + times в цикле. Скорее всего, это быстрее, чем цикл 700k + раз.

+0

Я должен быть что-то не так .. 'Mask' и' Pattern' всегда '0' после инициализации с шаблоном. – Alex

+0

@Alex Это не вы делаете что-то не так, это мой код. Я только что нашел эту ошибку, и сейчас я перехожу через CompilePattern. Еще немного. –

+0

@Alex Derp, 'mask | = 1 << bitoffset;'; он должен быть поразным или. Побитовая нуль против 1 не делает много. Должен иметь низкий IQ-день. Исправлено и обновлено. Сейчас работает для меня. –

0

Чувство похоже на вопрос, возможно, отсутствует какая-то важная информация. Например, если значения хранятся в базе данных, то хранимая процедура должна быть быстрее, чем загрузка всех данных в памяти.

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

Кроме того, если вы проверяете около миллиона шаблонов против около миллиона комбинаций, то есть лучшие алгоритмы, чтобы получить текущую сложность от O (n^2) до чего-то более близкого к O (n) http://bigocheatsheet.com/#chart

Для сравнения t он и комбинация, то, что у вас сейчас, должно быть немного быстрее, чем преобразование строк в целые числа. Если вы сравниваете один шаблон с несколькими комбинациями, то что может немного ускорить его, если вы сохраните позиции, которые не являются x, и сравнивайте их. Например:

foreach (string pattern in patterns) 
{ 
    // save the non x indexes 
    var indexes = new List<int>(); 
    for (int i = 0; i < pattern.Length; i++) 
     if (pattern[i] != 'x') 
      indexes.Add(i); 

    foreach (string combination in combinations) 
    { 
     bool combination_passed = true; 
     foreach (int i in indexes) 
     { 
      if (combination[i] != pattern[i]) 
      { 
       combination_passed = false; 
       break; 
      } 
     } 
     // ... 
    } 
}