sawa sawa - 3 years ago 148
Ruby Question

Transposing a string

Given a (multiline) string, where each line is separated by

"\n"
and may not be necessarily of the same length, what is the best way to transpose it into another string as follows? Lines shorter than the longest one should be padded with space (right padding in terms of the original, or bottom padding in terms of the output). Applying the operation on a string twice should be idempotent modulo padding.

Input string

abc
def ghi
jk lm no


Output string

adj
bek
cf
l
gm
h
in
o

Answer Source

Here are five approaches. (Yes, I got a bit carried away, but I find that trying to think of different ways to accomplish the same task is good exercise for the grey cells.)

#1

An uninteresting, brute-force method:

a = str.split("\n")
l = a.max_by(&:size).size
puts a.map { |b| b.ljust(l).chars }
      .transpose
      .map { |c| c.join.rstrip }.join("\n")
adj
bek
cf
  l
 gm
 h
 in
  o

#2

This method and all that follow avoid the use of ljust and transpose, and make use of the fact that if e is an empty array, e.shift returns nil and leaves e an empty array. (Aside: I am often reaching for the non-existent method String#shift. Here it would have avoided the need to convert each line to an array of characters.)

a = str.split("\n").map(&:chars)
a.max_by(&:size).size.times.map { a.map { |e| e.shift || ' ' }.join.rstrip }

#3

This and the remaining methods avoid the need to compute the length of the longest string:

a = str.split("\n").map(&:chars)
a_empty = Array(a.size, [])
[].tap { |b| b << a.map { |e| e.shift || ' ' }.join.rstrip while a != a_empty }

#4

This method makes use of Enumerator#lazy, which has been available since v2.0.

a = str.split("\n").map(&:chars)
(0..Float::INFINITY).lazy.map do |i|
  a.each { |e| e.shift } if i > 0 
  a.map  { |e| e.first || ' ' }.join.rstrip
end.take_while { c = a.any? { |e| !e.empty? };  }.to_a

(I initially had a problem getting this to work, as I was not getting the element of the output (" o"). The fix was adding the third line and changing the line that follows from a.map { |e| e.shift || ' ' }.join.rstrip to what I have now. I mention this because it seems like it may be common problem when using lazy.)

#5

Lastly, use recursion:

def recurse(a, b=[])
  return b[0..-2] if a.last.empty?
  b << a.map { |e| e.shift || ' ' }.join.rstrip
  recurse(a, b)
end

a = str.split("\n").map(&:chars)
recurse(a)
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download