Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks, I am a Perl newbie and I would like to kindly ask for the help of experts to write a code snippet for my research project in order to parse some text files.
The task:
I have some ranges like the following:
5-10,14-18,22-27 and a string that I have transformed into a series of characters like so:
1-2-3-4-5-6-7-8-...30
(not all strings are of the same length of course)
Now, what I need to do for my analysis is two things:

1. substitute the numbers that belong to the ranges with a character, say X. So, instead of 5-6-7-8-9-10 for instance, I will have X-X-X-X-X-X This I have managed to do

2. Starting with a specific character, say A from the beginning (in this case 1), substitute the remaining numbers, interchangably with A and B, when they are separated by a group of X's. For instance, I would do A-A-A-A (instead of 1-2-3-4) until I find 5 where the X's begin and then B-B-B (instead of 11-12-13) and then A-A-A instead of 19-20-21 and, finally B-B-B instead of 28-29-30.


The initial string is then:
1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22-23-24-25-26-2 +7-28-29-30

and the expected one is:
A-A-A-A-X-X-X-X-X-X-B-B-B-X-X-X-X-X-A-A-A-X-X-X-X-X-X-B-B-B

Can you please help me with the second point that I cannot solve?

Replies are listed 'Best First'.
Re: Help making this substitutions on a strin
by GrandFather (Saint) on Oct 21, 2022 at 03:15 UTC

    Just for fun:

    use strict; use warnings; my $raw = join '-', 1 .. 30; my @ranges = ([5, 10], [14, 18], [22, 27]); $raw =~ s/(\d+)/(grep {$1 >= $_->[0] && $1 <= $_->[1]} @ranges) ? 'X' +: $1/eg; my @parts = split /((?:-X)+-)/, $raw; my $sub = 'A'; for my $part (@parts) { next if $part =~ /X/; $part =~ s/\d+/$sub/g; $sub = 'A' if ++$sub eq 'C'; } print join('', @parts), "\n";

    Prints:

    A-A-A-A-X-X-X-X-X-X-B-B-B-X-X-X-X-X-A-A-A-X-X-X-X-X-X-B-B-B
    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Help making this substitutions on a strin
by choroba (Cardinal) on Oct 20, 2022 at 22:46 UTC
    #!/usr/bin/perl use warnings; use strict; my @ranges = ('5-10', '14-18', '22-27'); my $in = '1-2-3-4-5-6-7-8-9-10-11-12-13-14-15-16-17-18-19-20-21-22 +-23-24-25-26-27-28-29-30'; my $exp = 'A-A-A-A-X-X-X-X-X-X-B-B-B-X-X-X-X-X-A-A-A-X-X-X-X-X-X-B- +B-B'; use Test::More tests => 1; my $replace = 'A'; my $out = $in; my $in_range; while ($in =~ /([0-9]+)/g) { my $i = $1; if (! $in_range && grep /^$i-/, @ranges) { $in_range = 1; } $out =~ s/[0-9]+/$in_range ? 'X' : $replace/e; if ($in_range && grep /-$i$/, @ranges) { undef $in_range; $replace = $replace eq 'A' ? 'B' : 'A'; } } is $out, $exp;

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
      Thank you @choroba, this works excellent! Much appreciated!!
Re: Help making this substitutions on a strin
by hv (Prior) on Oct 20, 2022 at 22:34 UTC

    That's a nice problem. :) You may also want to show the code you've used to solve the first part of the problem, since I'm sure the Monks would relish the opportunity to see if they can come up with better/faster/cleaner solutions to that.

    For the second part, my approach would be to model it as a state machine. There are two states: state 0 "we're looking at a section of numbers", and state 1 "we're looking at a section of X's". In state 0 we want to replace the number with the appropriate letter A or B; in state 1 we want to leave the X as X; and at one of the transitions - I've chosen the "up" transition from state 0 to state 1 - we want to flip the letter we will replace with between A and B:

    # transition: -up- -down- -up- -down- -up- +-down- # state: 0 0 0 0 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 0 0 1 1 1 1 1 + 1 0 0 my $input = "1-2-3-4-X-X-X-X-X-X-11-12-13-X-X-X-X-X-19-20-21-X-X-X-X-X +-X-28-29"; my $output = part2($input); print "$output\n"; sub part2 { my($input) = @_; my $state = 1; my @replace = ("A", "B"); my $replace_index = 0; # start at 'A' $input =~ s{ ( \d+ | X ) (?# match a number or X) (?= - | \z) (?# followed by '-' or end of string) }{ # capture the old state, to detect transitions my $prev_state = $state; # decide the new state $state = ($1 eq 'X') ? 1 : 0; if ($state == 0) { # state 0: return the replacement character $replace[$replace_index]; } else { # state 1: did we transition? if ($prev_state == 0) { # flip the replacement character between 'A' and 'B' $replace_index = 1 - $replace_index; } # return the matched value, ie don't change anything $1; } }exg; return $input; }

    Note that I initialize the state machine to be in state 1, so that if the input starts with a section of Xs we will still use "A" as the first replacement character - "X-2" will be translated to "X-A". If I had started in state 0, the initial X would be seen as a transition so we would immediately flip the replacement character to be B, and "X-2" would be translated to "X-B". (This is also the reason I chose to flip on the 0-to-1 transition rather than on the 1-to-0 transition.) If you would rather have "X-2" become "X-B", then just change the initial state to be 0.

Re: Help making this substitutions on a strin
by tybalt89 (Monsignor) on Oct 21, 2022 at 10:00 UTC
    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11147549 use warnings; my $ranges = '5-10,14-18,22-27'; my $replace = 'B'; my $in = join '-', 1 .. 30; my $want = 'A-A-A-A-X-X-X-X-X-X-B-B-B-X-X-X-X-X-A-A-A-X-X-X-X-X-X-B-B- +B'; my %X = map { $_ => 'X' } map { /-/ and $` .. $' } split /,/, $ranges; my $out = $in =~ s!\d+! $X{$&} // $& !ger =~ s/[-0-9]{2,}/ $replace ^= 'A'^'B'; $& =~ s|\d+|$replace|gr /ger; print "ranges $ranges\n out $out\n want $want\n"; $out eq $want or die "**Mismatch**";

    Outputs:

    ranges 5-10,14-18,22-27 out A-A-A-A-X-X-X-X-X-X-B-B-B-X-X-X-X-X-A-A-A-X-X-X-X-X-X-B-B-B want A-A-A-A-X-X-X-X-X-X-B-B-B-X-X-X-X-X-A-A-A-X-X-X-X-X-X-B-B-B