How to create a function that converts a Number to a Bijective Hexavigesimal?
Asked Answered
P

8

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.

Plast answered 22/12, 2011 at 11:50 Comment(9)
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?Finance
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 ''... ^^ hmmmPlast
^ [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)...Plast
@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...Cambrai
This Question or this one addressing the same issue in other languages may give some cluesWindproof
@nnnnnn: Yes I understood now. I missed the bijective part, but Wikipedia was helpful :) Thanks!Finance
Oh dear - base 27? What was I thinking? This is what happens when I post at midnight.Cambrai
Thanks for asking this - I was trying to do the same thing and resorted to Googling it.Matteroffact
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, where the first answer brought me to this. +1 for teaching me the word "bijective numeration").Scholium
E
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.

Expel answered 22/12, 2011 at 13:31 Comment(3)
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... - ^^Plast
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!Expel
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]; }Guadiana
P
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"
Psychotechnics answered 16/7, 2012 at 14:5 Comment(1)
Btw in firefox the upper limit for type="a" lists is 2147483647 or fxshrxw ;)Psychotechnics
T
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
Thunderclap answered 9/5, 2019 at 2:44 Comment(1)
this isn't bijective, as 0 should map to A, not BTelemechanics
C
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.

Cambrai answered 22/12, 2011 at 13:37 Comment(0)
S
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("")
}
Scholium answered 17/1, 2016 at 14:23 Comment(0)
B
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.

Boding answered 25/2, 2022 at 4:53 Comment(2)
you have a fundamental misunderstanding of bijective bases. you start counting at 1, there is no value for 0. to steal your table: ``` ; ; ; ; ; ; A = 1*2^0 = 1 ; ; ; ; ; ; ; ; ; ; ; ; B = 2*2^0 = 2 ; ; ; ; ; ; ; ; ; ; ; ; AA = 1*2^1 + 1*2^0 = 3 ; ; ; ; ; ; ; ; ; ; ; AB = 1*2^1 + 2*2^0 = 4 ; ; ; ; ; ; ; ; ; ; ; BA = 2*2^1 + 1*2^0 = 5 ; ; ; ; ; ; ; ; ; ; ; BB = 2*2^1 + 2*2^0 = 6 ; ; ; ; ; ; ; ; ; ; ; AAA = 1*2^2 + 1*2^1 + 1*2^0 = 7 ; ; ; ; ; ; ``` they work the same as normal bases, they just represent the base number with one digit instead of using 10 to represent the base number.Gessner
@Gessner You are right. When I wrote this, I did not understand that bijective bases have no zero.Boding
G
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

Gules answered 26/10, 2022 at 3:31 Comment(0)
J
0

Here's a function for converting a number to bijective base-26.

function to(x) {
  const chars = 'abcdefghijklmnopqrstuvwxyz';
  const result = []
  while (x !== 0) {
    result.push(chars[(x - 1) % 26]);
    x = Math.floor((x - 1) / 26);
  }
  return result.reverse().join('');
}

And a function for converting back from bijective base-26.

function from(x) {
  let result = 0;
  x = x.split('');
  let multiplier = 1;
  const offset = 'a'.charCodeAt(0) - 1;
  while (x.length > 0) {
    result += (x.pop().charCodeAt(0) - offset) * multiplier;
    multiplier *= 26;
  }
  return result;
}

The following will output xfd and 16384.

console.log(to(16384))
console.log(from('xfd'))
Jumbo answered 20/8, 2024 at 18:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.