8

Maybe i am just not that good enough in math, but I am having a problem in converting a number into pure alphabetical Bijective Hexavigesimal just like how Microsoft Excel/OpenOffice Calc do it.

Here is a version of my code but did not give me the output i needed:


    var toHexvg = function(a){
     var x='';
     var let="_abcdefghijklmnopqrstuvwxyz";
     var len=let.length;
     var b=a;
     var cnt=0;
     var y = Array();
     do{
      a=(a-(a%len))/len;
      cnt++;
     }while(a!=0)
     a=b;
     var vnt=0;
     do{
      b+=Math.pow((len),vnt)*Math.floor(a/Math.pow((len),vnt+1));
      vnt++;
     }while(vnt!=cnt)
     var c=b;
     do{
      y.unshift( c%len );
      c=(c-(c%len))/len;
     }while(c!=0)
     for(var i in y)x+=let[y[i]];
     return x;
    }

The best output of my efforts can get is: a b c d ... y z ba bb bc - though not the actual code above. The intended output is suppose to be a b c ... y z aa ab ac ... zz aaa aab aac ... zzzzz aaaaaa aaaaab, you get the picture.

Basically, my problem is more on doing the ''math'' rather than the function. Ultimately my question is: How to do the Math in Hexavigesimal conversion, till a [supposed] infinity, just like Microsoft Excel.

And if possible, a source code, thank you in advance.

A. K. Tolentino
  • 2,112
  • 3
  • 25
  • 33
  • 1
    `aa` does not really make sense. It's `00`. The "number" after `z` is `ba`, so your output seems to be correct. Or is `_` your `0`, which seems kinda odd? – Felix Kling Dec 22 '11 at 11:54
  • 1
    uhm, sorry about the sample code, guess i shouldn't posted it, I think it made expressing my question more complicated, ahaha... But, i guess my bottom line is that I need a code that outputs the y z aa ab and not y x ba bb... And you could say '_' is the 0, but the situation i need is that no part of the output may contain any '_'... ^^ hmmm – A. K. Tolentino Dec 22 '11 at 12:09
  • ^ [corrections]: And you could say '\_' (underscore) is the 0, but the situation i need is that no part of the output may NOT contain any '\_' (underscore)... – A. K. Tolentino Dec 22 '11 at 12:27
  • @FelixKling - If you counted to infinity in base 27 with "_" = 0, "a" = 1, .. "z" = 26, "a_" = 27, "aa" = 28, etc., then you struck out all values containing "_" (0) in any "digit", the sequence remaining is how spreadsheet software labels columns. So determining what label goes with, say, column 589 is not just a conversion to base 26 or base 27... – nnnnnn Dec 22 '11 at 12:48
  • [This Question](http://stackoverflow.com/q/3302857/445425) or [this one](http://stackoverflow.com/q/297213/445425) addressing the same issue in other languages may give some clues – chris neilsen Dec 22 '11 at 12:51
  • 1
    @nnnnnn: Yes I understood now. I missed the *bijective* part, but Wikipedia was helpful :) Thanks! – Felix Kling Dec 22 '11 at 12:57
  • Oh dear - base 27? What was I thinking? This is what happens when I post at midnight. – nnnnnn Dec 22 '11 at 13:58
  • 1
    Thanks for asking this - I was trying to do the same thing and resorted to Googling it. – Steven Jul 10 '13 at 19:33
  • I wrote a function to do this and I had absolutely no idea what to name it. :-( I actually [had to ask on a different SE site](http://english.stackexchange.com/q/300389/), where the first answer brought me to this. +1 for teaching me the word "bijective numeration"). –  Jan 17 '16 at 13:51

7 Answers7

13

Okay, here's my attempt, assuming you want the sequence to be start with "a" (representing 0) and going:

a, b, c, ..., y, z, aa, ab, ac, ..., zy, zz, aaa, aab, ...

This works and hopefully makes some sense. The funky line is there because it mathematically makes more sense for 0 to be represented by the empty string and then "a" would be 1, etc.

alpha = "abcdefghijklmnopqrstuvwxyz";

function hex(a) {
  // First figure out how many digits there are.
  a += 1; // This line is funky
  c = 0;
  var x = 1;      
  while (a >= x) {
    c++;
    a -= x;
    x *= 26;
  }

  // Now you can do normal base conversion.
  var s = "";
  for (var i = 0; i < c; i++) {
    s = alpha.charAt(a % 26) + s;
    a = Math.floor(a/26);
  }

  return s;
}

However, if you're planning to simply print them out in order, there are far more efficient methods. For example, using recursion and/or prefixes and stuff.

karnok
  • 1,202
  • 1
  • 12
  • 17
  • Thank you!! ^^ You're awesome..! I never thought of doing it that way... Thank you much... I'll be putting ur name in the credits once the site is up and running, thank you... - ^^ – A. K. Tolentino Dec 23 '11 at 07:46
  • No worries, no need for credit, code should be shared and manipulated freely. Besides, user826788 isn't very romantic... Thanks @Jesse, didn't even notice! – karnok Dec 23 '11 at 14:43
  • This would be the recursion method: `alpha = "abcdefghijklmnopqrstuvwxyz";` `function a(num) { num = num > 0? num - 1 : 0; if (num < alpha.length) return alpha[num]; else return a(Math.floor(num/alpha.length)) + '' + alpha[num%alpha.length]; }` – aravindanve Nov 30 '15 at 13:31
3

Although @user826788 has already posted a working code (which is even a third quicker), I'll post my own work, that I did before finding the posts here (as i didnt know the word "hexavigesimal"). However it also includes the function for the other way round. Note that I use a = 1 as I use it to convert the starting list element from

aa) first
ab) second

