Alpha Challenge

john.uhden
Mentor
Mentor

Alpha Challenge

john.uhden
Mentor
Mentor

The objective is to create a function that returns a string from an integer based on 1=A, 26=Z, 27=AA, 52=AZ, 53=AAA, etc.

 

My bloated attempt is below, but I know that a number of you wizards can do a lot better, even keeping the function producing correct results on up to 1000 and more.  I am personally challenged with recursive functions which I think is the way to go.

 

(defun alpha (n / a)
  (cond
    ((<= n 26) (chr (+ 64 n)))
    ((< 26 n 52) (strcat "A" (chr (+ 64 (if (= (setq a (rem n 26)) 0) 1 a)))))
    ((= n 52) "AZ")
    ((< 52 n 78) (strcat "AA" (chr (+ 64 (if (= (setq a (rem n 26)) 0) 1 a)))))
    ("ZZZZZ") ;; Just a bail-out condition
  )
)

The winner will receive a personally autographed digital copy of my AARP card, if I can find it.

John F. Uhden

0 Likes
Reply
Accepted solutions (2)
1,804 Views
26 Replies
Replies (26)

dbroad
Mentor
Mentor
Accepted solution

Following your logic, this would work but it seems a strange code.

(defun ntos (n)
  (if (< n 27)
    (chr (+ n 64))
    (strcat "A" (ntos (- n 26)))
    )
  )
Architect, Registered NC, VA, SC, & GA.
0 Likes

Kent1Cooper
Consultant
Consultant

@john.uhden wrote:

The objective is to create a function that returns a string from an integer based on 1=A, 26=Z, 27=AA, 52=AZ, 53=AAA, etc.

....


You're going to end up with huge strings of A's, and only the last character running through the rest of the letters.  Shouldn't 53 be BA, through 78 = BZ, 79 = CA through 104 = CZ, etc.?  That would be [close to] "straight" base-26 counting, though without using the numerical characters as they do in hexadecimal.  But actually it would be base-26 + 1, because the single characters A through Z would represent 0 through 25 (one less than the base), just as in base 10 the single characters are 0 through 9.  Then the next number after that (the base value itself) is the first with two characters, and in the first two-character number the second character is 0.]

 

Under that scenario, I have a base-10-to-hexadecimal converter [developed for dealing with entity handles] that I tried to adjust, and I got it to compensate for the lowest "digit" in the series ["A"] representing 1, rather than 0 as it does in base-10 [or base-2, or base-anything under usual mathematical practice].  I forced it to actually work with the given number minus 1.  But it gets complicated when you get into multiple characters, and the compensation has to be "tweaked" for the second character in a different way.  It works as far as 675 [which is 26 squared minus 1] -- that's "YZ".  But it's a "forced" compensation that would need to compensate in a "differenter" way as you get up into higher numbers.  For 676 [which is 26 squared, and as the next number after "YZ" one would want to be "ZA"] it fails -- it gives "A@A".  It goes into 3 digits there [the middle one is the character just before "A" in the (ascii) code numbers], because the square of the base number [again, under any standard base-whatever system] always kicks into 3 digits -- the square itself in numerical-character systems is always represented by 100 within its own system.

 

I think if you want something that will work up into thousands, you may have to settle for A representing 0, not 1.  1 would be B, 25 would be Z, 26 would be BA [equivalent to 10 in any other base -- the base number itself is always represented by 10 in numerical-character systems].  If you can use it that way, I think I can provide a converter pretty easily, with a different [and simpler] adjustment of the hexadecimal version, and it would work consistently up into the gazillions, without any forced compensations.

 

And besides, if A is 1, and all the characters are letters from 1 to 26, what do you do if the number you want to convert into such a string is zero?  You have no character for that.  You could have Z  be 0, so A through Z represent 1 through 0 in the same way as the purely numerical sequence 1234567890, but that's really out of sequence.  26 would be AZ, but at least that's consistent with the base number always being the first with two characters, equivalent to its being represented by 10 in any numerical-character system.  26 squared would be AZZ, etc.

 

