How to get the number of the corresponding characters at the start of a string

Viewed 227

I have two strings e.g. $must and $is. They should be identical at the start. But if there is an error, I want to know where.

Example:

my $must = "abc;def;ghi";
my $is   = "abc;deX;ghi";

The "X" on position 6 is not equal, so this is my result I need.

So I need something like

my $count = count_equal_chars($is, $must);

where the result is "6" becaus character 0 to five are equal. (Don't matter if it is "5" because writing ++ is no problem.)

My code so far:

EDIT: Added a workaround.

#!/usr/bin/perl

use strict;
use warnings;
use utf8;

my $head_spec = "company;customer;article;price"; # specified headline
my $count = 0;                                    # row counter

while (<DATA>) {
    s/[\r\n]//;      # Data comes originally from Excel ...
    if (!$count) {
        # Headline:

        ## -> error message without position:
        ##print "error in headline\n" unless ($head_spec eq $_);

        ## -> writeout error message with position:
        next if ($head_spec eq $_);
        # Initialize char arrays and counter
        my @spec = split //, $head_spec; # Specified headline as character-array
        my @is   = split //, $_;         # Readed    headline as character-array
        my $err_pos = 0;                 # counter - current position
        # Find out the position:
        for (@spec) {
             $err_pos++, next if $is[$err_pos] eq $_;
             last;
        }
        # Writeout error message
        print "error in headline at position: $err_pos\n";
    }
    else {
        # Values
        print "process line $count, values: $_\n";
    }
}
continue { $count++; }

__DATA__
company;custXomer;article;price
Ser;0815;4711;3.99
Ser;0816;4712;4.85

Background:
The background is, that there is a .csv-file with a very long header (>1000 characters). This header specified. If there is an error in it, the file has an error and must be edited by the user. So it is useful to tell him, where the error is, so he doesn't need to compare the whole line.

3 Answers

Using the xor, ^, operator can find the error position. Where the sequential letters match, the xor operation gives a \0.

$-{0] is the last match start variable (for the regex in the preceding line, (($must ^ $is) =~ /[^\0]/).

You can find the position this way:

#!/usr/bin/perl
use strict;
use warnings;

my $must = "company;customer;article;price";
my $is   = "company;custXomer;article;price";


($must ^ $is) =~ /[^\0]/; # find first non-matching character

print "Error position is ", $-[0];  # position of first non-matching char

Prints: 12

We can compare character-by-character and also take the lengths of the strings into account for edge cases:

use strict;
use warnings;
use List::Util qw<min max>;

my $must = "company;customer;article;price";
my $is   = "company;custXomer;article;price";

# to-be reported position
my $pos = 0;

# get minimum and maximum of the lengths
my @lengths = map length, ($must, $is);
my $min_length = min @lengths;
my $max_length = max @lengths;

# increment till an inequality occurs or a string is consumed fully
++$pos until substr($must, $pos, 1) ne substr($is, $pos, 1) || $pos == $min_length;

# report the result
print $pos == $min_length ? ($pos < $max_length ? "missing cols" : "no diff") : $pos;

If, at the end, the position is equal to the minimum length, then there are 2 options: either they are fully equal or one is longer so we check against maximum length. Else, report the position as is.

The following code

  • splits lines into two character arrays @must and @is
  • compares length of the arrays and warns if they differ
  • then compare arrays until first mismatch
  • prints error if $pos is not matches last index of $must string
use strict;
use warnings;
use feature 'say';

my $must = "abc;def;ghi";
my $is   = "abc;deX;ghi";

my @must = split('', $must);
my @is   = split('', $is);
my $pos;

warn "Warning: length is differ"
    unless $#must == $#is;

for ( 0..$#must ) {
    $pos = $_;
    last unless $must[$pos] eq $is[$pos];
}

say "Error: The strings differ at position $pos"
    unless $pos == $#must;

Output

Error: The strings differ at position 6
Related