6.5 Grep & Map

The functions grep and map can be easily combined:

  • Step 1: use grep to filter an array and save the results into a new array
  • Step 2: use map to apply some operation on each element of the returned new array
  • Step 3: combine both functions in one statement (use brackets)
As already said, I prefer the block syntax with curly braces.

Step 1

@arr = (0..5);
@arr_new_1 = grep { $_ > 3 } @arr;
Step 2

@arr_new_2 = map { $_ + 10 } @arr_new_1; 
print("@arr_new_2"); # output: 14 15
Step 3:

@arr = (0 .. 5);
@arr_new = map { $_ + 10 } (grep { $_ > 3 } @arr);
print("@arr_new"); # output: 14 15
Now some examples will follow:

Example 1: filter even numbers and square them

@numbers = (1, 2, 3, 4, 5, 6, 7, 8, 9);
@squares_of_evens = map { $_ ** 2 } (grep { $_ % 2 == 0 } @numbers);
print "@squares_of_evens"; # Output: 4 16 36 64
Example 2: filter strings containing 'a' and convert them to uppercase

@words = qw (apple banana cherry grape kiwi);
@filtered_and_uppercased = map { uc($_) } (grep { /a/ } @words);
print "@filtered_and_uppercased"; # Output: APPLE BANANA GRAPE
Example 3: filter numbers greater than 3 and double them

@values = (2, 4, 6, 8, 10);
@doubled_greater_than_three = map { $_ * 2 } grep { $_ > 3 } @values;
print "@doubled_greater_than_three"; # Output: 8 12 16 20
Example 4: filter words starting with 'b' and append ' - starts with b'

@words = qw(apple banana cherry grape kiwi);
@transformed_words = map { "$_ - starts with b" } grep { /^b/ } @words;
print "@transformed_words"; # Output: banana - starts with b
Example 5: filter 2-digit numbers, square them and print the result as a string

@numbers = (1,2,4,8,16,32,64,128);
print( join (" ", map{ $_ * $_ } (grep { /^\d{2}$/ } @numbers) ) ); # 256 1024 4096
Example 6: find the first unique character in a string

The way with only grep

$str = "abcdabcdaabcdabcdaZabcdabcda";
$pos = -1;
@arr = split (//, $str);

foreach (@arr) {
	$pos++;
	if( grep(/@arr[$pos]/, @arr) == 1 ) {		
	  print("The first unique character in $str is: " . $arr[$pos] . "\n"); # Output: Z
	  exit;
	}  
}
BTW One could consider to narrow down the search array:

if( grep(/@arr[$pos]/, @arr[$pos..@arr-1]) == 1 )
The way with map and grep

$str = "abcdabcdaabcdabcdaZabcdabcda";
@arr = split (//, $str);

sub pos_first_unique_char {
 map { $i = $_; $c = $arr[$i]; return $_ if (grep ($c eq $_, @arr) == 1) } 0 .. @arr-1;
}

$pos_first_unique_char = pos_first_unique_char();

print("The first unique character in $str is: " .  $arr[$pos_first_unique_char] . " (index " . $pos_first_unique_char  . ") \n"); # Output unique character Z and index 18
A shorter alternative with a function as array index:

$str = "abcdabcdaabcdabcdaZabcdabcda";
@arr = split (//, $str);

sub pos_first_unique_char {
 map { $i = $_; $c = $arr[$i]; return $_ if (grep ($c eq $_, @arr) == 1) } 0 .. @arr-1;
}

print("The first unique character in $str is: " .  $arr[pos_first_unique_char] . " (index " . pos_first_unique_char  . ") \n"); # Output unique character Z and index 18