sawa
sawa

Reputation: 168239

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

Upvotes: 1

Views: 3368

Answers (5)

Cary Swoveland
Cary Swoveland

Reputation: 110745

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)

Upvotes: 2

Pend
Pend

Reputation: 752

I would write it like this:

def transpose_text(text)
  # split the text into lines
  text = text.split("\n")

  # find the length of the longest line
  max_line_length = text.map(&:size).max

  # pad each line with white space and convert them to character arrays
  text.map! { |line| line.ljust(max_line_length).chars }

  #transpose the character arrays and then join them all into one string
  text.transpose.map(&:join).join("\n")
end

Upvotes: 0

vol7ron
vol7ron

Reputation: 42149

A play on what Chron did for an earlier version of ruby (e.g., 1.8.x). Example based on your original input that showed newline characters

str="abc\\n
def ghi\\n
jk lm no\\n"


def transpose s
  lines = s.gsub("\\n","").split("\n")
  longest = lines.map { |line| line.length }.max

  (0..longest).map do |char_index|
    lines.map { |line| line.split('')[char_index] || ' ' }.join
  end * "\\n\n"
end

puts transpose(str)

Upvotes: 0

Paul Prestidge
Paul Prestidge

Reputation: 1127

I would write it like this:

def transpose s
  lines = s.split(?\n)
  longest = lines.map { |l| l.length }.max

  (0..longest).map do |index|
    lines.map { |l| l[index] || ' ' }.join
  end * ?\n
end

Upvotes: 1

Fabricator
Fabricator

Reputation: 12782

This one works

s = "abc\ndef ghi\njk lm no\n"

s = s.split("\n")
s2 = ''
i = 0
while true
  line = ''
  s.each do |row|
    line += (row[i] or ' ')
  end

  if line.strip == ''
    break
  end

  s2 += line + "\n"
  i += 1
end

puts s2

This one also works

s = "abc\ndef ghi\njk lm no\n"

s = s.split("\n")
maxlen = s.inject(0) {|m,r| m=[m, r.length].max}
s.map! {|r| r.ljust(maxlen).split(//)}
s = s.transpose.map {|r| r.join('')}.join("\n")

puts s

Upvotes: 0

Related Questions