Indexing system - ruby newbie

Hi there,

I'm trying to write a class which converts a number into letters like
so:
0 => -
1 => A
10 => J
27 => AA

... ad infinitum. My class looks like this at the moment (please don't
laugh!)

def letter(number)
  @index = number - 1
  if @index == 0
    return "-"
  end
  @index_string = ""
  @index_array= []
  while @index > 0 do
    @remainder = @index%27
    @index_array << @remainder
    @index = @index/27
  end
  @index_array
  @index_array.each do |i|
  # I'm sure there's a better way to do this
   @alphabet =
["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
    @index_string << @alphabet[i-1]
  end
  return @index_string.reverse!
end

This works fine for the first round: 26 returns "Z". But 27 returns "AZ"
because @index_array is [0,1]. I'd appreciate any help (and tips on how
to write tighter code!)

···

--
Posted via http://www.ruby-forum.com/.

Adam Groves wrote:

Hi there,

I'm trying to write a class which converts a number into letters like
so:
0 => -
1 => A
10 => J
27 => AA

Try this:

n = ""
while(n > 0)
  s << ?A + n % 26 - 1
  n /= 26
end
n << "-" if n.empty?
s.reverse

Also, your @alphabet is ("a".."z").to_a

Cheers,
Dave

Adam Groves wrote:

Hi there,

I'm trying to write a class which converts a number into letters like
so:
0 => -
1 => A
10 => J
27 => AA

.. ad infinitum. My class looks like this at the moment (please don't
laugh!)

def letter(number)
  @index = number - 1
  if @index == 0
    return "-"
  end
  @index_string = ""
  @index_array=
  while @index > 0 do
    @remainder = @index%27
    @index_array << @remainder
    @index = @index/27
  end
  @index_array
  @index_array.each do |i|
  # I'm sure there's a better way to do this
   @alphabet =
["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
    @index_string << @alphabet[i-1]
  end
  return @index_string.reverse!
end

This works fine for the first round: 26 returns "Z". But 27 returns "AZ"
because @index_array is [0,1]. I'd appreciate any help (and tips on how
to write tighter code!)

--
Posted via http://www.ruby-forum.com/\.

def letter( n )
  return "-" if n == 0

  result = n.to_s(27)
  i = 0
  while i < result.size - 1
    inc = result[-i-2,1].to_i(27)
    inc += 1 if inc + result[-1,1].to_i(27) > 26
    n += 27**i * inc
    result = n.to_s(27)
    i += 1
  end

  result.tr(((1..9).to_a + ('a'..'q').to_a).join,
             ('A'..'Z').to_a.join )
end

could add one method to Integer
and avoid a separate class.
(_to_aa is a helper that works on
0..25 nstead of 1..26)

eg
puts 10.to_aa, (10**1000).to_aa

class Integer

  def to_aa # (1..26) to (A..Z)
    if (self < 1)
        '-'
    else
      (self-1)._to_aa
    end
  end

  protected
  def _to_aa # helper (0..25) to (A..Z)
    if (self < 26)
      (self+?A).chr
    else
      ((self/26)-1)._to_aa+(self%26)._to_aa
    end
  end

end

···

On 2006-03-03, Malte Milatz <malteNOSPAM@gmx-topmail.de> wrote:

Adam Groves schrieb:

I'm trying to write a class which converts a number into letters like
so:
0 => -
1 => A
10 => J
27 => AA

Only after reading the other posts, I realized you want Y, Z, AA, AB, AC
and not Y, Z, AA, BB, CC, which is what is produced by this:

def letter(i)
  return '-' if i < 1
  ('A'..'Z').to_a.at(i%26 - 1) * ((i-1)/26 + 1)
end

Malte

This isn't as good as Dave's (it's potentially *lots* slower for a
start) but, well, I'm just an #inject addict really...:

  def letter(n)
    (n < 1) ? '_' : (1...n).inject("A") { |curr, i| curr.succ }
  end

  letter(0)
  # => "_"
  letter(1)
  # => "A"
  letter(10)
  # => "J"
  letter(27)
  # => "AA"
  letter(397)
  # => "OG"

···

On Wed, 2006-03-01 at 22:33 +0900, Dave Burt wrote:

Adam Groves wrote:
> Hi there,
>
> I'm trying to write a class which converts a number into letters like
> so:
> 0 => -
> 1 => A
> 10 => J
> 27 => AA

Try this:

n = ""
while(n > 0)
  s << ?A + n % 26 - 1
  n /= 26
