Как создать 5-символьные комбинации строк (2 разные цифры, две одинаковые буквы и 1 буква) без дублирования - PullRequest
0 голосов
/ 08 сентября 2018

Я уже опубликовал аналогичный вопрос , но это РАЗНЫЙ вопрос.
Я пытаюсь сгенерировать комбинации из 5-символьных строк, состоящих из трех букв (ровно две равны и другая другая буква) и двух разных цифр, но я получил дублирование при попытке Сделай так.

Пример для ПРАВИЛЬНЫЕ комбинации:

82ccb  
b8cc7  
7c6dc  

Пример для НЕПРАВИЛЬНЫХ комбинаций:

22ddc  -> incorrect because the digits are equal and should be different
46ddd  -> incorrect because there are more than 2 equal letters
2t4cd  -> No 2 equal letters + 2 equal different letters  

Это код, который я использую:

LETTERS = 'bcdfghjklmnpqrstvwxz'
DIGITS = '2456789'

def aab12(letters=LETTERS, digits=DIGITS):
    """Generate the distinct 5-character strings consisting of three
    letters (two are equal and a repeated letter) and two digits (each one is different from the other).

    """
    letterdxs = set(range(5))
    combs = []
    for (d1, d2), (i, j), (l1, l2) in product(
            permutations(digits, 2),       # 2 digits (a repeated).
            combinations(range(5), 2),     # Positions for the 1st and 2nd digits.
            permutations(letters, 2)):     # 2 letters (a repeated).

        x, y, z = letterdxs.difference((i, j))
        s = set((x, y, z))
        # Choosing 2 positions for the repeated letters
        c1 = combinations((x, y, z), 2) 
        for c in c1:
            result = []
            result[i:i] = d1,
            result[j:j] = d2,
            result[c[0]:c[0]] = l1,
            result[c[1]:c[1]] = l1,
            # Choosing position for the last letter. This is position that was left
            letter_indx = (s.difference(c)).pop()
            result[letter_indx:letter_indx] = l2,
            combs.append(''.join(result))
    # Should be 478,800
    print(len(combs))
    return combs

def is_contain_dup(combos):
    s = set(combos)
    if len(s) != len(combos):
       print('found duplicates !')

is_contain_dup(aab12())

У меня есть дублирование, хотя длина в порядке.
Эта функция основана на этой математике:
enter image description here

  • Выбор 2 мест для разных цифр
  • Выбор 2 мест для повторного письма
  • Выбор другой буквы из последней буквы

Я не уверен, что является причиной дублирования, но, вероятно, это связано с выбором двух одинаковых букв + другой буквы.

Ответы [ 3 ]

0 голосов
/ 08 сентября 2018

Вот чисто грубая сила, наивный метод с 4-мя вложенными циклами:

LETTERS = 'bcdfghjklmnpqrstvwxz'
DIGITS = '2456789'
from itertools import permutations

def aab12_1(letters=LETTERS, digits=DIGITS):
    st=[]
    for fc in letters:
        for sc in letters:
            if sc==fc: continue
            for n1 in digits:
                for n2 in digits:
                    if n1==n2: continue 
                    st.append(''.join((fc,fc,sc,n1,n2)))
    di={e:[''.join(t) for t in permutations(e)] for e in st}    
    return {s for sl in di.values() for s in sl}    

>>> r=aab12_1()
>>> len(r)
478800

Это имеет O(n**4) сложность; то есть действительно плохо для более длинных строк. Однако примеры строк не такие длинные, и это более выполнимый подход для более коротких строк.

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

def aab12_2(letters=LETTERS, digits=DIGITS):
    st=set()
    for fc in letters:
        for sc in letters:
            if sc==fc: continue
            for n1 in digits:
                for n2 in digits:
                    if n1==n2: continue 
                    st.add(''.join(sorted((fc,fc,sc,n1,n2))))

    di={e:[''.join(t) for t in permutations(e)] for e in st}    
    return {s for sl in di.values() for s in sl}    

Это можно упростить чуть дальше:

from itertools import permutations, product, combinations

def aab12_3(letters=LETTERS, digits=DIGITS):
    let_combo=[x+y for x,y in product([e+e for e in letters],letters) if x[0]!=y]   
    n_combos={a+b for a,b in combinations(digits,2)}
    di={e:[''.join(t) for t in permutations(e)] for e in (x+y for x,y in product(let_combo, n_combos))} 
    return {s for sl in di.values() for s in sl} 

Это все еще имеет подразумеваемый O(n**3) с 3 products(), который является эквивалентом вложенного цикла для каждого. Однако каждый O быстрее, и общее время здесь составляет около 350 мс.

Итак, давайте оценим. Вот три функции сверху, рекурсивная функция Ajax1234 и функция itertools Рори Даултона:

from itertools import combinations, permutations, product

def aab12_1(letters=LETTERS, digits=DIGITS):
    st=[]
    for fc in letters:
        for sc in letters:
            if sc==fc: continue
            for n1 in digits:
                for n2 in digits:
                    if n1==n2: continue 
                    st.append(''.join((fc,fc,sc,n1,n2)))
    di={e:[''.join(t) for t in permutations(e)] for e in st}    
    return {s for sl in di.values() for s in sl}    

def aab12_2(letters=LETTERS, digits=DIGITS):
    st=set()
    for fc in letters:
        for sc in letters:
            if sc==fc: continue
            for n1 in digits:
                for n2 in digits:
                    if n1==n2: continue 
                    st.add(''.join(sorted((fc,fc,sc,n1,n2))))
    di={e:[''.join(t) for t in permutations(e)] for e in st}    
    return {s for sl in di.values() for s in sl}    


