Home > other >  How can I remove all the vowels unless they are in word beginnings?
How can I remove all the vowels unless they are in word beginnings?

Time:09-22

$text = "I like apples more than oranges\n";

@words = split /” “/, $text;

foreach (@words) [1..] {
                      
    if $words "AEIOUaeiou";
                     
    $words =~ tr/A E I O U a e i o u//d;
}

print "$words\n";

"I like apples more than oranges" will become "I lk appls mr thn orngs". "I" in "I", "a" in "appls" and "o" in "orngs" will stay because they are the first letter in the word.

This is my research assignment as a first year student. I am allowed to ask questions and later cite them. Please don't be mean.

CodePudding user response:

I know you say you are not allowed to use a regex, but for everyone else that shows up here I'll show the use of proper tools. But, then I'll do something just as useful with tr///.

One of the tricks of programming (and mathematics) decomposing what look like hard problems into easier problems, especially if you already have solutions for the easy problems. (Read about Parnas decomposition, for example).

So, the question is "How can I remove all the vowels unless they are in word beginnings?" (after I made your title a bit shorter). This led the answers to think about words, so they split up the input, did some work to ensure they weren't working on the first character, and then reassembled the result.

But, another way to frame the problem is "How do I remove all the vowels that come after another letter?". The only letter that doesn't come after another letter is the first letter of a word.

