Smaller solution to this problem?

Started by
7 comments, last by Anon Mike 12 years, 8 months ago
[size="4"]I found an interesting problem online that has been taking from a programmer interview test from 10 years ago. I have managed to come up with a solution but was wondering if there were any more elegant solutions than the one I have devised.

The problem is as follows:


5986000252_6d3ed8675c_b.jpg



//main.cpp - contains solution for finding a wraparound number
#include <iostream>
#include <cassert>
using namespace std;

//ClearBools() initialises an array of bool values all to 0/false.
//The args passed in are the array of bools and the number of elements
//that need initialising to 0.
void ClearBools(bool arrayBools[],int numElems)
{
for(int elem = 0; elem < numElems; elem++)
arrayBools[elem] = 0;
}

//IsDigitVisitedIfNotMark() - this uses an index to check if an element of a bool array
//is true or false. If true return true, if false mark the element as visited and return false
bool IsDigitVisitedIfNotMark(int index,bool digitsVisited[])
{
if(digitsVisited[index] == 1) return true;

digitsVisited[index] = 1;
return false;
}

//FullTested() - This checks if an array of bools has all been set to true yet or not.
//In the context of this program if an array of bools representing the different digits have all been
//visited then they should all be true

bool FullyTested(bool arrayBools[],int numElems)
{
for(int elem = 0; elem < numElems; elem++)
{
if(arrayBools[elem] == 0) return false;
}

return true;
}

//NumDigitsInNumber - simply returns the amount of digits in the passed number
int NumDigitsInNumber(int value)
{
assert(value > 0);
int numDigits = 1;

while(true)
{
//if value divided by 10 is 0, there are no more digits to count
if(value/10 == 0) return numDigits;

//otherwise divide the value by 10, increase the number of recorded digits
//by 1
value /= 10;
numDigits++;
}
}

//PlaceDigitsInArray - This fills in the elements of an integer array with the digits of the number being tested
//e.g if 1234 was passed in, 1 would be stored at element 0 and 4 stored at element 3. Obviously the number
//of digits in the number and the number itself are passed in. The algorithm works backwards by using a
//remainder of the number being divided by 10.
void PlaceDigitsInArray(int digits[],int numOfDigits,int number)
{
//start at the last index
int index = numOfDigits-1;

while(index >= 0)
{
//index the digit by using the remainder and decrement the index
digits[index--] = number % 10;
//divide the number by 10 to place the next digit at the end
number /= 10;
}
}


//IsWrapAround - the main function that does the magic and uses the above functions to come to a result
bool IsWrapAround(int number)
{
//First determine the amount of digits in the number and create arrays to hold the digits in the
//number and a bool array to help 'mark' if that number has been visited yet.
int numOfDigits = NumDigitsInNumber(number);

int* digitsInNumber = new int[numOfDigits];
bool* digitsVisited = new bool[numOfDigits];

//bool array to help check if a digit in the number has already been used, as 1-9 can only be used
//once each
bool digitsUsed[9];

//initialise the bool arrays and put the digits of the number into the int array
ClearBools(digitsVisited,numOfDigits);
ClearBools(digitsUsed,9);
PlaceDigitsInArray(digitsInNumber,numOfDigits,number);

//set the index of the digit to be tested to the first/zeroth position
int index = 0;

while(true)
{
//have all the digits been tested? If so we are done and it is a wraparound number
if(FullyTested(digitsVisited,numOfDigits))
{
delete [] digitsVisited;
delete [] digitsInNumber;
return true;
}


//Check if the current digit has been used/checked already - if it has
//its not a wrap around number
if(IsDigitVisitedIfNotMark(index,digitsVisited))
{
delete [] digitsVisited;
delete [] digitsInNumber;
return false;
}

//get the digit to be tested using the current index
int digitTested = digitsInNumber[index];

//Check if the current digit has been used/checked already - if it has
//its not a wrap around number
if(IsDigitVisitedIfNotMark(digitTested,digitsUsed))
{
delete [] digitsVisited;
delete [] digitsInNumber;
return false;
}

//compute the index of the next digit to be used - add the digit being tested
//to the index and then 'wrap around' if the result is greater than the number
//of digits being tested
index = (index + digitTested)%numOfDigits;
}
}

int main()
{
int value;

cin >> value;

while(true)
{
if(IsWrapAround(value))
cout <<value<<" is a wraparound number"<<endl;
else
cout <<value<<" is not a wraparound number"<<endl;

cin >> value;
}
return 0;
}


I look forward to any suggestions, I found this a fun problem
Advertisement
Ok, here is my solution
#include <vector>
#include <algorithm>

