Forum: Ruby a better way for what is currently nested substitution

161289732b5b8c9dcb5834a3f0535340?d=identicon&s=25 Chad Perrin (Guest)
on 2013-09-07 22:50
(Received via mailing list)
There must be a better way to do this:

    numstr = '17      75      -1      25      -1      52  43  37  0'
    => "17      75      -1      25      -1      52  43  37  0"
    numstr.sub(/(.{46})/) {|a| a.sub(/(..$)/) {|b| b.to_i + 3 }.to_s }
    => "17      75      -1      25      -1      52  46  37  0"

What is the better way?  (Note that in practice I'm using this to
operate on several strings in series, where each string has a number in
the same columnar position that needs to be incremented by three.)
14b5582046b4e7b24ab69b7886a35868?d=identicon&s=25 Joel Pearson (virtuoso)
on 2013-09-07 23:40
Within that particular example, with an exact position:
numstr[44..45] = (numstr[44..45].to_i + 3).to_s
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-09-09 10:35
(Received via mailing list)
On Sat, Sep 7, 2013 at 11:40 PM, Joel Pearson <lists@ruby-forum.com>
wrote:
> Within that particular example, with an exact position:
> numstr[44..45] = (numstr[44..45].to_i + 3).to_s

Normally I'd recommend using a proper representation, i.e. an Array of
Fixnums:

irb(main):004:0> numstr = '17      75      -1      25      -1      52
43  37  0'
=> "17      75      -1      25      -1      52  43  37  0"
irb(main):005:0> nums = numstr.scan(/[-+]?\d+/).map(&:to_i)
=> [17, 75, -1, 25, -1, 52, 43, 37, 0]
irb(main):006:0> nums[6] += 3
=> 46
irb(main):007:0> nums
=> [17, 75, -1, 25, -1, 52, 46, 37, 0]
irb(main):008:0> numstr = nums.join '      '
=> "17      75      -1      25      -1      52      46      37      0"

For the fun of it a columnar replacement could be done like this:

irb(main):009:0> numstr = '17      75      -1      25      -1      52
43  37  0'
=> "17      75      -1      25      -1      52  43  37  0"
irb(main):010:0> pos = 0
=> 0
irb(main):011:0> numstr.gsub!(/[-+]?\d+/) {|m| (pos += 1) == 7 ? m.to_i
+ 3 : m}
=> "17      75      -1      25      -1      52  46  37  0"

Kind regards

robert
161289732b5b8c9dcb5834a3f0535340?d=identicon&s=25 Chad Perrin (Guest)
on 2013-09-09 17:56
(Received via mailing list)
On Sat, Sep 07, 2013 at 11:40:28PM +0200, Joel Pearson wrote:
>
> Within that particular example, with an exact position:
> numstr[44..45] = (numstr[44..45].to_i + 3).to_s

Hmm.

My actual use case, as it happens, involves shelling out to Ruby from
Vim, and I do not at first glance see how this would fit into that
approach.  Here's my original approach, as a Vim command:

    :.!ruby -pe 'sub(/(.{46})/) {|a| a.sub(/(..$)/) {|b| b.to_i + 3
}.to_s }'

I'll have to come back to this after more caffeine and see if your
approach can be made to fit without becoming uglier than what I ended up
doing.
161289732b5b8c9dcb5834a3f0535340?d=identicon&s=25 Chad Perrin (Guest)
on 2013-09-09 18:00
(Received via mailing list)
On Mon, Sep 09, 2013 at 10:34:51AM +0200, Robert Klemme wrote:
> => 46
> irb(main):007:0> nums
> => [17, 75, -1, 25, -1, 52, 46, 37, 0]
> irb(main):008:0> numstr = nums.join '      '
> => "17      75      -1      25      -1      52      46      37      0"

Unfortunately, this mangles the spacing between columns, which I do
actually need to preserve.


>
> For the fun of it a columnar replacement could be done like this:
>
> irb(main):009:0> numstr = '17      75      -1      25      -1      52
> 43  37  0'
> => "17      75      -1      25      -1      52  43  37  0"
> irb(main):010:0> pos = 0
> => 0
> irb(main):011:0> numstr.gsub!(/[-+]?\d+/) {|m| (pos += 1) == 7 ? m.to_i + 3 : m}
> => "17      75      -1      25      -1      52  46  37  0"

Given the contents of the strings in question, the regex could be
simplified to /-?\d+/ because positives are never indicated by plus
signs.  This is a pretty good solution to the problem as I stated it,
but I unfortunately forgot to mention that sometimes some columns might
be empty -- so you get a blue ribbon for solving the problem I
described, even if it doesn't solve the problem I actually had to solve.