The regex for a vowel that comes after another letter is simple (but I'll stick to ASCII here, although it is just as simple for any Unicode letter):

[a-z][aeiou]

That only matches when there is a vowel after the first letter. Now you want to replace all of those with nothing. Use the substitution operator, s///. The /g flag makes all global substitutions and the /i makes it case insensitive:

s/[a-z][aeiou]//gi;

But, there's a problem. It also replaces that leading letter. That's easy enough to fix. The \K in a substitution says to ignore the part of the pattern before it in the replacement. Anything before the \K is not replaced. So, this only replaces the vowels:

s/[a-z]\K[aeiou]//gi;

But, maybe there are vowels next to each other, so throw in the quantifier for "one or more" of the preceding item:

s/[a-z]\K[aeiou] //gi;

You don't need to care about words at all.

Some other ways

Saying that a letter must follow another letter has a special zero-width assertion: the non-word boundary, \B (although that also counts digits and underscore as "letters"):

s/\B[aeiou] //gi;

The \K was introduced v5.10 and was really a nifty trick to have a variable-width lookbehind. But, the lookbehind here is fixed width: it's one character:

s/(?<=[a-z])[aeiou] //gi;

But, caring about words

Suppose you need to handle each word separately, for some other requirement. It looks like you've mixed a little Python-ish sort of code, and it would be nice if Perl could do that :). The problem doesn't change that much because you can do the same thing for each individual word.

foreach my $word ( split /\s /, $x ) {
    .... # same thing for each word
    }

But, here's an interesting twist? How do you put it all back together? The other solutions just use a single space assuming that's the separator. Maybe there should be two spaces, or tabs, or whatever. The split has a special "separator retention mode" that can keep whatever was between the pieces. When you have captures in the split pattern, those capture values are part of the output list:

my @words_and_separators = split /(\s )/, $x;

Since you know that none of the separators will have vowels, you can make substitutions on them knowing they won't change. This means you can treat them just like the words (that is, there is no special case, which is another thing to think about as you decompose problems). To get your final string with the original spacing, join on the empty string:

my $ending_string = join '', @words_and_separators;

So, here's how that might all look put together. I'll add the /r flag on the substitution so it returns the modified copy instead of working on the original (don't modify the control variable!):

my @words;
foreach my $word ( split /(\s )/, $x ) {
    push @words, $word =~ s/\B[aeiou] //gr;
    }
my $ending_string = join '', @words;

But, that foreach is a bit annoying. This list pipeline is the same, and it's easier to read these bottom to top. Each thing produces a list that flows into the thing above it. This is how I'd probably express it in real code:

my $ending_string =
    join '',
    map { s/\B[aeiou] //gr }   # each item is in $_
    split /(\s )/, $x;

Now, here's the grand finale. What if we didn't split thing up on whitespace but on whitespace and the first letter of each word? With separator retention mode we know that we only have to affect every other item, so we count them as we do the map:

my $n = 0;
my $ending_string =
    join '',
    map {   $n % 2 ? tr/aeiouAEIOU//dr : $_ }
    split /((?:^|\s )[a-z])/i, $x;

But, I wouldn't write this technique in this way because someone would ultimately find me and exact their revenge. Instead, that foreach I found annoying before may soothe the angry masses:

my $n = 0;
foreach ( split /((?:^|\s )[a-z])/i, $x ) {
    print   $n % 2 ? tr/aeiouAEIOU//dr : $_;
    }

This now remembers the actual separators from the original string and leaves alone the first character of the "word" because it's not in the element we will modify.

The code in the foreach doesn't need to use the conditional operator, ?: or some of the other features. The important part is skipping every other element. That split pattern is a bit of a puzzler if you haven't seen it before, but that's what you get with those sorts of requirements. I think modifying a portion of the substring is just as likely to trip up people on a first read.

I mean, if they are going to make you do it the wrong way in the homework, strike back with something that will take up a bit of their time. :)

Oh, this is fun

I had another idea, because tr/// has another task beyond transliteration. It also counts. Because it returns the number of replacements, if you replace anything with itself, you get a count of the occurrences of that thing. You can count vowels, for instance:

my $has_vowels = $string =~ tr/aeiou/aeiou/;  # counts vowels

But, with a string of one letter, that means you have a way to tell if it is a vowel:

my $is_vowel = substr( $string, $i, 1 ) =~ tr/aeiou/aeiou/;

You also can know things about the previous character:

my $is_letter = substr( $string, $i - 1, 1 ) =~ tr/a-zA-Z/a-zA-Z/;

Put that together and you can look at any position and know if it's a vowel that follows a letter. If so, you skip that letter. Otherwise, you add that letter to the output:

use v5.10;

$x = "I like apples       more than oranges  oooooranges\n";

my $output = substr $x, 0, 1; # avoid the -1 trap (end of string!)
for( my $i = 1; $i < length $x; $i   ) {
    if( substr( $x, $i, 1 ) =~ tr/aeiou/aeiou/ ) { # is a vowel
        next if substr( $x, $i - 1, 1 ) =~ tr/a-zA-Z/a-zA-Z/;
        }

    $output .= substr $x, $i, 1;
    }

say $output;

This has the fun consequence of using the recommended operator but completely bypassing the intent. But, this is a proper and intended use of tr///.

CodePudding user response:

It appears that you need to put a little more effort into learning Perl before taking on challenges like this. Your example contains a lot of code that simply isn't valid Perl.

$x = "I like apples more than oranges\n";       #the original sentence
foreach $i in @x[1..] {

You assign your text to the scalar variable $x, but then try to use the array variable @x. In Perl, these are two completely separate variables that have no connection whatsoever. Also, in Perl, the range operator (..) needs values at both ends.

If you had an array called @x (and you don't, you have a scalar) then you could do what you're trying to do here with foreach $i (@x)

if $i "AEIOUaeiou";

I'm not sure what you're trying to do here. I guess the nearest useful Perl expression I can see would be something like:

if ($i =~ /^[AEIOUaeiou]$/)

Which would test if $i is a vowel. But that's a regex, so you're not allowed to use it.

Obviously, I'd solve this problem with a regex, but as those are banned, I've reached for some slightly more obscure Perl features in my code below (that's so your teacher won't believe this is your solution if you just cut and paste it):

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';

my $text = "I like apples more than oranges\n";

# Split the string into an array of words
my @words = split /\s /, $text;

# For each word...
for (@words) {
  # Get a substring that omits the first character
  # and use tr/// to remove vowels from that substring
  substr($_, 1) =~ tr/AEIOUaeiou//d;
}

# Join the array back together
$text = join ' ', @words;

say $text;

Update: Oh, and notice that I've used tr/AEIUOaeiou//d where you have tr/A E I O U a e i o u//d. It probably won't make any difference here (depending on your approach - but you'll probably be applying it to strings that don't contain spaces) but it's good practice to only include the characters that you want to remove.

CodePudding user response:

We can go over the input string from the end and remove any vowel that's not preceded by a space. We go from right to left so we don't have to adjust the position after each deletion. We don't need to check the very first letter, it shouldn't be ever removed. To remove a vowel, we can use tr///d on the substr of the original string.

for my $i (reverse 1 .. length $x) {
    substr($x, $i, 1) =~ tr/aeiouAEIOU//d
        if substr($x, $i - 1, 1) ne ' ';
}

CodePudding user response:

Firstly your if statement is wrong. Secondly this is not a Perl code. Here is a piece of code that will work, but there is a better way to do it

my $x = "I like apples more than oranges\n";
my $new = "";   
my @arr;
foreach my $word (split(' ', $x)) {  
    @arr =  split('', $word);
    foreach (my $i; $i<scalar @arr; $i  ){
        if ($i == 0){
            $new .= $arr[$i];
        }
        elsif (index("AEIOUaeiou", $arr[$i]) == -1) {
            $new .= $arr[$i];
        }   
    }
    $new .= " ";
}
print "$new\n";

Here I am splitting the string in order to get an array, then I am checking if the given char is a vowel, if it's not, I am appending it to a new string.

Always include

use strict;
use warnings;

on top of your code.

CodePudding user response:

Clearly this is an exercise in lvalues. Obviously. Indubitably!

#!/usr/bin/env perl
# any old perl will do

use 5.010;
use strict;
use warnings;

# This is not idomatic nor fantastic code. Idiotastic?

$_='I am yclept Azure-Orange, queueing to close a query. How are YOU?';

# My little paws typed "local pos" and got
# "Useless localization of match position" :(
# so a busy $b keeps/restores that value

while (/\b./g) {
    substr($_,$b=pos,/\b/g && -$b pos)
      # Suggestion to use tr is poetic, not pragmatic,
      # ~ tr is sometimes y and y is sometimes a vowel
      =~ y/aeiouAEIOU//d;
    pos=$b;
}

say
# "say" is the last word.

Was there an embargo against using s/// substitution, or against using all regex? For some reason I thought matching was OK, just not substitution. If matches are OK, I have an idea that "improves" upon this by removing $b through pattern matching side effects. Will see if it pans out. If not, should be pretty easy to replace /\b/ and pos with index and variables, though the definition of word boundary over-simplifies in that case.

(edit) here it is a little more legible with nary a regex

my $text="YO you are the one! The-only-person-  asking about double spaces.
Unfortunate about newlines...";

for (my $end=length $text;
     $end > 0 && (my $start = rindex $text,' ',$end);
     $end = $start-1) {
    # y is a beautiful letter, using it for vowels is poetry.
    substr($text,2 $start,$end-$start) =~ y/aeiouUOIEA//d;   
}
say $text;

Maybe more devious minds will succeed with vec, unpack, open, fork?

You can learn about some of these techniques via perldoc -f substr perldoc -f pos perldoc re

As for my own implementer notes, the least important thing is ending without punctuation so nothing can go after

  •  Tags:  
  • perl
  • Related