генерация телефонных номеров с использованием определенного набора правил в Python - PullRequest
0 голосов
/ 10 декабря 2018

Я хочу написать функцию, которая генерирует все возможные номера со стандартной клавиатуры телефона (рисунок 1), используя следующий набор правил:

  • номера телефонов начинаются с цифры 2
  • номер телефона состоит из 10 цифр
  • последовательные цифры в каждом номере телефона выбираются как ход рыцаря в шахматах

В шахматах рыцарь (иногда называемый лошадью)) перемещает два шага по вертикали и один шаг по горизонтали ИЛИ два шага по горизонтали и один шаг по вертикали.

enter image description here

В телефоне можно использовать только цифровые цифрыцифры - т. е. клавиши (#) и (*) недопустимы.

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

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

def genNumbers(len, initpos):
numb = list('2xxxxxxxxx')

#index 1
numb[1] = 7 or 9

if numb[1] == 7:
    numb[2] == 2 or 6
elif numb[1] == 9:
    numb[2] == 2 or 4

#index 2
if numb[2]== 2:
    numb[3] == 7 or 9
elif numb[2]== 4:
    numb[3] == 3 or 9
elif numb[2]== 6:
    numb[3] == 1 or 7

#index 3
if numb[3]== 1:
    numb[4] == 6 or 8  
elif numb[3]== 3:
    numb[4] == 4 or 8 
elif numb[3]== 7:
    numb[4] == 2 or 6 
elif numb[3]== 9:
    numb[4] == 2 or 4 

#index 4
if numb[4] == 8:
    numb[5]== 1 or 3
elif numb[4] == 2:
    numb[5]== 7 or 9
elif numb[4] == 4:
    numb[5]== 3 or 9
elif numb[4] == 6:
    numb[5]== 1 or 7

#index 5
if numb[5] == 1:
    numb[6]== 6 or 8
elif numb[5] == 3:
    numb[6]== 4 or 8
elif numb[5] == 7:
    numb[6]== 2 or 6 
elif numb[5] == 9:
    numb[6]== 2 or 4

#index 6 
if numb[6] == 2:
    numb[7]== 7 or 9
elif numb[6] == 4:
    numb[7]== 3 or 9 
elif numb[6] == 6:
    numb[7]== 1 or 7
elif numb[6] == 8:
    numb[7]== 1 or 3

#index 7 
if numb[7] == 1:
    numb[8]== 6 or 8
elif numb[7] == 3:
    numb[8]== 4 or 8
elif numb[7] == 7:
    numb[8]== 2 or 6   
elif numb[7] == 9:
    numb[8]== 2 or 4

#index 8
if numb[8] == 6:
    numb[9]== 1 or 7
elif numb[8] == 8:
    numb[9]== 1 or 3
elif numb[8] == 4:
    numb[9]== 3 or 9
elif numb[8] == 2:
    numb[9]== 7 or 9


return numb

Любая помощь будет принята с благодарностью!

Ответы [ 2 ]

0 голосов
/ 10 декабря 2018

Prelude

Позволяет указать другой способ решения вашей проблемы, который не связан с линейной алгеброй, но все еще полагается на теорию графов.

Представление

Естественное представление вашей проблемыГрафик, как показано ниже:

enter image description here

И эквивалентен:

enter image description here

Мы можем представить этот график с помощью словаря:

G = {
    0: [4, 6],
    1: [6, 8],
    2: [7, 9],
    3: [4, 8],
    4: [0, 3, 9],
    5: [],  # This vertex could be ignored because there is no edge linked to it
    6: [0, 1, 7],
    7: [2, 6],
    8: [1, 3],
    9: [2, 4],
}

Такая структура избавит вас от необходимости писать if операторов.

Матрица смежности

Представленное выше представление содержит ту же информацию, что и матрица смежности.Более того, мы можем сгенерировать его из приведенной выше структуры (преобразование логической разреженной матрицы в интегральную матрицу):

def AdjacencyMatrix(d):
    A = np.zeros([len(d)]*2)
    for i in d:
        for j in d[i]:
            A[i,j] = 1
    return A

C = AdjacencyMatrix(G)
np.allclose(A, C) # True

Где A - Матрица смежности, определенная в другой ответ .

Рекурсия

Теперь мы можем сгенерировать все телефонные номера, используя рекурсию:

def phoneNumbers(n=10, i=2, G=G, number='', store=None):
    if store is None:
        store = list()
    number += str(i)
    if n > 1:
        for j in G[i]:
            phoneNumbers(n=n-1, i=j, G=G, number=number, store=store)
    else:
        store.append(number)
    return store

Затем создаем список телефонных номеров:

plist = phoneNumbers(n=10, i=2)

Возвращает:

['2727272727',
 '2727272729',
 '2727272760',
 '2727272761',
 '2727272767',
 ...
 '2949494927',
 '2949494929',
 '2949494940',
 '2949494943',
 '2949494949']

Теперь нужно просто взять длину списка:

len(plist) # 1424

Проверки

Мы можем проверить, нет ли дубликатов:

len(set(plist)) # 1424

Мы можем проверить, что наблюдение, которое мы сделали относительно последней цифры в другом ответе, все еще сохраняется в этой версии:

d = set([int(n[-1]) for n in plist]) # {0, 1, 3, 7, 9}

Номера телефонов не могут заканчиваться на:

set(range(10)) - d # {2, 4, 5, 6, 8}

Сравнение

Этот второй ответ:

  • Не требует numpy (нет необходимости в линейной алгебре), он использует только стандартную библиотеку Python;
  • Использует ли представление Graph, потому что это естественное представление вашегопроблема;
  • Генерирует все номера телефонов перед их подсчетом, предыдущий ответ не генерировал все из них, у нас были только данные о числах в форме x********y;
  • Использует рекурсию для решенияпроблема и, кажется, имеет экспоненциальную временную сложность, , если нам не нужно генерировать телефонные номера, мы должны использовать версию Matrix Power .

Benchmark

Сложность рекурсивной функции должна быть ограничена между O(2^n) и O(3^n), поскольку дерево рекурсии имеет глубину n-1 (и все ветви имеют одинаковую глубину), и каждый внутренний узел создает минимум 2 ребра и максимально3 ребра.Методология здесь не разделяй и властвуй алгоритм, это комбинаторика строковый генератор, поэтому мы ожидаем, что сложность будет экспоненциальной.

Сравнительный анализ двух функцийкажется, подтверждают это утверждение:

enter image description here

Рекурсивная функция демонстрирует линейное поведение в логарифмическом масштабе, что подтверждает экспоненциальную сложность и ограничено, как указано.Хуже того, в дополнение к вычислениям, для хранения списка потребуется растущий объем памяти.Я не смог пройти дальше, чем n=23, тогда мой ноутбук завис до того, как MemoryError.Лучшая оценка сложности - O((20/9)^n), где основание равно среднему значению степеней вершин (несвязанные вершины игнорируются).

Кажется, что метод Matrix Power имеет постоянное время по сравнению сразмер проблемы n.В документации numpy.linalg.matrix_power нет подробностей реализации, но это известная проблема собственных значений .Поэтому мы можем объяснить, почему сложность кажется постоянной до n.Это потому, что форма матрицы не зависит от n (она остается матрицей 10x10).Большая часть времени вычислений отводится для решения проблемы собственных значений , а не для повышения диагональной матрицы собственных значений до n-й степени , которая является тривиальной операцией (и единственной зависимостью от n).Вот почему это решение работает с «постоянным временем».Более того, для хранения Матрицы и ее разложения также потребуется квазипостоянный объем памяти, но это также не зависит от n.

Bonus

Найдите ниже код, используемый дляэталонные функции:

import timeit

nr = 20
ns = 100
N = 15
nt = np.arange(N) + 1
t = np.full((N, 4), np.nan)
for (i, n) in enumerate(nt):
    t[i,0] = np.mean(timeit.Timer("phoneNumbersCount(n=%d)" % n, setup="from __main__ import phoneNumbersCount").repeat(nr, number=ns))
    t[i,1] = np.mean(timeit.Timer("len(phoneNumbers(n=%d, i=2))" % n, setup="from __main__ import phoneNumbers").repeat(nr, number=ns))
    t[i,2] = np.mean(timeit.Timer("len(phoneNumbers(n=%d, i=0))" % n, setup="from __main__ import phoneNumbers").repeat(nr, number=ns))
    t[i,3] = np.mean(timeit.Timer("len(phoneNumbers(n=%d, i=6))" % n, setup="from __main__ import phoneNumbers").repeat(nr, number=ns))
    print(n, t[i,:])
0 голосов
/ 10 декабря 2018

Подпись функции

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

Ключевая идея

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

Сначала мы создадим Матрица смежности , представляющая законные шаги на клавиатуре телефона:

import numpy as np

A = np.zeros((10, 10))
A[0,4]=1
A[0,6]=1
A[1,6]=1
A[1,8]=1
A[2,7]=1
A[2,9]=1
A[3,4]=1
A[3,8]=1
A[4,0]=1
A[4,3]=1
A[4,9]=1
A[6,0]=1
A[6,1]=1
A[6,7]=1
A[7,2]=1
A[7,6]=1
A[8,1]=1
A[8,3]=1
A[9,2]=1
A[9,4]=1

Мы можем проверить, что матрица симметрична (не обязательно, но это свойство системы):

np.allclose(A, A.T) # True

Запись в Матрице Смежности читается так: A[0,4]=1 означает, что есть переход от вершины 0 к вершине 4, а A[0,5]=0 означает, что нет перехода от 0 к 5.

[[0. 0. 0. 0. 1. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0. 1. 0.]
 [0. 0. 0. 0. 0. 0. 0. 1. 0. 1.]
 [0. 0. 0. 0. 1. 0. 0. 0. 1. 0.]
 [1. 0. 0. 1. 0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [1. 1. 0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 1. 0. 0. 0. 1. 0. 0. 0.]
 [0. 1. 0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 1. 0. 0. 0. 0. 0.]]

Затем мы вычисляем A, возведенное в степень 9, что дает нам число прогулок длины 9 (это соответствует количеству уникальных телефонных номеров длины10) междудве заданные вершины (начиная с цифры x и заканчивая цифрой y):

W = np.linalg.matrix_power(A, 9)

Длина пути равна n-1, поскольку вершины - это числа, а края - это перемещения на клавиатуре, таким образом, чтобы набрать10 -значный номер телефона, который вам нужен 9 ходов (длина шага 9).

Это дает нам:

[[  0.   0. 336.   0. 544.   0. 544.   0. 336.   0.]
 [  0.   0. 264.   0. 432.   0. 448.   0. 280.   0.]
 [336. 264.   0. 264.   0.   0.   0. 280.   0. 280.]
 [  0.   0. 264.   0. 448.   0. 432.   0. 280.   0.]
 [544. 432.   0. 448.   0.   0.   0. 432.   0. 448.]
 [  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
 [544. 448.   0. 432.   0.   0.   0. 448.   0. 432.]
 [  0.   0. 280.   0. 432.   0. 448.   0. 264.   0.]
 [336. 280.   0. 280.   0.   0.   0. 264.   0. 264.]
 [  0.   0. 280.   0. 448.   0. 432.   0. 264.   0.]]

Матрица W запись читается как: W[2,1] = 264 означает, что есть 264 номера телефонов длиной 10, начинающиеся с 2 и заканчивающиеся 1.

Теперь мы подводим итоги прогулок, начиная с вершины 2:

np.sum(W[2,:]) # 1424.0

Имеется 1424 телефонных номеров длиной 10, начинающихся с цифры 2 для предоставленного вами набора правил.

Функция

В этом случае функция написать тривиально:

def phoneNumbersCount(n=10, i=2, A=A):
    return np.sum(np.linalg.matrix_power(A, n-1)[i,:])

Большая часть работы состоит в кодировании матрицы, описывающей набор правил (допустимые перемещения на клавиатуре).

проверки

на основе наблюдениймы можем получить из описания проблемы, например, @SpghttCd, мы можем проверить, что естьнет числа длины 10, начинающегося с 2, содержащего цифру 5:

W[2,5] # 0.0

Мы можем проверить, что никакое число длины 10 не может быть записано, начиная с 5:

phoneNumbersCount(10, 5) # 0.0

На самом деле цифра 5 вообще не доступна для данного набора правил.

Мы также можем проверить другие свойства, которые не очевидны, например: нет номера длины10 начинается с 2 и заканчивается либо 2, 4, 5, 6 или 8:

W[2,:] # [336. 264.   0. 264.   0.   0.   0. 280.   0. 280.]

Подсказка

ПосколькуГраф не ориентирован (каждое ребро существует в обоих направлениях), матрица смежности симметрична.Поэтому создание матрицы может быть сокращено до:

B = np.zeros((10, 10))
B[0,4]=1
B[0,6]=1
B[1,6]=1
B[1,8]=1
B[2,7]=1
B[2,9]=1
B[3,4]=1
B[3,8]=1
B[4,9]=1
B[6,7]=1
B = np.maximum(B, B.T)

Ссылки

Некоторые полезные ссылки, чтобы понять, как и почему это работает:

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