to

<ol type="a" start="27">
<li>first</li>
<li>second</li>
</ol>

:

function linum2int(input) {
    input = input.replace(/[^A-Za-z]/, '');
    output = 0;
    for (i = 0; i < input.length; i++) {
        output = output * 26 + parseInt(input.substr(i, 1), 26 + 10) - 9;
    }
    console.log('linum', output);
    return output;
}

function int2linum(input) {

    var zeros = 0;
    var next = input;
    var generation = 0;
    while (next >= 27) {
        next = (next - 1) / 26 - (next - 1) % 26 / 26;
        zeros += next * Math.pow(27, generation);
        generation++;
    }
    output = (input + zeros).toString(27).replace(/./g, function ($0) {
        return '_abcdefghijklmnopqrstuvwxyz'.charAt(parseInt($0, 27));
    });
    return output;
}

linum2int("aa"); // 27
int2linum(27); // "aa"
jakov
  • 641
  • 5
  • 3
1

You could accomplish this with recursion, like this:

const toBijective = n => (n > 26 ? toBijective(Math.floor((n - 1) / 26)) : "") + ((n % 26 || 26) + 9).toString(36);
// Parsing is not recursive
const parseBijective = str => str.split("").reverse().reduce((acc, x, i) => acc + ((parseInt(x, 36) - 9) * (26 ** i)), 0);

toBijective(1) // "a"
toBijective(27) // "aa"
toBijective(703) // "aaa"
toBijective(18279) // "aaaa"
toBijective(127341046141) // "overflow"

parseBijective("Overflow") // 127341046141
A1rPun
  • 16,287
  • 7
  • 57
  • 90
0

Just finished writing this code earlier tonight, and I found this question while on a quest to figure out what to name the damn thing. Here it is (in case anybody feels like using it):

/**
 * Convert an integer to bijective hexavigesimal notation (alphabetic base-26).
 *
 * @param {Number} int - A positive integer above zero
 * @return {String} The number's value expressed in uppercased bijective base-26
 */
function bijectiveBase26(int){
    const sequence    = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const length      = sequence.length;

    if(int <= 0)      return int;
    if(int <= length) return sequence[int - 1];


    let index  = (int % length) || length;
    let result = [sequence[index - 1]];

    while((int = Math.floor((int - 1) / length)) > 0){
        index = (int % length) || length;
        result.push(sequence[index - 1]);
    }

    return result.reverse().join("")
}
0

I had to solve this same problem today for work. My solution is written in Elixir and uses recursion, but I explain the thinking in plain English.

Here are some example transformations:

0 -> "A", 1 -> "B", 2 -> "C", 3 -> "D", .. 25 -> "Z", 26 -> "AA", 27 -> "AB", ...

At first glance it might seem like a normal 26-base counting system but unfortunately it is not so simple. The "problem" becomes clear when you realize:

A = 0
AA = 26

This is at odds with a normal counting system, where "0" does not behave as "1" when it is in a decimal place other than then unit.

To understand the algorithm, consider a simpler but equivalent base-2 system:

