Check if two numbers have same digits

Viewed 1015

I need to write a program which will check if two numbers have same digits. For example:

a = 4423, b = 2433;

Even though their digits don't appear the same amount if times, they have same digits.

#include <stdio.h>
#include <stdlib.h>
int same_digit(int a, int b) {
  int n = abs(a), i;
  int count[10];
  for (i = 0; i < 10; ++i) {
    count[i] = 0;
  }
  while (n > 0) {
    count[n % 10]++;
    n = n / 10;
  }
  n = abs(b);
  while (n > 0) {
    count[n % 10]--;
    n = n / 10;
  }
  for (i = 0; i < 10; ++i)
    if (count[i] != 0)
      return 0;
  return 1;
}
int main() {
  int a, b;
  scanf("%d", &a);
  scanf("%d", &b);
  if (same_digit(a, b)) printf("Yes");
  else printf("No");
  return 0;
} 

Problem with my code is that this literally checks if they have same digits. How could I modify this code to return 1 if all digits from a are present in b, and all digits from b are present in a, no matter how many times they are present.

5 Answers

Using arrays is not necessary, but it sure becomes clean code:

// Remove duplicate characters from sorted array
void remove_dups(char *str) 
{
    char *cur = str;
    while(*cur) 
    {
        *str = *cur;
        while(*++cur == *str);
        ++str;
    }
    *str = 0;
}

int cmp(const void *a, const void *b)
{
    return *(char*)a - *(char*)b;
}

int same_digit(int a, int b)
{
    char A[22], B[22]; // Room for 64 bit. Increase if necessary.
    char Aun[11], Bun[11]; // 10 digits
    
    sprintf(A, "%d", a);
    sprintf(B, "%d", b);
    
    qsort(A, strlen(A), sizeof *A, cmp);
    qsort(B, strlen(B), sizeof *B, cmp);
    
    remove_dups(A);
    remove_dups(B);

    return !strcmp(A, B);
}

Demo

Many C coders will frown on this. And it's not completely without good reason. This is how you typically would solve things in Python or Javascript. If you're writing serious C code, there's probably a reason to why you're not choosing a more high level language. And it's quite likely that those reasons has to do with the performance C can offer.

However, there's absolutely nothing that prevents you from starting with this and optimize it later when you have concluded that this code indeed is a bottleneck with a profiler. And who knows? It may be the case that this is faster. Don't do premature optimization.

There are actually many problems involving digits that becomes so much easier if you convert the numbers to arrays of digits first. And many problems involving arrays becomes much easier if you sort the arrays. Don't be afraid to have this approach as a standard tool in your toolbox.

Furthermore, it's quite common that a naive solution for unsorted arrays is O(n²) while a naive solution for a sorted array is just O(n). And since sorting can be done in O(n * log n) or better, you often get a pretty good solution. Sure, since n in this case typically is less than 10, so a naive O(n²) is probably faster anyway. But it's worth remembering. I believe that you would have to write pretty fancy code to solve this in O(n * log n) without arrays. If it's at all possible. (Not saying that it's likely not possible. Only that I don't know)

A histogram of 10 digits (or 11 if we want to count the '-' sign) where each frequency is capped between 0 and 1 should be implemented as a vector of bits (a bitmask) stored in an integer. Then of course the solution can handle all the number bases only up to 32, but the overall code should be vastly clearer and smaller, without the need to compare arrays by looping.

int hash_int(int b) {
    int hash = 0;
    // take abs as unsigned -- now 0 <= a <= 0x80000000u
    // unsigned mod 10 is btw more performant than signed mod
    unsigned int a = b >= 0 ? b : 0u - b;
    // construct the hash, removing duplicate digits as we go
    // performance wise `do {} while` is better than `while`
    // due to less jumping around - here the side effect is
    // that a==0 hashes to 1.
    // For comparison a==0 -> hash = 0 would work equally well
    do {
        hash |= 1 << (a % 10);
        a /= 10;
    } while (a);
    return hash;
}

bool is_same(int a, int b) { return hash_int(a) == hash_int(b); }

Your code is a bit more complicated than it needs to be.

Just compute a "has a digit" vector for each number (e.g. each element is 1 if the corresponding digit is in the number). This is a histogram/frequency table except that instead of the count of the digits in a number, it's just 0 or 1.

Then, compare the vectors for equality.

Here's some refactored code:

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

void
count(int x,int *hasdig)
{
    int dig;

    // handle negative numbers
    if (x < 0)
        x = -x;

    // special case for zero
    // NOTE: may not be necessary
#if 1
    if (x == 0)
        hasdig[0] = 1;
#endif

    for (;  x != 0;  x /= 10) {
        dig = x % 10;
        hasdig[dig] = 1;
    }
}

