39
votes

I am solving a problem to find out all the 4 digit Vampire numbers.

A Vampire Number v=x*y is defined as a number with 'n' even number of digits formed by multiplying a pair of 'n/2'-digit numbers (where the digits are taken from the original number in any order)x and y together. If v is a vampire number, then x&y and are called its "fangs."

Examples of vampire numbers are:

    1.    1260=21*60
    2.    1395=15*93
    3.    1530=30*51

I have tried the brute force algorithm to combine different digits of a given number and multiply them together . But this method is highly inefficient and takes up a lot of time.

Is there a more efficient algorithmic solution to this problem?

16
Can't you just iterate through all 9000 posibilities? That's not that much? Just start with 1000 which means 10 * 00, check if the result is a vampire.Maarten Bodewes
Or, alternatively, just a nested loop over the potential factors - multiply them and test the result... At least for 2-digit -> 4-digit case. Now, if you wanted to find all the 128-digit -> 256-digit cases, that's a completely different scale of problem...twalberg
@HotLicks No matter how you slice it, for the 2-digit factor case, there are less than 100*100=10000 possibilities...twalberg
this is ridiculous. the reason given for closure is intended for people asking for problems with code (things like syntax). not for questions about algorithms. "brute force algorithm" alone is enough to explain the issue (and motivate the excellent answer). there is absolutely no need for an interesting question and brilliant answer like this to be closed - it's just small-minded rule following rather than trying to make the site a great place. barmar, ziyao wei, woodchips, christoffer hammarström and marko should be ashamed of themselves. really poor behaviour.andrew cooke

16 Answers

36
votes

Or you can use a property of vampire numbers described on this page (linked from Wikipedia) :

An important theoretical result found by Pete Hartley:

      If x·y is a vampire number then x·y == x+y (mod 9) 

Proof: Let mod be the binary modulo operator and d(x) the sum of the decimal digits of x. It is well-known that d(x) mod 9 = x mod 9, for all x. Assume x·y is a vampire. Then it contains the same digits as x and y, and in particular d(x·y) = d(x)+d(y). This leads to: (x·y) mod 9 = d(x·y) mod 9 = (d(x)+d(y)) mod 9 = (d(x) mod 9 + d(y) mod 9) mod 9 = (x mod 9 + y mod 9) mod 9 = (x+y) mod 9

The solutions to the congruence are (x mod 9, y mod 9) in {(0,0), (2,2), (3,6), (5,8), (6,3), (8,5)}

So your code could look like this :

for(int i=18; i<100; i=i+9){          // 18 is the first multiple of 9 greater than 10
   for(int j=i; j<100; j=j+9){        // Start at i because as @sh1 said it's useless to check both x*y and y*x
       checkVampire(i,j);
   }
}

for(int i=11; i<100; i=i+9){          // 11 is the first number greater than 10 which is = 2 mod 9
   for(int j=i; j<100; j=j+9){
       checkVampire(i,j);
   }
}

for(int i=12; i<100; i=i+9){
   for(int j=i+3; j<100; j=j+9){
       checkVampire(i,j);
   }
}

for(int i=14; i<100; i=i+9){
   for(int j=i+3; j<100; j=j+9){
       checkVampire(i,j);
   }
}

// We don't do the last 2 loops, again for symmetry reasons

Since they are 40 elements in each of the sets like {(x mod 9, y mod 9) = (0,0); 10 <= x <= y <= 100}, you only do 4*40 = 160 iterations, when a brute-force gives you 10ˆ4 iterations. You can do even less operations if you take into account the >= 1000 constraint, for instance you can avoid checking if j < 1000/i.

Now you can easily scale up to find vampires with more than 4 digits =)

9
votes

Iterate over all possible fangs (100 x 100 = 10000 possibilities), and find if their product has the same digits as the fangs.

5
votes

Yet another brute force (C) version, with a free bubble sort to boot...

#include <stdio.h>