Thanks for the ideas for how to write a better substitution routine than
I had.  These may not actually work in this specific case, but it's
always instructive to see what approaches to solving problems come from
different perspectives.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-09-09 19:01
(Received via mailing list)
On Mon, Sep 9, 2013 at 5:59 PM, Chad Perrin <code@apotheon.net> wrote:
>> irb(main):006:0> nums[6] += 3
>> => 46
>> irb(main):007:0> nums
>> => [17, 75, -1, 25, -1, 52, 46, 37, 0]
>> irb(main):008:0> numstr = nums.join '      '
>> => "17      75      -1      25      -1      52      46      37      0"
>
> Unfortunately, this mangles the spacing between columns, which I do
> actually need to preserve.

Yes, I figured that was a downside of the approach. Hence I tried the
text based solution. :-)

> Given the contents of the strings in question, the regex could be
> simplified to /-?\d+/ because positives are never indicated by plus
> signs.

Plus signs are rarely use, but since you did not mention this I wanted
to stay on the safe side. :-)

>  This is a pretty good solution to the problem as I stated it,
> but I unfortunately forgot to mention that sometimes some columns might
> be empty -- so you get a blue ribbon for solving the problem I
> described, even if it doesn't solve the problem I actually had to solve.

LOL

If arbitrary columns can be empty (i.e. empty strings), if whitespace
between columns does not have a fixed length and if integer values can
be arbitrary then there is no general solution to your problem because
the system cannot know which column you intend to change. I think you
need to impose at least one of these restrictions to be able to come
up with an automated solution.

> Thanks for the ideas for how to write a better substitution routine than
> I had.  These may not actually work in this specific case, but it's
> always instructive to see what approaches to solving problems come from
> different perspectives.

That's absolutely true!

Kind regards

robert
161289732b5b8c9dcb5834a3f0535340?d=identicon&s=25 Chad Perrin (Guest)
on 2013-09-10 00:52
(Received via mailing list)
On Mon, Sep 09, 2013 at 07:01:01PM +0200, Robert Klemme wrote:
> On Mon, Sep 9, 2013 at 5:59 PM, Chad Perrin <code@apotheon.net> wrote:
> >
> >  This is a pretty good solution to the problem as I stated it,
> > but I unfortunately forgot to mention that sometimes some columns might
> > be empty -- so you get a blue ribbon for solving the problem I
> > described, even if it doesn't solve the problem I actually had to solve.
>
> LOL

Yeah, that's pretty much the correct response to what I said.


>
> If arbitrary columns can be empty (i.e. empty strings), if whitespace
> between columns does not have a fixed length and if integer values can
> be arbitrary then there is no general solution to your problem because
> the system cannot know which column you intend to change. I think you
> need to impose at least one of these restrictions to be able to come
> up with an automated solution.

The columns are of fixed, but not uniform, width -- based on multiples
of four character width tab-stops, essentially, but using spaces instead
of tabs.  Thus, the problem is basically confined to solutions that
operate on specific character columns (that is, the Nth character from
the left-hand margin).
161289732b5b8c9dcb5834a3f0535340?d=identicon&s=25 Chad Perrin (Guest)
on 2013-09-10 01:04
(Received via mailing list)
On Mon, Sep 09, 2013 at 09:52:52AM -0600, Chad Perrin wrote:
>
>     :.!ruby -pe 'sub(/(.{46})/) {|a| a.sub(/(..$)/) {|b| b.to_i + 3 }.to_s }'
>
> I'll have to come back to this after more caffeine and see if your
> approach can be made to fit without becoming uglier than what I ended up
> doing.

. . . and after thinking about this and experimenting a little, I am
momentarily stymied by the fact that the way data is passed around
between Vim, the shell, ruby, the shell, and Vim again is a bit like
black magic to me just now.  I'll have to get back to this later.
9a45896e48a382fe5c656b8873e0dfcb?d=identicon&s=25 Stu (Guest)
on 2013-09-10 08:37
(Received via mailing list)
On Mon, Sep 9, 2013 at 6:03 PM, Chad Perrin <code@apotheon.net> wrote:

> > approach.  Here's my original approach, as a Vim command:
> between Vim, the shell, ruby, the shell, and Vim again is a bit like
> black magic to me just now.  I'll have to get back to this later.
>
> --
> Chad Perrin [ original content licensed OWL: http://owl.apotheon.org ]
>


Alfred Aho
Peter Weinberger
Brian Kernighan
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-09-10 10:24
(Received via mailing list)
On Tue, Sep 10, 2013 at 12:51 AM, Chad Perrin <code@apotheon.net> wrote:
> Yeah, that's pretty much the correct response to what I said.
:-)

> operate on specific character columns (that is, the Nth character from
> the left-hand margin).