Another possibility that would be easy to work out would be to use 0 with its typical meaning, and A through only Y for 1 through 25.  26 would be A0, 26 squared would be A00, etc.  Or if you want to include Z, make it a base-27 system, where 26 would still be Z, 27 would be A0, 28 would be AA, etc.

Kent Cooper, AIA
0 Likes

dbroad
Mentor
Mentor

Agreed.  Strange sequence.  I was thinking of a type of base 26 system as well.

Architect, Registered NC, VA, SC, & GA.
0 Likes

john.uhden
Mentor
Mentor

Although Kent is sounding like his cousin, Dr. Sheldon Cooper, he makes a good point about a string full of As.  So I took a look at Excel, and after AZ comes BA up to ZZ after which comes AAA up to AZZ after which is BAA.  Maybe that equates to (expt 26 3) = 17576 which I'm sure is way more than enough.

Can ntos be altered to use the Excel system?

John F. Uhden

0 Likes

Kent1Cooper
Consultant
Consultant

The converter I was monkeying with is at home, and I'll look into it further this evening or this weekend.

 

One major complication is that in any numerical base-whatever system, at the point of shifting from one character to two characters, the two characters are not the same as each other ["10"], but in what you're talking about, they are the same ["AA"].  And similarly at every shift to the next-higher number of characters.  I was able to force correction for that with two-digit numbers, but the correction has to be different once it shifts to three characters -- I think the correction itself  has to "shift over" and leave the previously-corrected place un-corrected, or something like that.

 

I think I have a notion how that might be done in a way that will translate through any number of characters, but it will have to wait until I can get into that file again -- I have the source hexadecimal one where I am now, but I don't want to redo all the monkeying I already did, try to recall the things I tried that didn't work, etc.  If you want to try messing with it, you can get it here.

Kent Cooper, AIA
0 Likes

john.uhden
Mentor
Mentor

I've got enough characters right here in my own family.  If you don't mind, I would appreciate your messing.

 

Maybe base 0 would be better;  one answer for everything, right?

John F. Uhden

0 Likes

marko_ribar
Advisor
Advisor

John, here is how would Owen Wengerd from manusoft increment strings :

 

(defun str+ ( source / prefix rchar )
  (cond
    ( (= source "") "A")
    ( (= (setq prefix (substr source 1 (1- (strlen source)))
                rchar (substr source (strlen source))
          )
          "Z"
      )
      (strcat (str+ prefix) "A")
    )
    ( (strcat prefix (chr (1+ (ascii rchar)))))
  )
)

 

And as I see that you're looking for Excel-ACAD manipulations, here is how would Gilles Chantaux approach to this problem... Here are couple of his sub functions implemented in my modification of GetExcel.lsp (look into sub functions) :

 

