You are given a string, group size and filler character.
Write a script to divide the string into groups of given size. In the last group if the string doesn’t have enough characters remaining fill with the given filler character.
Input: $str = "RakuPerl", $size = 4, $filler = "*"
Output: ("Raku", "Perl")
Example 2
Input: $str = "Python", $size = 5, $filler = "0"
Output: ("Pytho", "n0000")
Example 3
Input: $str = "12345", $size = 3, $filler = "x"
Output: ("123", "45x")
Example 4
Input: $str = "HelloWorld", $size = 3, $filler = "_"
Output: ("Hel", "loW", "orl", "d__")
Example 5
Input: $str = "AI", $size = 5, $filler = "!"
Output: "AI!!!"
Our solution will be simple. After reading and parsing the input, we add
$size - 1 filler characters to the string $str. Then, while $str has
at least $size characters, we remove $size characters from $str and
add those to the output array. Except for our C and sed solutions, all other
solutions follow this strategy.
This will be a simple program. After reading and parsing the input into
$str, $size and $filler, we do:
#
# Add filler characters to $str
#
$str .= $filler x ($size - 1);
#
# While we have at least $size characters in $str, chop off the
# first $size characters, and add them to @out.
#
my @out;
while (length ($str) >= $size) {
push @out => substr $str, 0, $size, ""
}
say "@out"
Note the 4-arg version of substr; it returns the substring of $str
consisting of the first $size characters, while at the same time, replacing
those characters by the fourth argument — the empty string.
A more Perlish way would have been to use $str =~ /..../g (with $size
dots), instead of using a loop and substr, but the loop translates easier
to various languages. But,
We do have a one liner using a regular expression, which we will present without further explaination:
perl -alpE '$_="@{[($F[0].($F[2]x($F[1]-1)))=~/${\(q!.!x$F[1])}/g]}"'
C requires us to do work. You need to malloc space for the output
array, and for each string in that array.
We'll pick up the program when we have char * str, int size, and
char filler (same variables as in our Perl solution — without a
sigil, but with a type).
First, we need to calculate the size of the output array (out_len):
size_t str_len = strlen (str);
size_t out_len = str_len / size + (str_len % size ? 1 : 0);
Next step is to allocate the memory. Since in C, strings ought to end with
a NUL character, we fill those in as well:
for (size_t i = 0; i < out_len; i ++) {
if ((out [i] = (char *)
malloc ((size + 1) * sizeof (char))) == NULL) {
perror ("Malloc failed");
exit (1);
}
out [i] [size] = NUL;
}
Next step is to fill the last string of the array with filler characters. Some may be over written later. The first character of the last string will not be a filler character:
for (size_t i = 1; i < size; i ++) {
out [out_len - 1] [i] = filler;
}
We now iterate over the characters of str, copying them to the
right place:
for (size_t i = 0; i < str_len; i ++) {
out [i / size] [i % size] = str [i];
}
If you malloc things, you ought to clean up after yourself, using
free:
for (size_t i = 0; i < out_len; i ++) {
free (out [i]);
}
free (out);
Find the full program on GitHub.
In our sed solution, we cheat a little. First of all, sed doesn't have variables, let alone array variables, so we cannot store groups. Instead, we just print out the results. Furthermore, we cannot do arithmetic; we will have to use hard coded numbers. As such, we will only support groups whose size does not exceed 20.
This is our program
s/ 1 (.)$//; Tl2; s/.{1}/& /g; be;
:l2 s/ 2 (.)$/\1/; Tl3; s/.{2}/& /g; be;
:l3 s/ 3 (.)$/\1\1/; Tl4; s/.{3}/& /g; be;
:l4 s/ 4 (.)$/\1\1\1/; Tl5; s/.{4}/& /g; be;
:l5 s/ 5 (.)$/\1\1\1\1/; Tl6; s/.{5}/& /g; be;
:l6 s/ 6 (.)$/\1\1\1\1\1/; Tl7; s/.{6}/& /g; be;
:l7 s/ 7 (.)$/\1\1\1\1\1\1/; Tl8; s/.{7}/& /g; be;
:l8 s/ 8 (.)$/\1\1\1\1\1\1\1/; Tl9; s/.{8}/& /g; be;
:l9 s/ 9 (.)$/\1\1\1\1\1\1\1\1/; Tl10; s/.{9}/& /g; be;
:l10 s/ 10 (.)$/\1\1\1\1\1\1\1\1\1/; Tl11; s/.{10}/& /g; be;
:l11 s/ 11 (.)$/\1\1\1\1\1\1\1\1\1\1/; Tl12; s/.{11}/& /g; be;
:l12 s/ 12 (.)$/\1\1\1\1\1\1\1\1\1\1\1/; Tl13; s/.{12}/& /g; be;
:l13 s/ 13 (.)$/\1\1\1\1\1\1\1\1\1\1\1\1/; Tl14; s/.{13}/& /g; be;
:l14 s/ 14 (.)$/\1\1\1\1\1\1\1\1\1\1\1\1\1/; Tl15; s/.{14}/& /g; be;
:l15 s/ 15 (.)$/\1\1\1\1\1\1\1\1\1\1\1\1\1\1/; Tl16; s/.{15}/& /g; be;
:l16 s/ 16 (.)$/\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1/; Tl17; s/.{16}/& /g; be;
:l17 s/ 17 (.)$/\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1/; Tl18; s/.{17}/& /g; be;
:l18 s/ 18 (.)$/\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1/; Tl19; s/.{18}/& /g; be;
:l19 s/ 19 (.)$/\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1/; Tl20; s/.{19}/& /g; be;
:l20 s/ 20 (.)$/\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1/; T; s/.{20}/& /g; be;
:e s/(.*) [^ ]*$/\1/
First, we try to find the right size; starting from 1 and counting up, we find try to match the size and the following filler character. If there is a match, the entire "space-size-space-filler character" is replaced by (size - 1) times the filler character (like we did in the Perl solution).
The T command jumps iff the previous command failed — that is, if the
size did not match the pattern, we just to the next line (the T command
is followed by a label name to jump to). If, however, the substitution
succeeded (so, we now know the size, and have added filler characters),
we execute the substitution in the third column: this substitution
(which acts globally due to the /g modifier (as in Perl)) replaces a
group of size characters by itself (& in the replacement part means
the entire match (like $& in Perl)), followed by a space. Then we
have an unconditional jump to the e label.
At the e label, we execute s/(.*) [^ ]*$/\1/, which just removes
the last space, and anything following it.
Find the full program on GitHub.
We also have solutions in AWK, Bash, Go, Lua, Node.js, Python, R, Ruby and Tcl, all using more or less the same steps as our Perl solution.