def aab12_3(letters=LETTERS, digits=DIGITS):
    let_combo=[x+y for x,y in product([e+e for e in letters],letters) if x[0]!=y]   
    n_combos={a+b for a,b in combinations(digits,2)}
    di={e:[''.join(t) for t in permutations(e)] for e in (x+y for x,y in product(let_combo, n_combos))} 
    return {s for sl in di.values() for s in sl} 

def aab12_4():
# Ajax1234 recursive approach
    def validate(val, queue, counter):
        if not queue:
            return True
        if val.isdigit():
            return sum(i.isdigit() for i in queue) < 2 and val not in queue
        _sum = sum(i.isalpha() for i in counter)
        return _sum < 3 and counter.get(val, 0) < 2

    def is_valid(_input):
        d = Counter(_input)
        return sum(i.isdigit() for i in d) == 2 and sum(i.isalpha() for i in d) == 2

    def combinations(d, current = []):
        if len(current) == 5:
            yield ''.join(current)
        else:
            for i in d:
                if validate(i, current, Counter(current)):
                    yield from combinations(d, current+[i])

    return [i for i in combinations(DIGITS+LETTERS) if is_valid(i)]


def aab12_5(letters=LETTERS, digits=DIGITS):
    """ Rory Daulton
    Generate the distinct 5-character strings consisting of three
    letters (two are equal and a repeated letter) and two digits (each
    one is different from the other).
    """
    indices = range(5)  # indices for the generated 5-char strings
    combs = []
    for (letterdbl, lettersngl), (digit1, digit2), (indx1, indx2, indx3) in (
            product(permutations(letters, 2),
            combinations(digits, 2),
            permutations(indices, 3))):
        charlist = [letterdbl] * 5
        charlist[indx1] = lettersngl
        charlist[indx2] = digit1
        charlist[indx3] = digit2
        combs.append(''.join(charlist))
    return combs    

if __name__=='__main__':
    import timeit
    funcs=(aab12_1,aab12_2,aab12_3,aab12_4,aab12_5)
    di={f.__name__:len(set(f())) for f in funcs}
    print(di)
    for f in funcs:
        print("   {:^10s}{:.4f} secs".format(f.__name__, timeit.timeit("f()", setup="from __main__ import f", number=1))) 

Печать:

{'aab12_1': 478800, 'aab12_2': 478800, 'aab12_3': 478800, 'aab12_4': 478800, 'aab12_5': 478800}
   aab12_1  0.6230 secs
   aab12_2  0.3433 secs
   aab12_3  0.3292 secs
   aab12_4  50.4786 secs
   aab12_5  0.2094 secs

Самым быстрым здесь является функция itertools Рори Донтона. Отлично сделано!

0 голосов
/ 08 сентября 2018

Вот один ответ, который использует несколько функций в itertools. Моя стратегия изложена в комментариях. В функциях itertools делается как можно больше циклов, чтобы максимизировать скорость. Это возвращает желаемые 478 800 строк, которые все различны.

Запуск %timeit на aab12() в моей системе дает результат времени

391 ms ± 2.34 ms per loop

.

"""
Strategy: Generate a permutation of 2 distinct characters, a
combination of 2 distinct digits, and a permutation of 3 distinct
indices in range(5). Then build a 5-char string of the first character
(which will be the repeated one), use the first two indices to place
the digits and the last index to place the non-repeated character.
This yields a total of (20*19) * (7/1*6/2) * (5*4*3) = 478,800
items.
"""
from itertools import combinations, permutations, product

LETTERS = 'bcdfghjklmnpqrstvwxz'
DIGITS = '2456789'

def aab12(letters=LETTERS, digits=DIGITS):
    """Generate the distinct 5-character strings consisting of three
    letters (two are equal and a repeated letter) and two digits (each
    one is different from the other).
    """
    indices = range(5)  # indices for the generated 5-char strings
    combs = []
    for (letterdbl, lettersngl), (digit1, digit2), (indx1, indx2, indx3) in (
            product(permutations(letters, 2),
                    combinations(digits, 2),
                    permutations(indices, 3))):
        charlist = [letterdbl] * 5
        charlist[indx1] = digit1
        charlist[indx2] = digit2
        charlist[indx3] = lettersngl
        combs.append(''.join(charlist))
    return combs

Первые пятнадцать строк в списке

['24cbb',
 '24bcb',
 '24bbc',
 '2c4bb',
 '2b4cb',
 '2b4bc',
 '2cb4b',
 '2bc4b',
 '2bb4c',
 '2cbb4',
 '2bcb4',
 '2bbc4',
 '42cbb',
 '42bcb',
 '42bbc']
0 голосов
/ 08 сентября 2018

Вы можете создать рекурсивную функцию:

from collections import Counter
LETTERS = 'bcdfghjklmnpqrstvwxz'
DIGITS = '2456789'
def validate(val, queue, counter):
  if not queue:
    return True
  if val.isdigit():
    return sum(i.isdigit() for i in queue) < 2 and val not in queue
  _sum = sum(i.isalpha() for i in counter)
  return _sum < 3 and counter.get(val, 0) < 2

def is_valid(_input):
  d = Counter(_input)
  return sum(i.isdigit() for i in d) == 2 and sum(i.isalpha() for i in d) == 2

def combinations(d, current = []):
  if len(current) == 5:
    yield ''.join(current)
  else:
    for i in d:
      if validate(i, current, Counter(current)):
        yield from combinations(d, current+[i])

_r = [i for i in combinations(DIGITS+LETTERS) if is_valid(i)]
print(len(_r))

Выход:

478800
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...