Возможные комбинации символов на цифровой клавиатуре телефона

Недавно я столкнулся с вопросом в C. У нас есть цифровая клавиатура телефона со следующей раскладкой:

1[abc] 2[def] 3[ghi]
4[jkl] 5[mno] 6[pqr]
7[st]  8[uv]  9[wx]
       0[yz]

Как придумать API, который выдает все возможные комбинации символов, принадлежащих каждому числу, для данного числового ввода. Например, input = 1234

Затем API должен вывести все возможные комбинации символов.

adgj bdgj cdgj aegj begj cegj... и так далее.

Есть ли простой способ сделать это? Помимо жестко закодированных вложенных циклов for. Мне дали подсказку как рекурсию, но я не мог найти выход из нее.


person tcpip    schedule 15.11.2014    source источник
comment
Простым решением грубой силы является использование вложенных циклов.   -  person Some programmer dude    schedule 15.11.2014
comment
@JoachimPileborg: Вложенные циклы — это нормально, если у вас всегда четыре цифры. Я думаю, что рекурсия - лучшее решение.   -  person M Oehm    schedule 15.11.2014
comment
@MOehm Да, рекурсия, вероятно, лучшее решение, особенно для общего случая. Однако многие новички, как правило, имеют проблемы с рекурсией в начале, поэтому, если количество цифр известно с самого начала, то вложенные циклы являются абсолютно простым решением.   -  person Some programmer dude    schedule 15.11.2014
comment
Рекурсия @MOehm обеспечивает элегантное решение многих проблем, но одним большим недостатком, особенно для начинающих программистов, является отладка логических ошибок и/или ошибок программирования (segfaults и т. д.), возникающих в рекурсивных функциях. Особенно там, где рекурсия повторяет индексы массива (например, recfn (index + 1) или тому подобное). Во вложенных циклах гораздо проще отловить проблемы отладки. Хотя преимуществом рекурсии является то, что она приведет к более глубокому знакомству с gdb раньше, а не позже.   -  person David C. Rankin    schedule 16.11.2014
comment
Я признаю, что вложенные циклы — это простое решение, и я бы не стал возражать, если бы вопрос касался четырехзначных чисел. Но ОП спросил о рекурсии и API, что намекает на что-то общее и многократное использование. И рекурсия — ценный метод, даже если он может быть более сложным для начинающих.   -  person M Oehm    schedule 16.11.2014


Ответы (4)


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

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

  • исходная строка
  • вспомогательный char буфер для раствора* и
  • текущий индекс, который начинается с 0.

Рекурсивные функции требуют условия завершения: когда вы достигли конца исходной строки, распечатайте ее и верните.

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

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

#include <stdlib.h>
#include <stdio.h>



/*
 *      Recursive back-end, that produces all combinations in sol.
 */
void alpha_r(const char *str, char *sol, int index)
{
    const char *combo[] = {
        "yz", "abc", "def", "ghi", "jkl", "mno", "pqr", "st", "uv", "wx" 
    };

    if (str[index] == '\0') {
        printf("%s\n", sol);
    } else {
        int k = str[index] - '0';
        const char *p = combo[k];

        while (*p) {
            sol[index] = *p++;
            alpha_r(str, sol, index + 1);
        }        
    }
}

/* 
 *      Non-recursive front-end that checks the string for validity
 *      and creates a temporary buffer for the solutions.
 */    
void alpha(const char *str)
{
    int len = 0;

    while (str[len]) {
        if (str[len] < 0 || str[len] > '9') {
            fprintf(stderr, "Invalid input.\n");
            return;
        }
        len++;
    }

    char sol[len + 1];

    sol[len] = '\0';
    alpha_r(str, sol, 0);
}

int main()
{
    alpha("123");

    return 0;
}

*) Вы также можете использовать саму строку для хранения решений.

person M Oehm    schedule 15.11.2014
comment
+1 Очень красиво сделано. Обработка ошибок, особенно начиная с "123", а не 123, поскольку этот подход хорошо обрабатывает "000". Тривиально преобразовать 0 в "0", если требуется ввод int. - person chux - Reinstate Monica; 15.11.2014

(Кстати, это не стандартная раскладка для телефона.)

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

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

Если вы дойдете до конца массива, распечатайте и вернитесь.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

char* data[] = {"0", "1", "2abc", "3def", "4ghi", "5jkl", "6mno", "7prs", "8tuv", "9wxy"};

char* input = "23456783";

char* arr;