static inline void bubsort(int *p)
{ while (1)
  { int s = 0;

    for (int i = 0; i < 3; ++i)
      if (p[i] > p[i + 1])
      { s = 1;
        int t = p[i]; p[i] = p[i + 1]; p[i + 1] = t;
      }

    if (!s) break;
  }
}

int main()
{ for (int i = 10; i < 100; ++i)
    for (int j = i; j < 100; ++j)
    { int p = i * j;

      if (p < 1000) continue;

      int xd[4];
      xd[0] = i % 10;
      xd[1] = i / 10;
      xd[2] = j % 10;
      xd[3] = j / 10;

      bubsort(xd);
      int x = xd[0] + xd[1] * 10 + xd[2] * 100 + xd[3] * 1000;

      int yd[4];
      yd[0] = p % 10;
      yd[1] = (p / 10) % 10;
      yd[2] = (p / 100) % 10;
      yd[3] = (p / 1000);

      bubsort(yd);
      int y = yd[0] + yd[1] * 10 + yd[2] * 100 + yd[3] * 1000;

      if (x == y)
        printf("%2d * %2d = %4d\n", i, j, p);
    }

  return 0;
}

Runs pretty much instantaneously. Variable names aren't too descriptive, but should be pretty obvious...

The basic idea is to start with two potential fangs, break them down into digits, and sort the digits for easy comparison. Then we do the same with the product - break it down to digits and sort. Then we re-constitute two integers from the sorted digits, and if they're equal, we have a match.