int
same_digit(int a, int b)
{
    int dig_a[10] = { 0 };
    int dig_b[10] = { 0 };
    int dig;
    int same = 1;

    count(a,dig_a);
    count(b,dig_b);

    for (dig = 0;  dig < 10;  ++dig) {
        same = (dig_a[dig] == dig_b[dig]);
        if (! same)
            break;
    }

    return same;
}

int
main()
{
    int a, b;

    scanf("%d", &a);
    scanf("%d", &b);

    if (same_digit(a, b))
        printf("Yes\n");
    else
        printf("No\n");

    return 0;
}

UPDATE:

Also beware UB if either or both numbers is INT_MIN

Yes, -INT_MIN is still negative :-(

I've come up with an alternate way to deal with it. However, IMO, [almost] any approach seems to be extra work, just to make one special case work. So, I've kept the original [faster/simpler] code above

And, I've added some extra test/debug code.

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

int opt_t;

void
count(int x,int *hasdig)
{
    int dig;

    // handle negative numbers
    if (x < 0)
        x = -x;

    // special case for zero
    // NOTE: may not be necessary
#if 1
    if (x == 0)
        hasdig[0] = 1;
#endif

    for (;  x != 0;  x /= 10) {
        dig = x % 10;

        // my -INT_MIN fix ...
        if (dig < 0)
            dig = -dig;

        hasdig[dig] = 1;
    }
}

int
same_digit(int a, int b)
{
    int dig_a[10] = { 0 };
    int dig_b[10] = { 0 };
    int dig;
    int same = 1;

    count(a,dig_a);
    count(b,dig_b);

    for (dig = 0;  dig < 10;  ++dig) {
        same = (dig_a[dig] == dig_b[dig]);
        if (! same)
            break;
    }

    return same;
}

void
dotest(int a,int b)
{

    printf("a=%d b=%d -- %s\n",
        a,b,same_digit(a,b) ? "Yes" : "No");
}

int
main(int argc,char **argv)
{
    char *cp;

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        cp = *argv;
        if (*cp != '-')
            break;

        if ((cp[1] == '-') && (cp[2] == 0)) {
            --argc;
            ++argv;
            break;
        }

        cp += 2;
        switch(cp[-1]) {
        case 't':
            opt_t = ! opt_t;
            break;
        }
    }

    do {
        int a, b;

        if (opt_t) {
            dotest(4423,2433);
            dotest(INT_MIN,1234678);
            dotest(INT_MIN,INT_MIN);
            dotest(INT_MAX,INT_MAX);
            break;
        }

        if (argc > 0) {
            for (;  argc > 0;  --argc, ++argv) {
                cp = *argv;
                if (sscanf(cp,"%d,%d",&a,&b) != 2)
                    break;
                dotest(a,b);
            }
            break;
        }

        scanf("%d", &a);
        scanf("%d", &b);
        dotest(a,b);
    } while (0);

    return 0;
}

You need to store which digits have occurred in both numbers. For example, for both numbers, you could define an array of 10 bool elements, where the first element specifies whether the digit 0 occurred, the second element whether the digit 1 occurred, etc. Since you need two such arrays, you could make an array of 2 of these arrays, giving you a 2D array. That way, you can process both arrays in the same loop.

After you have filled both arrays, you can compare them, in order to determine whether both numbers use the same digits.

Also, it may be better to change the return type of same_digit to bool.

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

bool same_digit( int a, int b )
{
    int numbers[2] = { a, b };
    bool occurred[2][10] = { {false} };

    //determine which digits are used by the individual numbers
    //and fill the two arrays accordingly
    for ( int i = 0; i < 2; i++ )
    {
        int n = abs( numbers[i] );

        while ( n > 0 )
        {
            occurred[i][n%10] = true;
            n = n / 10;
        }
    }

    //determine whether both numbers use the same digits
    for ( int i = 0; i < 10; i++ )
        if ( occurred[0][i] != occurred[1][i] )
            return false;

    return true;
}

//the following code has not been modified
int main() {
  int a, b;
  scanf("%d", &a);
  scanf("%d", &b);
  if (same_digit(a, b)) printf("Yes");
  else printf("No");
  return 0;
}

Here is a simpler version of Aki Suihkonen's solution that works for all int values including INT_MIN:

#include <stdlib.h>

int digit_set(int a) {
    int set = 0;
    // construct the set, one bit per digit value.
    // do / while allows for digit_set(0) = 1
    // integer division truncates towards 0 so the
    // remainder is the negative value of the low digit
    // hence abs(a % 10) is the digit value.
    // this works for all values including INT_MIN
    do {
        set |= 1 << abs(a % 10);
    } while ((a /= 10) != 0);

    return set;
}

bool same_digits(int a, int b) { return digit_set(a) == digit_set(b); }
Related