1

I need to unpack the string of packed ASCII characters. The algorithm is following:

  1. Get 3 packed ASCII bytes, put them into 4 ASCII bytes. (6 + 6 + 6 + 6 bits in 3 bytes to 4 bytes)
  2. For each byte, set bit 6 as complement to bit 5.
  3. For each byte reset bit 7 to zero.
  4. Repeat for next 3 packed bytes.

I'm new to Ruby, may be there is more correct and elegant way to solve this task, rather my code:

while i < pstr.length 
    parr = [pstr[0] & 0x3F, pstr[0]>>6 | ((pstr[1] << 2 ) & 0x3F),
            pstr[1]>>4 | ((pstr[2] << 4 ) & 0x3F),
            pstr[2]>>2]
    parr.collect! { |a| a | (~(a << 1) & 0x20) }        
    parr.collect! { |a| a & 0x7F }

    puts parr

    i += 3
end

Update1: Thx for correction of collect.

5
  • 2
    Just to clarify: Your code works as intended (e.g. passes tests), but you would like help with Ruby idioms or short-cuts? Could you add some test inputs and verified outputs to the question, it would help. Commented May 1, 2014 at 13:31
  • Have you looked at #unpack? ruby-doc.org/core-2.1.1/String.html#method-i-unpack Commented May 1, 2014 at 13:42
  • @UriAgassi As i read in docs, Ruby doesn't support packed ASCII code in pack/unpack. Commented May 1, 2014 at 13:44
  • @NeilSlater Code works on samples that i have, but ofc have bugs, just want to clarify that I understand Ruby construction correctly and there is no way to solve this problem in more compact way using Ruby features. Thanks for correction of collect. Commented May 1, 2014 at 13:46
  • 2
    Could you provide a couple of the test samples please? Commented May 1, 2014 at 14:41

1 Answer 1

1

Though ruby's Array#pack and String#unpack don't directly support ASCII pack and unpack, they do support Base64 by way of pack('m') and unpack('m'). This can help with the bit-shifting involved with translating to and from 3 bytes that hold 8 relevant bits each, and 4 bytes that hold 6 each.

Here's a starting implementation of pack and unpack which is a bit more ruby-esque. pack operates on strings which are an exact multiple of 4, dropping any remainder. Conversely unpack expands every 3 characters to 4.

B64 = ('A'..'Z').to_a+('a'..'z').to_a+('0'..'9').to_a+%w(+ /) # Base64 alphabet
H64 = Hash[B64.zip(0..63)] # Hash character to index

# Translates every 4 characters to 3, drops any remainder
def pack( ascii )
    ascii.bytes.map { |b| B64[b&(b&0x40==0?0x3f:0x1f)] }.join.unpack('m')[0]
end

# Translates every 3 characters to 4
def unpack( bstr )
    [bstr].pack('m').chomp.split('').map do |c|
       ( (H64[c]|0x40) & (H64[c]&0x20==0?0x5f:0x3f) ).chr
    end.join
end

Example usage:

ascii_packed = pack('Hello World!')
puts ascii_packed.length # => 9
puts unpack( ascii_packed ) # => "HELLO WORLD!"

For those unfamiliar with ASCII-pack, it is a "lossy" compression. ASCII characters x outside the range 0x20 <= x < 0x60 are translated to a character within that range. This is why lower case letters get capitalized when packed/unpacked.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.