end
n << "-" if n.empty?
s.reverse

Also, your @alphabet is ("a".."z").to_a

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

Hey thanks guys. I'm really enjoying learning ruby - especially because
the ruby community is so helpful.

Ross Bamford wrote:

···

On Wed, 2006-03-01 at 22:33 +0900, Dave Burt wrote:

Try this:

n = ""
while(n > 0)
  s << ?A + n % 26 - 1
  n /= 26
end
n << "-" if n.empty?
s.reverse

Also, your @alphabet is ("a".."z").to_a

This isn't as good as Dave's (it's potentially *lots* slower for a
start) but, well, I'm just an #inject addict really...:

  def letter(n)
    (n < 1) ? '_' : (1...n).inject("A") { |curr, i| curr.succ }
  end

  letter(0)
  # => "_"
  letter(1)
  # => "A"
  letter(10)
  # => "J"
  letter(27)
  # => "AA"
  letter(397)
  # => "OG"

--
Posted via http://www.ruby-forum.com/\.

Dear Ross,

it works a treat but I'm having a bit of trouble figuring out what's
going on.

     (n < 1) ? '_' : (1...n).inject("A") { |curr, i| curr.succ }

I get this:
if n<1
  '_'
else

But I'm stuck here.

  (1...n).inject("A") { |curr, i| curr.succ}

I still can't quite get my head around blocks beyond .each do |x|

Ross Bamford wrote:

···

On Wed, 2006-03-01 at 22:33 +0900, Dave Burt wrote:

Try this:

n = ""
while(n > 0)
  s << ?A + n % 26 - 1
  n /= 26
end
n << "-" if n.empty?
s.reverse

Also, your @alphabet is ("a".."z").to_a

This isn't as good as Dave's (it's potentially *lots* slower for a
start) but, well, I'm just an #inject addict really...:

  def letter(n)
    (n < 1) ? '_' : (1...n).inject("A") { |curr, i| curr.succ }
  end

  letter(0)
  # => "_"
  letter(1)
  # => "A"
  letter(10)
  # => "J"
  letter(27)
  # => "AA"
  letter(397)
  # => "OG"

--
Posted via http://www.ruby-forum.com/\.

#inject is one of the basic tools of functional programming. That's
why it seems hard. It's a very different way of thinking. And also
very powerful.

That said, I think this case is a completely gratuitious use of
inject. The tipoff is that the argument "i" is completely ignored.

I like this much better:

def letter(n)
  return '_' if n==0
  n==1 ? "A" : letter(n-1).succ
end

regards,
Ed

···

On Thu, Mar 02, 2006 at 12:14:47AM +0900, Adam Groves wrote:

But I'm stuck here.

  (1...n).inject("A") { |curr, i| curr.succ}

I still can't quite get my head around blocks beyond .each do |x|

Inject is real easy, and very handy. It's just like 'each', except it
also allows the result of the previous iteration to be injected via the
first argument. For the first iteration, you provide the initial result.

For example:

  a = [1,2,3,4,5]

  a.inject(0) { |sum, i| sum + i }
  # => 15

What happens is:

  Block is called with sum = 0, i = 1
    Block returns 1
  Block is called with sum = 1, i = 2
    Block returns 3
  Block is called with sum = 3, i = 3
    Block returns 6
  Block is called with sum = 6, i = 4
    Block returns 10
  Block is called with sum = 10, i = 5
    Block returns 15
  No more elements, so inject returns 15.

In Ruby, inject allows you to omit the initial value, in which case the
first _two_ elements from the enumerable are passed to the first
iteration, with things proceeding as above from there, so I could have
written:

  a.inject { |sum, i| sum + i }

And would have:

  Block is called with sum = 1, i = 2
    Block returns 3
  Block is called with sum = 3, i = 3
    Block returns 6
  .
  .
  etc.

You can use inject for much more than just summing stuff up. Comes in
very handy for these cryptic one-lines (even if you have to 'misuse' it
a bit occasionally).

···

On Thu, 2006-03-02 at 00:14 +0900, Adam Groves wrote:

Dear Ross,

it works a treat but I'm having a bit of trouble figuring out what's
going on.

     (n < 1) ? '_' : (1...n).inject("A") { |curr, i| curr.succ }

I get this:
if n<1
  '_'
else

But I'm stuck here.

  (1...n).inject("A") { |curr, i| curr.succ}

I still can't quite get my head around blocks beyond .each do |x|

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

> But I'm stuck here.
>
> (1...n).inject("A") { |curr, i| curr.succ}
>
> I still can't quite get my head around blocks beyond .each do |x|