Possible improvements: 1) start j at 1000 / i instead of i to avoid having to do if (p < 1000) ..., 2) maybe use insertion sort instead of bubble sort (but who's gonna notice those 2 extra swaps?), 3) use a real swap() implementation, 4) compare the arrays directly rather than building a synthetic integer out of them. Not sure any of those would make any measurable difference, though, unless you run it on a Commodore 64 or something...

Edit: Just out of curiosity, I took this version and generalized it a bit more to work for the 4, 6 and 8 digit cases - without any major optimization, it can find all the 8-digit vampire numbers in < 10 seconds...

3
votes

This is an ugly hack (brute force, manual checking for permutations, unsafe buffer operations, produces dupes, etc.) but it does the job. Your new exercise is to improve it :P

Wikipedia claims that there are 7 vampire numbers which are 4 digits long. The full code has found them all, even some duplicates.

Edit: Here's a slightly better comparator function.

Edit 2: Here's a C++ version that uniques results (therefore it avoids duplicates) using an std::map (and stores the last occurrence of the particular vampire number along with its factors in it). It also meets the criterion that at least one of the factors should not end with 0, i. e. a number is not a vampire number if both of the multiplicands are divisible by then. This test looks for 6-digit vampire numbers and it does indeed find exactly 148 of them, in accordance with what Wikipedia sates.


The original code:

#include <stdio.h>

void getdigits(char buf[], int n)
{
    while (n) {
        *buf++ = n % 10;
        n /= 10;
    }
}

int is_vampire(const char n[4], const char i[2], const char j[2])
{
    /* maybe a bit faster if unrolled manually */
    if (i[0] == n[0]
     && i[1] == n[1]
     && j[0] == n[2]
     && j[1] == n[3])
        return 1;

    if (i[0] == n[1]
     && i[1] == n[0]
     && j[0] == n[2]
     && j[1] == n[3])
            return 1;

    if (i[0] == n[0]
     && i[1] == n[1]
     && j[0] == n[3]
     && j[1] == n[2])
            return 1;

    if (i[0] == n[1]
     && i[1] == n[0]
     && j[0] == n[3]
     && j[1] == n[2])
            return 1;

    // et cetera, the following 20 repetitions are redacted for clarity
    // (this really should be a loop, shouldn't it?)

    return 0;
}

int main()
{
    for (int i = 10; i < 100; i++) {
        for (int j = 10; j < 100; j++) {
            int n = i * j;
            if (n < 1000)
                continue;

            char ndigits[4];
            getdigits(ndigits, n);

            char idigits[2];
            char jdigits[2];
            getdigits(idigits, i);
            getdigits(jdigits, j);

            if (is_vampire(ndigits, idigits, jdigits))
                printf("%d * %d = %d\n", i, j, n);
        }
    }

    return 0;
}
1
votes

I wouldn't have given up so easily on brute force. You have distinct set of numbers, 1000 to 9999 that you must run through. I would divide up the set into some number of subsets, and then spin up threads to handle each subset.

You could further divide the work be coming up with the various combinations of each number; IIRC my discrete math, you have 4*3*2 or 24 combinations for each number to try.

A producer / consumer approach might be worthwhile.

1
votes

Iteration seems fine to me, since you only need to do this once to find all the values and you can just cache them afterwards. Python (3) version that takes about 1.5 seconds:

# just some setup
from itertools import product, permutations
dtoi = lambda *digits: int(''.join(str(digit) for digit in digits))
gen = ((dtoi(*digits), digits) for digits in product(range(10), repeat=4) if digits[0] != 0)
l = []

for val, digits in gen:
    for check1, check2 in ((dtoi(*order[:2]), dtoi(*order[2:])) for order in permutations(digits) if order[0] > 0 and order[2] > 0):
        if check1 * check2 == val:
            l.append(val)
            break

print(l)

Which will give you [1260, 1395, 1435, 1530, 1827, 2187, 6880]

1
votes

EDIT: full brute force that weeds out identical X and Y values...

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Vampire {

    public static void main(String[] args) {
        for (int x = 10; x < 100; x++) {
            String sx = String.valueOf(x);
            for (int y = x; y < 100; y++) {
                int v = x * y;
                String sy = String.valueOf(y);
                String sv = String.valueOf(v);
                if (sortVampire(sx + sy).equals(sortVampire(sv))) {
                    System.out.printf("%d * %d = %d%n", x, y, v);
                }
            }
        }
    }

    private static List<Character> sortVampire(String v) {
        List<Character> vc = new ArrayList<Character>();
        for (int j = 0; j < v.length(); j++) {
            vc.add(v.charAt(j));
        }
        Collections.sort(vc);
        return vc;
    }
}
1
votes

Brute force version in C# with LINQ:

class VampireNumbers
{
    static IEnumerable<int> numberToDigits(int number)
    {
        while(number > 0)
        {
            yield return number % 10;
            number /= 10;
        }
    }

    static bool isVampire(int first, int second, int result)
    {
        var resultDigits = numberToDigits(result).OrderBy(x => x);

        var vampireDigits = numberToDigits(first)
                             .Concat(numberToDigits(second))
                             .OrderBy(x => x);                                  

        return resultDigits.SequenceEqual(vampireDigits);
    }

    static void Main(string[] args)
    {
        var vampires = from fang1 in Enumerable.Range(10, 89)
                       from fang2 in Enumerable.Range(10, 89)
                       where fang1 < fang2
                             && isVampire(fang1, fang2, fang1 * fang2)       
                       select new { fang1, fang2 };

        foreach(var vampire in vampires)
        {
            Console.WriteLine(vampire.fang1 * vampire.fang2 
                              + " = " 
                              + vampire.fang1 
                              + " * " 
                              + vampire.fang2);
        }
    }
}
1
votes

Similar to someone mentioned above, my method is to first find all permutations of a number, then split them in half to form two 2-digit numbers, and test if their product equal to the original number.

Another interesting discussion above is how many permutations a number can have. Here is my opinion: (1) a number whose four digitals are the same has 1 permutation; (2) a number who has only two different digits has 6 permutations (it doesn't matter if it contains zeros, because we don't care after permutation if it is still a 4-digit number); (3) a number who has three different digits has 12 permutations; (4) a number with all four different digits has 24 permutations.

public class VampireNumber {

// method to find all permutations of a 4-digit number
public static void permuta(String x, String s, int v)
{for(int i = 0; i < s.length(); i++)
{permuta( x + s.charAt(i), s.substring(0,i) + s.substring(i+1), v);
if (s.length() == 1)
    {x = x + s;
    int leftpart = Integer.parseInt(x.substring(0,2));
    int rightpart = Integer.parseInt(x.substring(2));
    if (leftpart*rightpart == v) 
        {System.out.println("Vampir = " + v);
        }
    }
  }
}

public static void main(String[] args){
for (int i = 1000; i < 10000; i++) {
permuta("", Integer.toString(i), i); //convert the integer to a string
}
}
}
0
votes

The approach I would try would be to loop through each number in [1000, 9999], and test if any permutation of its digits (split in the middle) multiplied to make it.

This will require (9999 - 1000) * 24 = 215,976 tests, which should execute acceptably fast on a modern machine.

I would definitely store the digits separately, so you can avoid having to do something like a bunch of division to extract the digits from a single integer.

If you write your code such that you're only ever doing integer addition and multiplication (and maybe the occasional division to carry), it should be pretty fast. You could further increase the speed by skipping two-digit pairs which "obviously" won't work - e.g., ones with leading zeros (note that the largest product than can be produced by a one digit number and a two digit number is 9 * 99, or 891).

Also note that this approach is embarassingly parallel (http://en.wikipedia.org/wiki/Embarrassingly_parallel), so if you really need to speed it up even more then you should look into testing the numbers in separate threads.

0
votes
<?php
for ($i = 10; $i <= 99; $j++) {

    // Extract digits
    $digits = str_split($i);

    // Loop through 2nd number
    for ($j = 10; $j <= 99; $j++) {

         // Extract digits
         $j_digits = str_split($j);
         $digits[2] = $j_digits[0];
         $digits[3] = $j_digits[1];

         $product = $i * $j;

         $product_digits = str_split($product);

         // check if fangs

         $inc = 0;

         while (in_array($digits[$inc], $product_digits)) {

            // Remove digit from product table
               /// So AAAA -> doesnt match ABCD
             unset($product_digits[$array_serach($digits[$inc], $product_digits)]);

             $inc++;

             // If reached 4 -> vampire number
             if ($inc == 4) {

                  $vampire[] = $product;
                  break;
             }
         }
    }
}

// Print results
print_r($vampire);
?>

Took less than a second on PHP. couldn't even tell it had to run 8100 computations... computers are fast!

Results:

Gives you all the 4 digits plus some are repeated. You can further process the data and remove duplicates.

0
votes

It seems to me that to perform the fewest possible tests without relying on any particularly abstract insights, you probably want to iterate over the fangs and cull any obviously pointless candidates.

For example, since x*y == y*x about half your search space can be eliminated by only evaluating cases where y > x. If the largest two-digit fang is 99 then the smallest which can make a four-digit number is 11, so don't start lower than 11.

EDIT:

OK, throwing everything I thought of into the mix (even though it looks silly against the leading solution).

for (x = 11; x < 100; x++)
{
    /* start y either at x, or if x is too small then 1000 / x */
    for (y = (x * x < 1000 ? 1000 / x : x); y < 100; y++)
    {
        int p = x * y;

        /* if sum of digits in product is != sum of digits in x+y, then skip */
        if ((p - (x + y)) % 9 != 0)
            continue;

        if (is_vampire(p, x, y))
            printf("%d\n", p);
    }
}

and the test, since I haven't seen anyone use a histogram, yet:

int is_vampire(int p, int x, int y)
{
    int h[10] = { 0 };
    int i;
    for (i = 0; i < 4; i++)
    {
        h[p % 10]++;
        p /= 10;
    }
    for (i = 0; i < 2; i++)
    {
        h[x % 10]--;
        h[y % 10]--;
        x /= 10;
        y /= 10;
    }
    for (i = 0; i < 10; i++)
        if (h[i] != 0)
            return 0;
    return 1;
}
0
votes

1260 1395 1435 1530 1827 2187 6880 is vampire

I am new to programming... But there are only 12 combinations in finding all 4-digit vampire numbers. My poor answer is:

public class VampNo {

    public static void main(String[] args) {
        for(int i = 1000; i < 10000; i++) {

            int a = i/1000;
            int b = i/100%10;
            int c = i/10%10;
            int d = i%10;

                 if((a * 10 + b) * (c * 10 + d) == i || (b * 10 + a) * (d * 10 + c) == i ||
                    (a * 10 + d) * (b * 10 + c) == i || (d * 10 + a) * (c * 10 + b) == i ||
                    (a * 10 + c) * (b * 10 + d) == i || (c * 10 + a) * (d * 10 + b) == i ||
                    (a * 10 + b) * (d * 10 + c) == i || (b * 10 + a) * (c * 10 + d) == i ||
                    (b * 10 + c) * (d * 10 + a) == i || (c * 10 + b) * (a * 10 + d) == i ||
                    (a * 10 + c) * (d * 10 + b) == i || (c * 10 + a) * (b * 10 + d) == i)
                System.out.println(i + " is vampire");

        }
    }
}

The main task now is to simplify boolean expression in If() block

0
votes

I've edited Owlstead's algorithm a bit to make it more understandable to Java beginners/learners.

import java.util.Arrays;

public class Vampire {

  public static void main(String[] args) {
    for (int x = 10; x < 100; x++) {
        String sx = Integer.toString(x);
        for (int y = x; y < 100; y++) {
            int v = x * y;
            String sy = Integer.toString(y);
            String sv = Integer.toString(v);
            if( Arrays.equals(sortVampire(sx + sy), sortVampire(sv))) 
                System.out.printf("%d * %d = %d%n", x, y, v);
        }
    }
}
private static char[] sortVampire (String v){
    char[] sortedArray = v.toCharArray();
    Arrays.sort(sortedArray);
    return sortedArray;
}

}

0
votes

This python code run very fast (O(n2))

result = []
for i in range(10,100):
    for j in range(10, 100):
        list1 = []
        list2 = []
        k = i * j
        if k < 1000 or k > 10000:
            continue
        else:
            for item in str(i):
                list1.append(item)
            for item in str(j):
                list1.append(item)
            for item in str(k):
                list2.append(item)
            flag = 1  
            for each in list1:
                if each not in list2:
                    flag = 0
                else:
                    list2.remove(each)
            for each in list2:
                if each not in list1:
                    flag = 0

            if flag == 1:
                if k not in result:
                    result.append(k)
for each in result:
    print(each)          
0
votes

And here is my code. To generate zombie numbers we need to use Random class :)

import java.io.PrintStream;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;

class VampireNumbers {
    static PrintStream p = System.out;

    private static Set<Integer> findVampireNumber() {
        Set<Integer> vampireSet = new HashSet<Integer>();
        for (int y = 1000; y <= 9999; y++) {
            char[] numbersSeparately = ("" + y).toCharArray();
            int numberOfDigits = numbersSeparately.length;
            for (int i = 0; i < numberOfDigits; i++) {
                for (int j = 0; j < numberOfDigits; j++) {
                    if (i != j) {
                        int value1 = Integer.valueOf("" + numbersSeparately[i] + numbersSeparately[j]);
                        int ki = -1;
                        for (int k = 0; k < numberOfDigits; k++) {
                            if (k != i && k != j) {
                                ki = k;
                            }
                        }
                        int kj = -1;
                        for (int t = 0; t < numberOfDigits; t++) {
                            if (t != i && t != j && t != ki) {
                                kj = t;
                            }
                        }

                        int value21 = Integer.valueOf("" + numbersSeparately[ki] + numbersSeparately[kj]);
                        int value22 = Integer.valueOf("" + numbersSeparately[kj] + numbersSeparately[ki]);
                        if (value1 * value21 == y && !(numbersSeparately[j] == 0 && numbersSeparately[kj] == 0)
                                || value1 * value22 == y
                                        && !(numbersSeparately[j] == 0 && numbersSeparately[ki] == 0)) {
                            vampireSet.add(y);
                        }
                    }
                }
            }
        }
        return vampireSet;
    }

    public static void main(String[] args) {
        Set<Integer> vampireSet = findVampireNumber();
        Iterator<Integer> i = vampireSet.iterator();
        int number = 1;
        while (i.hasNext()) {
            p.println(number + ": " + i.next());
            number++;
        }
    }
}