halving a string

C

Chris Shea

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?
 
T

Trans

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?

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.
 
C

Chris Shea

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?

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.

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.
 
B

Bill Kelly

From: "Chris Shea said:
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()

:)


Regards,

Bill
 
C

Chris Shea

From: "Chris Shea" <[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()

:)

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
 
C

Chris Shea

From: "Chris Shea" <[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).

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

ahem: split(' ', 2)
 
D

Devin Mullins

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 == ' '[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
 
C

Chris Shea

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 == ' '[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


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.
 
P

Phrogz

Chris said:
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.
 
R

Robert Klemme

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
 
T

Trans

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?
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)

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.
 
T

Trans

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)

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.
 
R

Rob Biedenharn

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?

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 Biedenharn http://agileconsultingllc.com
(e-mail address removed)
 
C

Chris Shea

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?
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.
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.

I thought of that, but:

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

Phrogz

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

Seeing Robert Klemme'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}" }
 
R

Robert Klemme

Seeing Robert Klemme'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}" }

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
 
P

Phrogz

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.
 
T

Trans

On Feb 1, 2007, at 11:10 PM, Chris Shea wrote:
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)

[snip]

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

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

T.
 
T

Tiago Pinto

Hi Chris,
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.

aside the string hackery, will the comunication be done using ruby? ;P

On Feb 1, 2007, at 11:10 PM, Chris Shea wrote:
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)

[snip]

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

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

T.
 
C

Chris Shea

Hi Chris,


aside the string hackery, will the comunication be done using ruby? ;P
[snip]

Mine will be. The code for handling the communication is ugly (the
win32api pretty much guarantees that), but I've wrapped it in a module
so I never have to look at it again (hopefully). I'm using the display
as stdout for a couple of little maintenance scripts, too.

Otherwise, there's been some great solutions here. Thanks everyone.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,230
Messages
2,571,161
Members
47,794
Latest member
LucretiaEl

Latest Threads

Top