(defun c:excel2lst ( / excel fn )

  (defun excel ( ExcelFile$ / Cell->ColumnRow ColumnRow->Cell Alpha2Number Number2Alpha LM:listbox startcell endcell stc enc colrow *ExcelApp% Sheets@ SheetName$ CurRange c r Value Valuel Valuelst )

    (vl-load-com)

    ;-------------------------------------------------------------------------------
    ; Cell->ColumnRow - Returns a list of the Column and Row number
    ; Function By: Gilles Chanteau from Marseille, France
    ; Arguments: 1
    ;   Cell$ = Cell ID
    ; Syntax example: (Cell->ColumnRow "ABC987") = '(731 987)
    ;-------------------------------------------------------------------------------
    (defun Cell->ColumnRow (Cell$ / Column$ Char$ Row#)
      (setq Column$ "")
      (while (< 64 (ascii (setq Char$ (strcase (substr Cell$ 1 1)))) 91)
        (setq Column$ (strcat Column$ Char$)
              Cell$ (substr Cell$ 2)
        );setq
      );while
      (if (and (/= Column$ "") (numberp (setq Row# (read Cell$))))
        (list (Alpha2Number Column$) Row#)
        '(1 1);default to "A1" if there's a problem
      );if
    );defun Cell->ColumnRow
    ;-------------------------------------------------------------------------------
    ; ColumnRow->Cell - Returns Cell ID from list of the Column and Row number
    ; Function By: Marko Ribar from Belgrade, Serbia
    ; Arguments: 1
    ;   ColumnRow$ = list
    ; Syntax example: (ColumnRow->Cell '(731 987)) = "ABC987"
    ;-------------------------------------------------------------------------------
    (defun ColumnRow->Cell (Lst$ / Column$)
      (setq Column$ (Number2Alpha (car Lst$)))
      (strcat Column$ (itoa (cadr Lst$)))
    );defun ColumnRow->Cell
    ;-------------------------------------------------------------------------------
    ; Alpha2Number - Converts Alpha string into Number
    ; Function By: Gilles Chanteau from Marseille, France
    ; Arguments: 1
    ;   Str$ = String to convert
    ; Syntax example: (Alpha2Number "ABC") = 731
    ;-------------------------------------------------------------------------------
    (defun Alpha2Number (Str$ / Num#)
      (if (= 0 (setq Num# (strlen Str$)))
        0
        (+ (* (- (ascii (strcase (substr Str$ 1 1))) 64) (expt 26 (1- Num#)))
           (Alpha2Number (substr Str$ 2))
        );+
      );if
    );defun Alpha2Number
    ;-------------------------------------------------------------------------------
    ; Number2Alpha - Converts Number into Alpha string
    ; Function By: Gilles Chanteau from Marseille, France
    ; Arguments: 1
    ;   Num# = Number to convert
    ; Syntax example: (Number2Alpha 731) = "ABC"
    ;-------------------------------------------------------------------------------
    (defun Number2Alpha (Num# / Val#)
      (if (< Num# 27)
        (chr (+ 64 Num#))
        (if (= 0 (setq Val# (rem Num# 26)))
          (strcat (Number2Alpha (1- (/ Num# 26))) "Z")
          (strcat (Number2Alpha (/ Num# 26)) (chr (+ 64 Val#)))
        );if
      );if
    );defun Number2Alpha

    ;; List Box  -  Lee Mac
    ;; Displays a DCL list box allowing the user to make a selection from the supplied data.
    ;; msg - [str] Dialog label
    ;; lst - [lst] List of strings to display
    ;; bit - [int] 1=allow multiple; 2=return indexes
    ;; Returns: [lst] List of selected items/indexes, else nil
     
    (defun LM:listbox ( msg lst bit / dch des tmp rtn )
        (cond
            (   (not
                    (and
                        (setq tmp (vl-filename-mktemp nil nil ".dcl"))
                        (setq des (open tmp "w"))
                        (write-line
                            (strcat "listbox:dialog{label=\"" msg "\";spacer;:list_box{key=\"list\";multiple_select="
                                (if (= 1 (logand 1 bit)) "true" "false") ";width=50;height=15;}spacer;ok_cancel;}"
                            )
                            des
                        )
                        (not (close des))
                        (< 0 (setq dch (load_dialog tmp)))
                        (new_dialog "listbox" dch)
                    )
                )
                (prompt "\nError Loading List Box Dialog.")
            )
            (   t     
                (start_list "list")
                (foreach itm lst (add_list itm))
                (end_list)
                (setq rtn (set_tile "list" "0"))
                (action_tile "list" "(setq rtn $value)")
                (setq rtn
                    (if (= 1 (start_dialog))
                        (if (= 2 (logand 2 bit))
                            (read (strcat "(" rtn ")"))
                            (mapcar '(lambda ( x ) (nth x lst)) (read (strcat "(" rtn ")")))
                        )
                    )
                )
            )
        )
        (if (< 0 dch)
            (unload_dialog dch)
        )
        (if (and tmp (setq tmp (findfile tmp)))
            (vl-file-delete tmp)
        )
        rtn
    )

    (setq ExcelFile$ (findfile ExcelFile$))
    (setq *ExcelApp% (vlax-get-or-create-object "Excel.Application"))
    ;;;(vlax-put-property *ExcelApp% "Visible" :vlax-true)
    (vlax-invoke-method (vlax-get-property *ExcelApp% 'WorkBooks) 'Open ExcelFile$)
    (vlax-for Sheet$ (vlax-get-property *ExcelApp% "Sheets")
      (setq Sheets@ (append Sheets@ (list (vlax-get-property Sheet$ "Name"))))
    )
    (setq SheetName$ (car (LM:Listbox "Select Sheet to process..." Sheets@ 0)))
    (vlax-for Worksheet (vlax-get-property *ExcelApp% "Sheets")
      (if (= (vlax-get-property Worksheet "Name") SheetName$)
        (vlax-invoke-method Worksheet "Activate")
      )
    )
    (setq startcell (strcase (getstring "\nSpecify start cell of excel table - upper left cell : ")))
    (setq endcell (strcase (getstring "\nSpecify end cell of excel table - lower right cell : ")))
    (setq stc (Cell->ColumnRow startcell))
    (setq enc (Cell->ColumnRow endcell))
    (setq colrow (mapcar '- enc stc))
    (setq r (1- (cadr stc)))
    (repeat (1+ (cadr colrow))
      (setq c (1- (car stc)) r (1+ r))
      (repeat (1+ (car colrow))
        (setq CurRange (vlax-get-property (vlax-get-property *ExcelApp% "ActiveSheet") "Range" (ColumnRow->Cell (list (setq c (1+ c)) r))))
        (setq Value (vlax-get CurRange 'Text))
        (setq Valuel (cons Value Valuel))
      )
      (setq Valuel (reverse Valuel))
      (setq Valuelst (cons Valuel Valuelst) Valuel nil)
    )
    (vlax-invoke-method (vlax-get-property *ExcelApp% "ActiveWorkbook") 'Close :vlax-False)
    (vlax-invoke-method *ExcelApp% 'Quit)
    (vlax-release-object *ExcelApp%)(gc)
    (reverse Valuelst)
  )

  (setq fn (getfiled "Select Excel file to process..." "\\" "xlsx;xls;csv;*" 16))
  (setq *excellst* (excel fn))
  (prompt "\nProcessed list is stored in variable *excellst* ...")
  (textscr)
  (princ)
)

I hope this is close to what you're looking for... Hope it helps, regards, M.R.

Marko Ribar, d.i.a. (graduated engineer of architecture)

Kent1Cooper
Consultant
Consultant

That (Number2Alpha) function seems to do just what the doctor ordered -- amazing what recursive operations can do.  It's a lot shorter than what I was working on, though mine was coming from something originally for a more generic conversion, that could also take 0 and negative integers [which (Number2Alpha) won't convert correctly], but I assume those are not part of the picture here.

Kent Cooper, AIA
0 Likes

john.uhden
Mentor
Mentor

I think that Number2Alpha function is probably the winner.  Thank you, Marko, for submitting it!

I need to take a closer look at Owen's str+ function.  He is one of my idols.

I'll just give a few more days for anyone else to chime in.

 

BTW, is Gilles the same as that Gile or Giles person we see referred to here so often?

John F. Uhden

0 Likes

marko_ribar
Advisor
Advisor

(eq _Gile Gilles) => T

(eq gile Gilles) => T

 

http://forums.autodesk.com/t5/user/viewprofilepage/user-id/109424

Marko Ribar, d.i.a. (graduated engineer of architecture)
0 Likes

dbroad
Mentor
Mentor
Accepted solution

Sorry @john.uhden.  Been busy with a Statics and Strengths class today.  Here is one that works with a base 26, using 0 as A and 25 as Z.

 

;;Convert base 10 to letters
;;0 = a to 25 = z for each digit.
;;Works for 0 to (- (expt 26 7) 1) integer maximum
(defun ntos (n / r d)
  (if
    (< n 26)
    (chr (+ 65 n))
    (progn
      (setq r (/ n 26)
	   d (rem n 26)
     )
     (strcat (ntos r)
	      (chr (+ 65 d))
	     )
     )
  )
  )

 

Architect, Registered NC, VA, SC, & GA.

john.uhden
Mentor
Mentor

Holy defecation, Doug.  That may well be the winner.

Just don't give me that old "structural" bull.

I did a little Cooperial compaction and accounted for the number being off by one, as follows:

 

(defun ntos (n)
  (if (< (setq n (1- n)) 26)
    (chr (+ 65 n))
    (strcat (ntos (/ n 26))(chr (+ 65 (rem n 26))))
  )
)

It works!!

John F. Uhden

0 Likes

dbroad
Mentor
Mentor

IMO, its better to revise the input than to revise the program, but whatever floats your boat. 🙂

 

(ntos (- 26 1)) -> "Z"

 

Architect, Registered NC, VA, SC, & GA.
0 Likes

john.uhden
Mentor
Mentor

NNZZZZZZZZZZZZZZZZZTTTTT.

 

Using my version or your revised function...

 

Command: (ntos 25) "Y"

Command: (ntos 26) "Z"

 

I'll have to do a time test on Number2Alpha vs. ntos.

My boat floats fine.  It just doesn't run.  Wanna buy it?

John F. Uhden

0 Likes

john.uhden
Mentor
Mentor

Well, here are the timetest results...

 

Command: (timer (list number2alpha 731) 100000)
Elapsed time for 100000 iterations: 6.15 secs.
"ABC"

Command: (timer (list ntos 731) 100000)
Elapsed time for 100000 iterations: 6.04 secs.
"ABC"

That to me is a virtual tie.

The judges will decide by Monday, after the neighbors' dog has reviewed.  She is a mixed breed so she has no bias.

John F. Uhden

0 Likes

phanaem
Collaborator
Collaborator

Number to alpha and inverse transformation

(defun nr->alpha (n / l r)
  (while
    (> n 0)
    (setq r (rem (1- n) 26)
          n (/   (1- n) 26)
          l (cons (+ 65 r) l)
    )
  )
  (vl-list->string l)
)

(defun alpha->nr (s / i)
  (setq i -1)
  (apply '+
    (mapcar
      '(lambda (x)
         (* (- x 64) (expt 26 (setq i (1+ i))))
       )
       (reverse (vl-string->list s))
    )
  )
)

 

john.uhden
Mentor
Mentor

Excellent Mr. Phenom, and it's right in there on the timer, and with no recursion!

John F. Uhden

0 Likes

john.uhden
Mentor
Mentor

After careful deliberation between myself and my neighbor's dogs. the winner of the Alpha Challenge is @dbroad with his very concise and recursive ntos function (though I had to fix it).  Honorable mentions go to @marko_ribar and @phanaem for virtually equally performing functions.

 

The function is a critical part of my response to @Kyle.para re: Autogeneration of attributes.

 

I need Doug's email address to send him my personally autographed digital copy of my AARP card.  If he refuses, I will send him my floating boat that doesn't run.  BTW, Doug's credibility is suspect as he has claimed he is no programmer, just a Professor of Architectural Technology.  What do those architects know anyway?

 

THANK YOU ALL!!

John F. Uhden

0 Likes

dbroad
Mentor
Mentor

@john.uhden

 

Smiley Happy  Thanks John.  Boats are too expensive though, unless you live to work on them.  My father loved working on his boat. 

 

AARP is after me too.

Architect, Registered NC, VA, SC, & GA.
0 Likes