bool isRunaroundNumber(int number)
{
// special case when number is zero.
if (number <= 0)
{
return false;
}

// vector to hold the digits
std::vector<int> digits;

// fill the vector with digits
while (number > 0)
{
int digit = number % 10;
// check so the digit is unique
if (std::find(digits.begin(), digits.end(), digit) != digits.end())
{
return false;
}
digits.push_back(digit);
number /= 10;
}

// put the digits in opposite order to make it easier to work with
std::reverse(digits.begin(), digits.end());

int i = 0; // marks the current digit
do
{
// set the digit to zero to mark visited
// and move to the next digit
int n = digits;
digits = 0;
i = (i + n) % digits.size();
} while (digits != 0);

// if the number is a runaround number all digits should have been
// visited and marked as zero.
for (int d : digits)
{
if (d != 0)
{
return false;
}
}

// The sequence must end at the leftmost digit
return i == 0;
}
Whoah Wooh! That's really nice :) I like how you checked if the number has been used already before adding it to the vector of digits, and that you mark the numbers in the vector itself as zero as opposed to using any additional storage like I did with my arrays of bools. I might have a crack at implementing your strategy myself later, but without the STL
Here is an updated version that uses one loop less. It is also more correct because the previous one accepted some numbers that contained the digit zero (like 204). This one don't have this problem.
bool isRunaroundNumber(int number)
{
// special case when number is zero.
if (number <= 0)
{
return false;
}

// vector to hold the digits
std::vector<int> digits;

// fill the vector with digits
while (number > 0)
{
int digit = number % 10;
// check so the digit is unique
if (std::find(digits.begin(), digits.end(), digit) != digits.end())
{
return false;
}
digits.push_back(digit);
number /= 10;
}

// put the digits in opposite order to make it easier to work with
std::reverse(digits.begin(), digits.end());

int i = 0; // marks the current digit
for (std::size_t j = 0; j < digits.size(); j++)
{
int n = digits;
if (n == 0)
{
// digit already visited so it can't be a runaround number
return false;
}
// set the digit to zero to mark visited
// and move to the next digit
digits = 0;
i = (i + n) % digits.size();
}

// The sequence must end at the leftmost digit
return i == 0;
}
Duplicate checking:
bool test(int n) {
...
static const int magic[] = { 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 };

int seen = 1;

if (n < 0) return false;
while (n) {
int i = n % 10;
if (seen % magic == 0) return false;
seen *= magic;
digits.push_back(i);
n /= 10;
}
if (n) return false;

...


And I'm aware it's *too* clever.
Slightly less tricky than Antheus's code:
unsigned seen = 0u;
for (;n; n/=10) {
int i = n % 10;
unsigned bit_i = 1u << i;
if (seen & bit_i) return false;
seen |= bit_i;
digits.push_back(i);
}


Why did you write the last check? It has the same condition as the preceding while loop, so it can't possible get executed...
This is more a math problem than a programming problem. Not sure if this is entirely correct, but I suspect that the mathematical approach is correct:

[source lang="Csharp"]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace runaround
{
class Program
{
public static int GCD(int a, int b)
{
int limit = a < b ? a : b;
int gcd = 1;
for (int x = 2; x <= limit; ++x)
{
if (a % x == 0 && b % x == 0)
{
gcd = x;
}
}

return gcd;
}

public static bool IsRunaround(List<int> digits)
{
if (!(digits.Aggregate(0, (x, i) => x += i) % digits.Count == 0))
{
return false;
}

int ix =0;
foreach (var entry in digits)
{
ix++;
if (entry % digits.Count == 0)
{
return false;
}

var index = (digits.IndexOf(entry) + 1);
var mod = entry % index;
if (index != ix)
{
return false;
}

if (GCD(mod, index) != 1)
{
return false;
}
}

return true;
}

static void Main(string[] args)
{
List<List<int>> tests = new List<List<int>>(){
new List<int>(){1,3},
new List<int>(){1,2,6,3},
new List<int>(){1,1,4},
new List<int>(){1,4,6},
new List<int>(){1,2,3,6},
new List<int>(){2,0,1}};

foreach (var entry in tests)
{
Console.WriteLine("{0} - {1}", string.Join("", entry), IsRunaround(entry));
}
}
}
}
[/source]
In Perl:

#!/usr/local/bin/perl

print "True!\n" if is_runaround(1263);

sub is_runaround {
my @x = split "", shift;
my $n = scalar(@x);

return 0 if scalar(keys %{{ map { $_ => 1 } @x }}) < $n;
my $p=0;
for my $i (0..$#x) {
$p = ($p + $x[$p]) % $n;
return $i==$#x if $p==0;
}
return 0;
}

I'm pretty sure 0 meets the criteria for a run-around number.
Here's a C version that doesn't use an intermediate digit array:

[source]

#include <limits.h>

int isRunaround(unsigned x)
{
unsigned divisor = 1;
unsigned first;
unsigned useddig = 0;
unsigned usedval = 0;

while (divisor <= (UINT_MAX / 10) && x > divisor * 10)
divisor *= 10;

first = divisor;

do
{
unsigned digit = (x / divisor) % 10;
if (useddig & (1 << digit))
return 0;

useddig |= (1 << digit);
usedval += digit * divisor;

for ( ; digit; --digit)
divisor = (divisor == 1) ? first : (divisor / 10);
}
while (divisor != first);

return x == usedval;
}
[/source]
-Mike

This topic is closed to new replies.

Advertisement