0

I'm trying to get a specific combination from all possible combinations of a 6 character combination with any characters a-z,0-9. There are a few hundred million possible combinations and I can find the offset when generating the combinations through recursion but I would much rather use an algorithm that doesn't need to store all preceding combinations to arrive at the given offset. I'm not a math guy and I figure this is pretty mathie. Any advice would be greatly appreciated... Thanks

I found this question: Find the index of a specific combination without generating all ncr combinations

.. but I'm not adept at C# and not sure if I can correctly translate it to PHP.

tom_nb_ny
  • 110
  • 10
  • Tomp, it is not clear if you want a function `combination => index` or reverse `index => combination`. Could you specify which one you really need? Also could you specify how exactly your combinations are built? Can the same character be in the combination several times? So is `aaaaaa` a valid combination? – SergGr Jan 27 '18 at 18:11

1 Answers1

2

I'm not a PHP-guy but hopefully a JavaScript code with no advanced magic could be our common ground. So first here is some code:

const defaultChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

function indexToPermutation(index, targetLength, chars) {
    if (arguments.length < 3)
        chars = defaultChars;
    const charsLen = chars.length;
    let combinationChars = [];
    for (let i = 0; i < targetLength; i++) {
        let ch = chars[index % charsLen];
        combinationChars.push(ch);
        index = Math.floor(index / charsLen); //integer division
    }
    return combinationChars.reverse().join("")
}

function permutationToIndex(combination, chars) {
    if (arguments.length < 2)
        chars = defaultChars;
    const charsLen = chars.length;
    let index = 0;
    let combinationChars = combination.split("");
    for (let i = 0; i < combination.length; i++) {
        let digit = chars.indexOf(combinationChars[i]);
        index = index * charsLen + digit;
    }
    return index;
}


function indexToCombination(index, targetLength, chars) {
    if (arguments.length < 3)
        chars = defaultChars;

    let base = chars.length - targetLength + 1;
    let combinationIndices = [];
    for (let i = 0; i < targetLength; i++) {
        let digit = index % base;
        combinationIndices.push(digit);
        index = Math.floor(index / base); //integer division
        base++;
    }
    let combinationChars = [];
    for (let i = targetLength - 1; i >= 0; i--) {
        let ch = chars[combinationIndices[i]];
        combinationChars.push(ch);
        // here it is important that chars is only local copy rather than global variable
        chars = chars.slice(0, combinationIndices[i]) + chars.slice(combinationIndices[i] + 1);  // effectively chars.removeAt(combinationIndices[i])
    }

    return combinationChars.join("")
}

function combinationToIndex(combination, chars) {
    if (arguments.length < 2)
        chars = defaultChars;
    const charLength = chars.length; // full length before removing!
    let combinationChars = combination.split("");
    let digits = [];
    for (let i = 0; i < combination.length; i++) {
        let digit = chars.indexOf(combinationChars[i]);
        // here it is important that chars is only local copy rather than global variable
        chars = chars.slice(0, digit) + chars.slice(digit + 1); // effectively chars.removeAt(digit)
        digits.push(digit);
    }
    let index = 0;
    let base = charLength;
    for (let i = 0; i < combination.length; i++) {
        index = index * base + digits[i];
        base--;
    }
    return index;
}

and here are some results

indexToPermutation(0, 6) => "000000"
indexToPermutation(1, 6) => "000001"
indexToPermutation(123, 6) => "00003F"
permutationToIndex("00003F") => 123
permutationToIndex("000123") => 1371

indexToCombination(0,6) => "012345"
indexToCombination(1,6) => "012346"
indexToCombination(123,6) => "01237Z"
combinationToIndex("01237Z") => 123
combinationToIndex("543210") => 199331519

Obviously if order of digits and letters is different for you, you can change it by either passing explicitly chars (last argument) or changing defaultChars.

Some math behind it

For permutations you may see a string as a number written in a 36-base numeral system (36 = 10 digits + 26 letters) similar to how hexadecimal works. So converting index <=> permutation is actually a simple job of converting to and from that numeral system.

For combinations idea is similar but the numeral system is what I call a "variable-base" numeral system with base changing from 36 to 36-n+1 (where n is the target length of combination). An example of a more familiar "variable-base" numeral system is clock. If you want to convert milliseconds into time, you first divide by 1000 milliseconds, then by 60 seconds, then by 60 minutes, and then by 24 hours. The only really trick part here is that which "digits" are allowed for each position depends on which digits are already used by the previous positions (size of the digits set is always the same nevertheless). This tricky part is the reason chars argument is modified to remove one char after each iteration in both combinationToIndex and indexToCombination.

SergGr
  • 23,570
  • 2
  • 30
  • 51
  • Thank you! indexToPermutation and indexToCombination achieve the same end goal for me. Would you recommend one over the other? – tom_nb_ny Jan 29 '18 at 16:53
  • @tomp, they are different. Very first (0-th) `indexToPermutation(0, 6)` generates "000000" (note that all the digits/chars are the same). Very first (0-th) `indexToCombination(0,6)` generates "012345" (note that all digits/chars are different). Which one you need: the one that generates strings of only different chars or the one that generate strings that might contain the same char several time - is a question about your business logic. – SergGr Jan 29 '18 at 18:10