#inject is one of the basic tools of functional programming. That's
why it seems hard. It's a very different way of thinking. And also
very powerful.

That said, I think this case is a completely gratuitious use of
inject. The tipoff is that the argument "i" is completely ignored.

Well, sorry, I didn't realise we had to use them all. I like to use it
where I want to give back something new from a block, but don't want:

  a =
  something.each { |e| a << e end }
  a

In this case it was just a snazzier alternative to doing the (n-1).times
and so on...

Did I say I'm an #inject *addict* ?

I like this much better:

def letter(n)
  return '_' if n==0
  n==1 ? "A" : letter(n-1).succ
end

Well, to each his own, but (maybe this is a bit pathological,
though...):

  def letter(n)
    (1...n).inject("A") { |curr, i| curr.succ}
  end

  def letter2(n)
    return '_' if n==0
    n==1 ? "A" : letter2(n-1).succ
  end

  p letter(327021)
  # => "ROSS"

  p letter2(327021)
  # => -:3:in `letter2': stack level too deep (SystemStackError)
                    from -:3:in `letter2'
              from -:11

···

On Thu, 2006-03-02 at 00:50 +0900, Edward Faulkner wrote:

On Thu, Mar 02, 2006 at 12:14:47AM +0900, Adam Groves wrote:

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

how about that?

(n < 1) ? "_" : (("A"[0] + n).chr)

n = 12

···

M

--- Ursprüngliche Nachricht ---
Von: Ross Bamford <rossrt@roscopeco.co.uk>
An: ruby-talk@ruby-lang.org (ruby-talk ML)
Betreff: Re: Indexing system - ruby newbie
Datum: Thu, 2 Mar 2006 01:37:06 +0900

On Thu, 2006-03-02 at 00:14 +0900, Adam Groves wrote:
> Dear Ross,
>
> it works a treat but I'm having a bit of trouble figuring out what's
> going on.
>
> (n < 1) ? '_' : (1...n).inject("A") { |curr, i| curr.succ }
>
> I get this:
> if n<1
> '_'
> else
>
> But I'm stuck here.
>
> (1...n).inject("A") { |curr, i| curr.succ}
>
> I still can't quite get my head around blocks beyond .each do |x|

Inject is real easy, and very handy. It's just like 'each', except it
also allows the result of the previous iteration to be injected via the
first argument. For the first iteration, you provide the initial result.

For example:

  a = [1,2,3,4,5]

  a.inject(0) { |sum, i| sum + i }
  # => 15

What happens is:

  Block is called with sum = 0, i = 1
    Block returns 1
  Block is called with sum = 1, i = 2
    Block returns 3
  Block is called with sum = 3, i = 3
    Block returns 6
  Block is called with sum = 6, i = 4
    Block returns 10
  Block is called with sum = 10, i = 5
    Block returns 15
  No more elements, so inject returns 15.

In Ruby, inject allows you to omit the initial value, in which case the
first _two_ elements from the enumerable are passed to the first
iteration, with things proceeding as above from there, so I could have
written:

  a.inject { |sum, i| sum + i }

And would have:

  Block is called with sum = 1, i = 2
    Block returns 3
  Block is called with sum = 3, i = 3
    Block returns 6
  .
  .
  etc.

You can use inject for much more than just summing stuff up. Comes in
very handy for these cryptic one-lines (even if you have to 'misuse' it
a bit occasionally).

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

another one:

tab = ["_", ("A".."Z").to_a].flatten
puts tab[n]

···

--- Ursprüngliche Nachricht ---
Von: Ross Bamford <rossrt@roscopeco.co.uk>
An: ruby-talk@ruby-lang.org (ruby-talk ML)
Betreff: Re: Indexing system - ruby newbie
Datum: Thu, 2 Mar 2006 01:37:06 +0900

On Thu, 2006-03-02 at 00:14 +0900, Adam Groves wrote:
> Dear Ross,
>
> it works a treat but I'm having a bit of trouble figuring out what's
> going on.
>
> (n < 1) ? '_' : (1...n).inject("A") { |curr, i| curr.succ }
>
> I get this:
> if n<1
> '_'
> else
>
> But I'm stuck here.
>
> (1...n).inject("A") { |curr, i| curr.succ}
>
> I still can't quite get my head around blocks beyond .each do |x|

Inject is real easy, and very handy. It's just like 'each', except it
also allows the result of the previous iteration to be injected via the
first argument. For the first iteration, you provide the initial result.

For example:

  a = [1,2,3,4,5]

  a.inject(0) { |sum, i| sum + i }
  # => 15

What happens is:

  Block is called with sum = 0, i = 1
    Block returns 1
  Block is called with sum = 1, i = 2
    Block returns 3
  Block is called with sum = 3, i = 3
    Block returns 6
  Block is called with sum = 6, i = 4
    Block returns 10
  Block is called with sum = 10, i = 5
    Block returns 15
  No more elements, so inject returns 15.

In Ruby, inject allows you to omit the initial value, in which case the
first _two_ elements from the enumerable are passed to the first
iteration, with things proceeding as above from there, so I could have
written:

  a.inject { |sum, i| sum + i }

And would have:

  Block is called with sum = 1, i = 2
    Block returns 3
  Block is called with sum = 3, i = 3
    Block returns 6
  .
  .
  etc.

You can use inject for much more than just summing stuff up. Comes in
very handy for these cryptic one-lines (even if you have to 'misuse' it
a bit occasionally).

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

And we can shorten "A"[0] to ?A.

James Edward Gray II

···

On Mar 1, 2006, at 10:42 AM, Peter Ertl wrote:

how about that?

(n < 1) ? "_" : (("A"[0] + n).chr)

how about that?

(n < 1) ? "_" : (("A"[0] + n).chr)

n = 12
> M

n = 27

\

OP wanted:

... 24 25 26 27 28 29 ...
... 'X', 'Y', 'Z', 'AA', 'AB', 'AC' ...

···

On Thu, 2006-03-02 at 01:42 +0900, Peter Ertl wrote:

> --- Ursprüngliche Nachricht ---
> Von: Ross Bamford <rossrt@roscopeco.co.uk>
> An: ruby-talk@ruby-lang.org (ruby-talk ML)
> Betreff: Re: Indexing system - ruby newbie
> Datum: Thu, 2 Mar 2006 01:37:06 +0900
>
> On Thu, 2006-03-02 at 00:14 +0900, Adam Groves wrote:
> > Dear Ross,
> >
> > it works a treat but I'm having a bit of trouble figuring out what's
> > going on.
> >
> > (n < 1) ? '_' : (1...n).inject("A") { |curr, i| curr.succ }
> >
> > I get this:
> > if n<1
> > '_'
> > else
> >
> > But I'm stuck here.
> >
> > (1...n).inject("A") { |curr, i| curr.succ}
> >
> > I still can't quite get my head around blocks beyond .each do |x|
>
> Inject is real easy, and very handy. It's just like 'each', except it
> also allows the result of the previous iteration to be injected via the
> first argument. For the first iteration, you provide the initial result.
>
> For example:
>
> a = [1,2,3,4,5]
>
> a.inject(0) { |sum, i| sum + i }
> # => 15
>
> What happens is:
>
> Block is called with sum = 0, i = 1
> Block returns 1
> Block is called with sum = 1, i = 2
> Block returns 3
> Block is called with sum = 3, i = 3
> Block returns 6
> Block is called with sum = 6, i = 4
> Block returns 10
> Block is called with sum = 10, i = 5
> Block returns 15
> No more elements, so inject returns 15.
>
> In Ruby, inject allows you to omit the initial value, in which case the
> first _two_ elements from the enumerable are passed to the first
> iteration, with things proceeding as above from there, so I could have
> written:
>
> a.inject { |sum, i| sum + i }
>
> And would have:
>
> Block is called with sum = 1, i = 2
> Block returns 3
> Block is called with sum = 3, i = 3
> Block returns 6
> .
> .
> etc.
>
> You can use inject for much more than just summing stuff up. Comes in
> very handy for these cryptic one-lines (even if you have to 'misuse' it
> a bit occasionally).
>
> --
> Ross Bamford - rosco@roscopeco.REMOVE.co.uk
>
>

--
This email has been verified as Virus free
Virus Protection and more available at http://www.plus.net

--
Ross Bamford - rosco@roscopeco.REMOVE.co.uk

OP wanted:

... 24 25 26 27 28 29 ...
... 'X', 'Y', 'Z', 'AA', 'AB', 'AC' ...

Here's one that uses succ to do the dirty work, but doesn't complicate
things with inject:
(note that '@'.succ = 'A')

def letter n
  l='@'
  n.times{l.succ!}
  l.gsub(/@/,'-')
end

-Adam

And now for the over-engineered approach to balance out the golfing <g>:
% cat indexer.rb
class Indexer
   def initialize
     @index_cache = ('A'..'Z').to_a
     @index_cache.unshift('-')
   end

   def alpha_index(i)
     if res = @index_cache[i]
       res
     else
       @index_cache[i] = alpha_index(i - 1).succ
     end
   end
   alias alpha_index
end

if $0 == __FILE__
   idx = Indexer.new
   puts idx.alpha_index(27)
   puts idx.alpha_index(0)
   puts idx[26]
end

% ruby indexer.rb
AA

···

On Mar 1, 2006, at 12:43 PM, Adam Shelly wrote:

OP wanted:

... 24 25 26 27 28 29 ...
... 'X', 'Y', 'Z', 'AA', 'AB', 'AC' ...

Here's one that uses succ to do the dirty work, but doesn't complicate
things with inject:
(note that '@'.succ = 'A')

def letter n
  l='@'
  n.times{l.succ!}
  l.gsub(/@/,'-')
end

-Adam

-
Z

A different approach from others in this thread... no memoization, no
math in the method (let to_s(base) handle it), and a loop that only
runs, at most, as many times as the length of the resulting string.
The idea is, change to base 26, then "uncarry" the ones. Should be
fast.

def letterize(num)
    b26 = num.to_s(26).tr("0-9a-p","@-Y")
    while b26.sub!(/.@/) { |s| (s[0]-1).chr + "Z" }
    end
    b26.sub!(/^@/, "")
    b26 << "-" if b26.empty?
    b26
end

···

On 3/1/06, Logan Capaldo <logancapaldo@gmail.com> wrote:

On Mar 1, 2006, at 12:43 PM, Adam Shelly wrote:

>> OP wanted:
>>
>> ... 24 25 26 27 28 29 ...
>> ... 'X', 'Y', 'Z', 'AA', 'AB', 'AC' ...
>
> Here's one that uses succ to do the dirty work, but doesn't complicate
> things with inject:
> (note that '@'.succ = 'A')
>
> def letter n
> l='@'
> n.times{l.succ!}
> l.gsub(/@/,'-')
> end
>
> -Adam
>

And now for the over-engineered approach to balance out the golfing <g>:
% cat indexer.rb
class Indexer
   def initialize
     @index_cache = ('A'..'Z').to_a
     @index_cache.unshift('-')
   end

   def alpha_index(i)
     if res = @index_cache[i]
       res
     else
       @index_cache[i] = alpha_index(i - 1).succ
     end
   end
   alias alpha_index
end

if $0 == __FILE__
   idx = Indexer.new
   puts idx.alpha_index(27)
   puts idx.alpha_index(0)
   puts idx[26]
end

% ruby indexer.rb
AA
-
Z

Thank you, I was trying hard to work out a way to use to_s(26) and couldn't quit get it to work

···

On Mar 2, 2006, at 3:43 PM, A LeDonne wrote:

On 3/1/06, Logan Capaldo <logancapaldo@gmail.com> wrote:

On Mar 1, 2006, at 12:43 PM, Adam Shelly wrote:

OP wanted:

... 24 25 26 27 28 29 ...
... 'X', 'Y', 'Z', 'AA', 'AB', 'AC' ...

Here's one that uses succ to do the dirty work, but doesn't complicate
things with inject:
(note that '@'.succ = 'A')

def letter n
  l='@'
  n.times{l.succ!}
  l.gsub(/@/,'-')
end

-Adam

And now for the over-engineered approach to balance out the golfing <g>:
% cat indexer.rb
class Indexer
   def initialize
     @index_cache = ('A'..'Z').to_a
     @index_cache.unshift('-')
   end

   def alpha_index(i)
     if res = @index_cache[i]
       res
     else
       @index_cache[i] = alpha_index(i - 1).succ
     end
   end
   alias alpha_index
end

if $0 == __FILE__
   idx = Indexer.new
   puts idx.alpha_index(27)
   puts idx.alpha_index(0)
   puts idx[26]
end

% ruby indexer.rb
AA
-
Z

A different approach from others in this thread... no memoization, no
math in the method (let to_s(base) handle it), and a loop that only
runs, at most, as many times as the length of the resulting string.
The idea is, change to base 26, then "uncarry" the ones. Should be
fast.

def letterize(num)
    b26 = num.to_s(26).tr("0-9a-p","@-Y")
    while b26.sub!(/.@/) { |s| (s[0]-1).chr + "Z" }
    end
    b26.sub!(/^@/, "")
    b26 << "-" if b26.empty?
    b26
end