1.5. Processing a String One Character at a Time

Problem

You want to process a string one character at a time.

Solution

Use split with a null pattern to break up the string into individual characters, or use unpack if you just want their ASCII values:

@array = split(//, $string);

@array = unpack("C*", $string);

Or extract each character in turn with a loop:

    while (/(.)/g) { # . is never a newline here
        # do something with $1
    }

Discussion

As we said before, Perl's fundamental unit is the string, not the character. Needing to process anything a character at a time is rare. Usually some kind of higher-level Perl operation, like pattern matching, solves the problem more easily. See, for example, Recipe 7.7 , where a set of substitutions is used to find command-line arguments.

Splitting on a pattern that matches the empty string returns a list of the individual characters in the string. This is a convenient feature when done intentionally, but it's easy to do unintentionally. For instance, /X*/ matches the empty string. Odds are you will find others when you don't mean to.

Here's an example that prints the characters used in the string " an apple a day ", sorted in ascending ASCII order:

%seen = ();
$string = "an apple a day";
foreach $byte (split //, $string) {
    $seen{$byte}++;
}
print "unique chars are: ", sort(keys %seen), "\n";




unique chars are:  adelnpy



These split and unpack solutions give you an array of characters to work with. If you don't want an array, you can use a pattern match with the /g flag in a while loop, extracting one character at a time:

%seen = ();
$string = "an apple a day";
while ($string =~ /(.)/g) {
    $seen{$1}++;
}
print "unique chars are: ", sort(keys %seen), "\n";




unique chars are:  adelnpy



In general, if you find yourself doing character-by-character processing, there's probably a better way to go about it. Instead of using index and substr or split and unpack , it might be easier to use a pattern. Instead of computing a 32-bit checksum by hand, as in the next example, the unpack function can compute it far more efficiently.

The following example calculates the checksum of $string with a foreach loop. There are better checksums; this just happens to be the basis of a traditional and computationally easy checksum. See the MD5 module from CPAN if you want a more sound checksum.

$sum = 0;
foreach $ascval (unpack("C*", $string)) {
    $sum += $ascval;
}
print "sum is $sum\n";
# prints "1248" if $string was "an apple a day"

This does the same thing, but much faster:

$sum = unpack("%32C*", $string);

This lets us emulate the SysV checksum program:

#!/usr/bin/perl
# sum - compute 16-bit checksum of all input files
$checksum = 0;
while (<>) { $checksum += unpack("%16C*", $_) }
$checksum %= (2 ** 16) - 1;
print "$checksum\n";

Here's an example of its use:

% perl sum /etc/termcap




1510



If you have the GNU version of sum , you'll need to call it with the - -sysv option to get the same answer on the same file.

% sum --sysv /etc/termcap




1510 851 /etc/termcap



Another tiny program that processes its input one character at a time is slowcat , shown in Example 1.1 . The idea here is to pause after each character is printed so you can scroll text before an audience slowly enough that they can read it.

Example 1.1: slowcat

#!/usr/bin/perl
# 

slowcat - emulate a   s l o w   line printer
# usage: slowcat [-DELAY] [files ...]
$DELAY = ($ARGV[0] =~ /^-([.\d]+)/) ? (shift, $1) : 1;
$| = 1;
while (<>) {
    for (split(//)) {
        print;
        select(undef,undef,undef, 0.005 * $DELAY);
    }
}

See Also

The split and unpack functions in perlfunc (1) and Chapter 3 of Programming Perl ; the use of select for timing is explained in Recipe 3.10


Previous: 1.4. Converting Between ASCII Characters and Values Perl Cookbook Next: 1.6. Reversing a String by Word or Character
1.4. Converting Between ASCII Characters and Values Book Index 1.6. Reversing a String by Word or Character