A = 0
B = 1
AA = 2
AB = 3
BA = 4
BB = 5
AAA = 6

In a normal binary counting system we can determine the "value" of decimal places by taking increasing powers of 2 (1, 2, 4, 8, 16) and the value of a binary number is calculated by multiplying each digit by that digit place's value. e.g. 10101 = 1 * (2 ^ 4) + 0 * (2 ^ 3) + 1 * (2 ^ 2) + 0 * (2 ^ 1) + 1 * (2 ^ 0) = 21

In our more complicated AB system, we can see by inspection that the decimal place values are:

1, 2, 6, 14, 30, 62

The pattern reveals itself to be (previous_unit_place_value + 1) * 2. As such, to get the next lower unit place value, we divide by 2 and subtract 1.

This can be extended to a base-26 system. Simply divide by 26 and subtract 1.

Now a formula for transforming a normal base-10 number to special base-26 is apparent. Say the input is x.

  1. Create an accumulator list l.
  2. If x is less than 26, set l = [x | l] and go to step 5. Otherwise, continue.
  3. Divide x by 2. The floored result is d and the remainder is r.
  4. Push the remainder as head on an accumulator list. i.e. l = [r | l]
  5. Go to step 2 with with (d - 1) as input, e.g. x = d - 1
  6. Convert """ all elements of l to their corresponding chars. 0 -> A, etc.

So, finally, here is my answer, written in Elixir:

defmodule BijectiveHexavigesimal do
  def to_az_string(number, base \\ 26) do
    number
    |> to_list(base)
    |> Enum.map(&to_char/1)
    |> to_string()
  end

  def to_09_integer(string, base \\ 26) do
    string
    |> String.to_charlist()
    |> Enum.reverse()
    |> Enum.reduce({0, nil}, fn
      char, {_total, nil} ->
        {to_integer(char), 1}

      char, {total, previous_place_value} ->
        char_value = to_integer(char + 1)
        place_value = previous_place_value * base
        new_total = total + char_value * place_value
        {new_total, place_value}
    end)
    |> elem(0)
  end

  def to_list(number, base, acc \\ []) do
    if number < base do
      [number | acc]
    else
      to_list(div(number, base) - 1, base, [rem(number, base) | acc])
    end
  end

  defp to_char(x), do: x + 65
end

You use it simply as BijectiveHexavigesimal.to_az_string(420). It also accepts on optional "base" arg.

I know the OP asked about Javascript but I wanted to provide an Elixir solution for posterity.

Peaceful James
  • 1,807
  • 1
  • 7
  • 16
0

I have published these functions in npm package here:

https://www.npmjs.com/package/@gkucmierz/utils

Converting bijective numeration to number both ways (also BigInt version is included).

https://github.com/gkucmierz/utils/blob/main/src/bijective-numeration.mjs

gkucmierz
  • 1,055
  • 1
  • 9
  • 26
0

I don't understand how to work it out from a formula, but I fooled around with it for a while and came up with the following algorithm to literally count up to the requested column number:

var getAlpha = (function() {
    var alphas = [null, "a"],
        highest = [1];

    return function(decNum) {
        if (alphas[decNum])
            return alphas[decNum];

        var d,
            next,
            carry,
            i = alphas.length;

        for(; i <= decNum; i++) {
            next = "";
            carry = true;
            for(d = 0; d < highest.length; d++){
                if (carry) {
                    if (highest[d] === 26) {
                        highest[d] = 1;
                    } else { 
                        highest[d]++;
                        carry = false;
                    }
                }
                next = String.fromCharCode(
                          highest[d] + 96)
                     + next;
            }
            if (carry) {
                highest.push(1);
                next = "a" + next;
            }
            alphas[i] = next;
        }

        return alphas[decNum];
    };
})();


alert(getAlpha(27));     // "aa"
alert(getAlpha(100000)); // "eqxd"

Demo: http://jsfiddle.net/6SE2f/1/

The highest array holds the current highest number with an array element per "digit" (element 0 is the least significant "digit").

When I started the above it seemed a good idea to cache each value once calculated, to save time if the same value was requested again, but in practice (with Chrome) it only took about 3 seconds to calculate the 1,000,000th value (bdwgn) and about 20 seconds to calculate the 10,000,000th value (uvxxk). With the caching removed it took about 14 seconds to the 10,000,000th value.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241