void current(int index)
{
    if(index == strlen(input)) printf("%s\n", arr);
    else
    {  
         for(int i = 0; i < strlen(data[input[index] - '0']); ++i)
         {
              arr[index] = data[input[index] - '0'][i];
              current(index + 1);
         }
    }
}

void main()
{
    arr = malloc(strlen(input) + 1);
    arr[strlen(input)] = '\0';
    printf("%s\n\n", input);
    current(0);
}
person uncleO    schedule 15.11.2014

Рекурсия — это просто хитрый способ вложения четырех циклов for. Вот как выглядит код

#include <stdio.h>

void sneaky( int depth, int maxDepth, char str[] )
{
    char c, start;

    start = 'a' + depth * 3;
    for ( c = start; c < start + 3; c++ )
    {
        str[depth] = c;
        str[depth+1] = '\0';

        if ( depth == maxDepth )
            printf( "%s\n", str );
        else
            sneaky( depth + 1, maxDepth, str );
    }
}

int main( void )
{
    char str[5] = { 0 };
    sneaky( 0, 3, str );
}

Вы также можете решить эту задачу и подобные комбинаторные задачи с помощью простого алгоритма подсчета. Алгоритм счета имитирует естественный счет, при котором вы увеличиваете младшую значащую цифру от 0 до 9. Когда младшая значащая цифра переходит от 9 обратно к 0, увеличивается следующая цифра слева.

То же самое можно сделать, чтобы решить проблему ОП. Но в этом случае цифры имеют либо два, либо три возможных значения. И если вы изучите шаблон в OP, то сразу станет ясно, что младшая значащая цифра находится слева. В шаблоне
adgj bdgj cdgj aegj
видно, что a становится b, b становится c, а когда c возвращается к a, d становится e.

Вот код

#include <stdio.h>
#include <stdlib.h>

static char InitialValue[] = { 'y', 'a', 'd', 'g', 'j', 'm', 'p', 's', 'u', 'w' };

static char NextValue[] = { 'b', 'c', 'a',   'e', 'f', 'd',   'h', 'i', 'g',
                            'k', 'l', 'j',   'n', 'o', 'm',   'q', 'r', 'p',
                            't', 's',   'v', 'u',   'x', 'w',   'z', 'y'     };

static void error( char *msg )
{
    fprintf( stderr, "%s\n", msg );
    exit( EXIT_FAILURE );
}

int main( void )
{
    int i, oldDigit;
    char str[12];

    // get the input string from the user
    printf( "Enter the input string: " );
    fflush( stdout );
    if ( scanf( "%10s", str ) != 1 )
        error( "whatever" );

    // convert the input string to the corresponding first output string
    for ( i = 0; str[i] != '\0'; i++ )
    {
        if ( str[i] < '0' || str[i] > '9' )
            error( "invalid input string" );
        str[i] = InitialValue[str[i] - '0'];
    }
    printf( "%s\n", str );

    // use a simple counting algorithm to generate the string combinations
    for (;;)
    {
        for ( i = 0; str[i] != '\0'; i++ )
        {
            oldDigit = str[i];                   // save the current digit
            str[i] = NextValue[oldDigit - 'a'];  // advance the digit to the next value 
            if ( str[i] > oldDigit )             // if the digit did not wrap
                break;                           // then we've got a new string
        }

        if ( str[i] == '\0' )                    // if all the digits wrapped
            break;                               // then we're done
        printf( "%s\n", str );                   // output the new string
    }

    return( EXIT_SUCCESS );
}
person user3386109    schedule 15.11.2014

Способом поиска комбинаций, которые вы ищете, может быть побитовая логика с двоичным числом и целым числом. Двоичное число будет такой же длины, как строка, где 0 и 1 действуют как переключатели включения и выключения того, что включается и исключается в строку. Дело в том, что мы используем основание 3 или 4 в зависимости от числа, которое «нажали», и

Если основание четыре, то должны быть применены некоторые операторы if, чтобы переместить те, которые на самом деле являются основанием три.

person T.Woody    schedule 15.11.2014
comment
Согласен с @MOehm. Строки не должны иметь значение. Я ищу API, который будет делать это с рекурсией. - person tcpip; 15.11.2014
comment
Да, вы можете использовать схему подсчета с основанием 3 для выполнения задачи без рекурсии. - person user3386109; 15.11.2014
comment
@MOehm Я изменил свой ответ. Я подумал, что дам вам знать, чтобы вы могли изменить свой комментарий, если хотите. - person T.Woody; 16.11.2014
comment
Я удалил комментарий, так как он не имеет ничего общего с текущим ответом. - person M Oehm; 16.11.2014