Halving a string

I have a vacuum fluorescent display in my office, and I’ve been
messing around with it. Now that I’ve figured out how to communicate
with the serial connection it’s time for some fun. Its shortcoming is
its 2 lines of 20 chars. So I naturally wrote a fortune cookie program
for it.

I want to make adding new fortunes easy (and without having to figure
out myself where to force a line break), so I basically need to split
a string (approximately) in half at a word boundary. This is what I
have:

class String
def halve
first_half = ‘’
second_half = self
until first_half.length >= length / 2
match = / /.match(second_half)
first_half << match.pre_match << ’ ’
second_half = match.post_match
end
[first_half.strip, second_half]
end
end

I have a feeling there’s a one-line regexp that can do this. Am I
right? If not, is there a better way?

On Feb 1, 9:20 pm, “Trans” [email protected] wrote:

I want to make adding new fortunes easy (and without having to figure
first_half << match.pre_match << ’ ’

i = [0…str.size / 2].index(’ ')
first_half, second_half = str[0…i], str[i…-1].strip

however, you might just prefer

require ‘facets/core/string/word_wrap’
str.word_wrap(20)

T.

Tested, and failed. The index returns the position of the very first
space. (Also, it would have to be str[0…(str.size / 2)].index(’ ')
but, still, it’s no good).

Word wrapping is wrong for the occasion, as I’d like to split even
lines less than 20 chars, and have them of approximately equal length.

On Feb 1, 11:10 pm, “Chris S.” [email protected] wrote:

end
end

I have a feeling there’s a one-line regexp that can do this. Am I
right? If not, is there a better way?

untested but…

i = [0…str.size / 2].index(’ ')
first_half, second_half = str[0…i], str[i…-1].strip

however, you might just prefer

require ‘facets/core/string/word_wrap’
str.word_wrap(20)

T.

From: “Chris S.” [email protected]

Tested, and failed. The index returns the position of the very first
space. (Also, it would have to be str[0…(str.size / 2)].index(’ ')
but, still, it’s no good).

rindex()

:slight_smile:

Regards,

Bill

On Feb 1, 10:24 pm, “Chris S.” [email protected] wrote:

:slight_smile:
if i.nil?
if include?(’ ‘)
split(’ ', 1)
else
[self, ‘’]
end
else
[self[0…i].strip, self[i…-1].strip]
end
end
end

ahem: split(’ ', 2)

On Feb 1, 9:57 pm, “Bill K.” [email protected] wrote:

:slight_smile:

Regards,

Bill

rindex, of course. Here’s my current version then:

class String
def halve
i = self[0…(length / 2)].rindex(’ ‘)
if i.nil?
if include?(’ ‘)
split(’ ', 1)
else
[self, ‘’]
end
else
[self[0…i].strip, self[i…-1].strip]
end
end
end

On Feb 1, 11:03 pm, Devin M. [email protected] wrote:

 else
   assert_equal ['the frog', 'is green'], 'the frog is green'.halve
   assert_equal ['ponchielli', 'wrote songs'],
                'ponchielli wrote songs'.halve
   assert_raise(RuntimeError) { 'ab d'.halve 1 }
   assert_nothing_raised { 'a b'.halve 1 }
   assert_equal ['lizardman', 'lives'], 'lizardman lives'.halve
   assert_equal ['lizardm', 'an lives'], 'lizardman lives'.halve(8)
   assert_equal ['abcdef','ghijkl'], 'abcdefghijkl'.halve
 end

end
end

Yes. I see how expressions like space_indices.sort_by {|i| (i - size/
2).abs }.first work, I just need to be able to think of them. It does
what I really want (get the closest space to the middle of the
string), instead of what I almost want (the last space in the first
half of the string). Maybe I just gave up too early, or started
barking up the wrong tree. I said, “I know, I’ll use regular
expressions” and then I had two problems. Thanks.

Chris S. wrote:

I want to make adding new fortunes easy (and without having to figure
out myself where to force a line break), so I basically need to split
a string (approximately) in half at a word boundary. This is what I
have:
[snip[
I have a feeling there’s a one-line regexp that can do this. Am I
right? If not, is there a better way?

This is what I would do:

strings = DATA.read.split( /\n/ )

strings.each{ |str|
puts str.gsub( /^(.{#{str.length/2},}?)\s(.+)/ ){ “#{$1}\n#{$2}” }
puts
}
#=> Hello
#=> World

#=> It’s the end of the
#=> world as we know it

#=> If you didn’t know any better
#=> you’d think this was magic.

END
Hello World
It’s the end of the world as we know it
If you didn’t know any better you’d think this was magic.

Naive solution:

class String
def halve(max_len=nil)
raise if max_len and size > max_len * 2 + 1
space_indices = (0…size).find_all {|i| self[i] == ’ '[0] }
splitter = space_indices.sort_by {|i| (i - size/2).abs }.first
if splitter.nil? ||
(max_len && (splitter > max_len || size - splitter - 1 >
max_len))
[self[0…size/2], self[size/2…-1]]
else
[self[0…splitter], self[splitter+1…-1]]
end
end
end

if FILE == $0
require ‘test/unit’

class SplitterTest < Test::Unit::TestCase
def test_stuff
assert_equal [‘the frog’, ‘is green’], ‘the frog is green’.halve
assert_equal [‘ponchielli’, ‘wrote songs’],
‘ponchielli wrote songs’.halve
assert_raise(RuntimeError) { ‘ab d’.halve 1 }
assert_nothing_raised { ‘a b’.halve 1 }
assert_equal [‘lizardman’, ‘lives’], ‘lizardman lives’.halve
assert_equal [‘lizardm’, ‘an lives’], ‘lizardman lives’.halve(8)
assert_equal [‘abcdef’,‘ghijkl’], ‘abcdefghijkl’.halve
end
end
end

On 02.02.2007 05:26, Chris S. wrote:

Word wrapping is wrong for the occasion, as I’d like to split even
lines less than 20 chars, and have them of approximately equal length.

If you want to first fill the first string and then the second you can
do this:

first, second = s.scan /.{1,20}/

For evenly distribution you could do:

irb(main):019:0> s=“foo bar ajd as dashd kah sdhakjshd ahdk ahsd asjh”
=> “foo bar ajd as dashd kah sdhakjshd ahdk ahsd asjh”
irb(main):020:0> l=[40,s.length].min / 2
=> 20
irb(main):021:0> first = s[0…l]
=> “foo bar ajd as dashd”
irb(main):022:0> second = s[l…l+l]
=> " kah sdhakjshd ahdk "

If you want to break at white space, you can do it with one regexp (you
did ask for the one regexp solution :-)):

irb(main):030:0> s=“aasd laksjd asdj asjkd asdj jlas d”
=> “aasd laksjd asdj asjkd asdj jlas d”
irb(main):031:0> %r[(.{1,#{s.length/2}})\s*(.{1,#{s.length/2}})] =~ s or
raise “cannot split”
=> 0
irb(main):032:0> first = $1
=> “aasd laksjd asdj”
irb(main):033:0> second = $2
=> “asjkd asdj jlas d”

Kind regards

robert

On Feb 1, 11:30 pm, “Chris S.” [email protected] wrote:

for it.
until first_half.length >= length / 2

T.

Tested, and failed. The index returns the position of the very first
space. (Also, it would have to be str[0…(str.size / 2)].index(’ ')
but, still, it’s no good).

ah, size/2 has to be added to i, then it works.

Word wrapping is wrong for the occasion, as I’d like to split even
lines less than 20 chars, and have them of approximately equal length.

not sure i understand, the size can be set, for example:

word_wrap(str.size/2)

t.

puts str.gsub( /^(.{#{str.length/2},}?)\s(.+)/ ){ “#{$1}\n#{$2}” }
irb(main):031:0> %r[(.{1,#{s.length/2}})\s*(.{1,#{s.length/2}})] =~ s or
raise “cannot split”

Kind regards

   robert

you guys are hard core when it comes to regexes. damn.

On Feb 1, 11:30 pm, “Chris S.” [email protected] wrote:

T.

Tested, and failed. The index returns the position of the very first
space. (Also, it would have to be str[0…(str.size / 2)].index(’ ')
but, still, it’s no good).

To clarify…

class String
def halve
i = size / 2
j = i + self[i…-1].index(’ ')
return self[0…j].strip, self[j…-1].strip
end
end

T.

On Feb 2, 6:53 am, “Trans” [email protected] wrote:

with the serial connection it’s time for some fun. Its shortcoming is
first_half = ‘’
I have a feeling there’s a one-line regexp that can do this. Am I
str.word_wrap(20)
lines less than 20 chars, and have them of approximately equal length.

not sure i understand, the size can be set, for example:

word_wrap(str.size/2)

t.

I thought of that, but:

irb> str = “longlongword bit bits”
irb> puts str.word_wrap(str.size/2)
longlongwo
rd bit
bits
=> nil

On Feb 1, 11:25 pm, “Phrogz” [email protected] wrote:

strings.each{ |str|
puts str.gsub( /^(.{#{str.length/2},}?)\s(.+)/ ){ “#{$1}\n#{$2}” }
puts
}

Seeing Robert K.'s regexp, it does make sense to be a hair
greedier on the whitespace match, just in case the source string has
more than one space between words at the boundary:

str.gsub( /^(.{#{str.length/2},}?)\s+(.+)/ ){ “#{$1}\n#{$2}” }

On Feb 1, 2007, at 11:10 PM, Chris S. wrote:

end
end

I have a feeling there’s a one-line regexp that can do this. Am I
right? If not, is there a better way?

Good solutions already, but I had to chime in with one less clever
and without regexps.
I also didn’t like “halve” as a name so I used “cleave”.

Enjoy!

class String
def cleave
middle = self.length/2
early = self.rindex(’ ‘, middle)
late = self.index(’ ', middle)

 if self[middle,1] == ' '
   [ self[0...middle], self[middle+1..-1] ]
 elsif early.nil? && late.nil?
   [ self.dup, '' ]
 elsif early.nil?
   [ self[0...late], self[late+1..-1] ]
 elsif late.nil?
   [ self[0...early], self[early+1..-1] ]
 else
   middle = middle - early < late - middle ? early : late
   [ self[0...middle], self[middle+1..-1] ]
 end

end
end

if FILE == $0
require ‘test/unit’
class StringCleaveTest < Test::Unit::TestCase
def test_nospaces
assert_equal [ ‘whole’,
‘’ ], ‘whole’.cleave
assert_equal [ ‘Supercalifragilisticexpialidocious’,
‘’ ], ‘Supercalifragilisticexpialidocious’.cleave
end
def test_exact_middle
assert_equal [ ‘fancy’,
‘split’ ], ‘fancy split’.cleave
assert_equal [ ‘All good Rubyists’,
‘know how to party’ ], ‘All good Rubyists know
how to party’.cleave
end
def test_closer_to_start
assert_equal [ ‘short’,
‘splitter’ ], ‘short splitter’.cleave
assert_equal [ ‘Four score and’,
‘seven years ago…’ ], ‘Four score and seven
years ago…’.cleave
assert_equal [ ‘abc def’,
‘ghijklm nop’ ] , ‘abc def ghijklm nop’.cleave
end
def test_closer_to_end
assert_equal [ ‘extended’,
‘split’ ], ‘extended split’.cleave
assert_equal [ ‘abc defghi’,
‘jklm nop’ ] , ‘abc defghi jklm nop’.cleave
end
end
end

Rob B. http://agileconsultingllc.com
[email protected]

On 02.02.2007 17:28, Phrogz wrote:

str.gsub( /^(.{#{str.length/2},}?)\s+(.+)/ ){ “#{$1}\n#{$2}” }
You’re making length/2 the minimum for matching. I believe that should
rather be the max:

untested

str.sub( /\A(.{1,#{str.length/2}})\s+(.+)/ ){ “#{$1}\n#{$2}” }

Also, #sub seems sufficient.

Kind regards

robert

On Feb 2, 10:55 am, Rob B. [email protected]
wrote:

On Feb 1, 2007, at 11:10 PM, Chris S. wrote:

 late = self.index(' ', middle)

[snip]

This is nice and versitle. One good augmentation might be…

def cleave(middle=nil)
  middle ||= self.length/2

T.

On Feb 2, 9:44 am, Robert K. [email protected] wrote:

On 02.02.2007 17:28, Phrogz wrote:

str.gsub( /^(.{#{str.length/2},}?)\s+(.+)/ ){ “#{$1}\n#{$2}” }

You’re making length/2 the minimum for matching. I believe that should
rather be the max:

untested

str.sub( /\A(.{1,#{str.length/2}})\s+(.+)/ ){ “#{$1}\n#{$2}” }

I suppose it depends on whether you want the first line to be longer
or shorter than the 2nd line. In my mind, it looks better longer. The
{x,} range does make it the minimum, but the non-greedy quantifier
ensures that it breaks as soon as possible after starting the word
that you’re in the middle of.

Also, #sub seems sufficient.

Good point. I don’t think I have ever used #sub, so it’s never at the
forefront of my mind.

On 2/2/07, Chris S. [email protected] wrote:
[snip]

I have a feeling there’s a one-line regexp that can do this. Am I
right? If not, is there a better way?

no big regexp here

def half(s, threshold)
l = s.size / 2
0.upto([threshold, l].min) do |i|
(l -= i; break) if s[l-i, 1] =~ /\s/
(l += i; break) if s[l+i, 1] =~ /\s/
end
s.dup.insert(l, “\n”).gsub(/^\s+|\s+$/, ‘’)
end

ary = [
“abcd efgh ijkl”,
“abcdef”,
“hello world”,
“aasd laksjd asdj asjkd asdj jlas d”,
“foo bar ajd as dashd kah sdhakjshd ahdk ahsd asjh”,
“It’s the end of the world as we know it”,
“If you didn’t know any better you’d think this was magic.”,
“lizardman lives”,
“ponchielli wrote songs”,
“NSIntersectionRect YES NO”
]

ary.each{|s| p half(s, 6) }
p ‘---------------------’
ary.each{|s| p half(s, 2) }

output below:

“abcd efgh\nijkl”
“abc\ndef”
“hello\nworld”
“aasd laksjd asdj\nasjkd asdj jlas d”
“foo bar ajd as dashd kah\nsdhakjshd ahdk ahsd asjh”
“It’s the end of the\nworld as we know it”
“If you didn’t know any better\nyou’d think this was magic.”
“lizardman\nlives”
“ponchielli\nwrote songs”
“NSIntersectionRect\nYES NO”
“---------------------”
“abcd efgh\nijkl”
“abc\ndef”
“hello\nworld”
“aasd laksjd asdj\nasjkd asdj jlas d”
“foo bar ajd as dashd kah\nsdhakjshd ahdk ahsd asjh”
“It’s the end of the\nworld as we know it”
“If you didn’t know any better\nyou’d think this was magic.”
“lizardman\nlives”
“ponchielli\nwrote songs”
“NSIntersecti\nonRect YES NO”