If I understand you correctly then it is not possible to derive from a
single line how much spacing between two columns exists. Then I guess
there is no solution which can be applied by handing off a single line
from vim to ruby -pe.  If you need that functionality more often, I
guess this warrants a full fledged Ruby script with proper inputs
which operates on the whole file.

Kind regards

robert
14b5582046b4e7b24ab69b7886a35868?d=identicon&s=25 Joel Pearson (virtuoso)
on 2013-09-10 12:12
How about
numstr.split( /\b/ )

=> ["17", "      ", "75", "      -", "1", "      ", "25", "      -",
"1", " ", "52", "  ", "43", "  ", "37", "  ", "0"]

Ok, at the moment it doesn't handle minuses correctly; but with a bit of
work to iron that out, and with some rules on handling "empty" columns
(are they padded with spaces or 0 length?), that might be a place to
start.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (robert_k78)
on 2013-09-10 14:10
(Received via mailing list)
On Tue, Sep 10, 2013 at 12:12 PM, Joel Pearson <lists@ruby-forum.com>
wrote:
> start.
That still does not work since it is based solely on a single line.
With columns optionally empty you cannot derive from a single line the
width of the whitespace separators. This is nothing which can be
solved by any amount of coding magic done with a single line. You
either need more lines or additional information which is fed into the
process. But information contained in a single line does not allow to
identify column positions properly.

Cheers

robert
161289732b5b8c9dcb5834a3f0535340?d=identicon&s=25 Chad Perrin (Guest)
on 2013-09-11 04:46
(Received via mailing list)
On Tue, Sep 10, 2013 at 10:23:37AM +0200, Robert Klemme wrote:
> >
> >
> > The columns are of fixed, but not uniform, width -- based on multiples
> > of four character width tab-stops, essentially, but using spaces instead
> > of tabs.  Thus, the problem is basically confined to solutions that
> > operate on specific character columns (that is, the Nth character from
> > the left-hand margin).
>
> If I understand you correctly then it is not possible to derive from a
> single line how much spacing between two columns exists. Then I guess
> there is no solution which can be applied by handing off a single line
> from vim to ruby -pe.

. . . except the solution I used, then shared here hoping there was
something more elegant, I suppose.  It's not a general solution, but it
worked for the specific case in which I used it.  I was just dismayed by
the uglitude of nesting a block-fed String#sub inside the block of
another String#sub.

> If you need that functionality more often, I guess this warrants a
> full fledged Ruby script with proper inputs which operates on the
> whole file.

I guess so.  C'est la vie.
161289732b5b8c9dcb5834a3f0535340?d=identicon&s=25 Chad Perrin (Guest)
on 2013-09-11 04:47
(Received via mailing list)
>
> Alfred Aho
> Peter Weinberger
> Brian Kernighan

Err . . . okay.
161289732b5b8c9dcb5834a3f0535340?d=identicon&s=25 Chad Perrin (Guest)
on 2013-09-11 04:50
(Received via mailing list)
On Tue, Sep 10, 2013 at 02:09:53PM +0200, Robert Klemme wrote:
> > (are they padded with spaces or 0 length?), that might be a place to
> > start.
>
> That still does not work since it is based solely on a single line.
> With columns optionally empty you cannot derive from a single line the
> width of the whitespace separators. This is nothing which can be
> solved by any amount of coding magic done with a single line. You
> either need more lines or additional information which is fed into the
> process. But information contained in a single line does not allow to
> identify column positions properly.

To be precise, no *general* solution seems possible given the
restrictions imposed, though using split on /\b/ would have sufficed for
the one-off usage I needed at the time.  I'd really like to have found
out there was a general solution, though, using some trick I had
overlooked.

I'd also like to have found out there was a way to pass multiple
parameters to the block for String#sub that would allow me to operate on
a pair of match captures, but the way String#sub works it only allows
one capture to be passed to the block as a parameter.  I'm not really
sure why it was designed that way, unless there was some performance
constraint for the implementation.
2f4d4f9c35ea851bffb9a9cc2e086365?d=identicon&s=25 Harry Kakueki (Guest)
on 2013-09-11 08:07
(Received via mailing list)
>
>


If you never have a 3 digit number and the spacing never changes,


m = "17      75      -1      25      -1      52  43  37  0"
n = "        75                              52  43  37  0"

p m
p m.scan(/.{1,2}/).tap{|a|
a[22],a[24]=(a[22].to_i+3).to_s,(a[24].to_i+4).to_s}.join
puts
p n
p n.scan(/.{1,2}/).tap{|a|
a[22],a[24]=(a[22].to_i+3).to_s,(a[24].to_i+4).to_s}.join


This may not solve your problem but maybe it will give you some other
ideas.


Harry
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.