5
\$\begingroup\$

I am looking for a solution with fewer lines of code. This is code that is working perfectly, but I think I typed too much for this algorithm. If I did something wrong, feel free to tell me the error.

Code

    <?php
    function Clearstring(string $string) 
    {
    $string = strtolower($string);
    $string = preg_replace('/[,.!;?]/',"",$string);
    return $string;
    }   

    function CountString($string){
        $textsplit = preg_split("/\s/",$string);
        $wordsArray = [];
        $array= [];
        $counter = 0;
        foreach($textsplit as $word){
            try{
                $word = Clearstring($word);
                if(in_array($word,$array))
                {
                    if(in_array($wordsArray[$word],$wordsArray))
                    { 
                    $wordsArray[$word] +=1;
                    }
                }
                else{
                    array_push($array,$word);
                    $counter =0;
                    $wordsArray[$word] = $counter;
                    if(in_array($wordsArray[$word],$wordsArray))
                    { 
                        $wordsArray[$word] +=1;
                    }
                }
        }catch(Exception $ex){
            echo "It has occurred the next error ".$ex;
        }
    }
    print_r($wordsArray);
    }
    CountString('Hello, I don`t know who I am but I know who you are, but I don`t know where I am');
?>

Output

Array
(
    [hello] => 1
    [i] => 5
    [don`t] => 2
    [know] => 3
    [who] => 2
    [am] => 2
    [but] => 2
    [you] => 1
    [are] => 1
    [where] => 1
)
\$\endgroup\$

2 Answers 2

5
\$\begingroup\$

You can competely avoid rolling your own code for this task. PHP offers a solution via just two native function calls.

Ask str_word_count() to return all words found using 1 as the second parameter AND nominate the backtick as an acceptable character within words.

Then call array_count_values() to group the encountered words and find their total occurrences.

...How considerate of PHP to offer these tools for you!

Code: (Demo)

$string = 'Hello, I don`t know who I "am" but I know who you are, but I don`t know where I am';
var_export(array_count_values(str_word_count($string, 1, '`')));

Output:

array (
  'Hello' => 1,
  'I' => 5,
  'don`t' => 2,
  'know' => 3,
  'who' => 2,
  'am' => 2,
  'but' => 2,
  'you' => 1,
  'are' => 1,
  'where' => 1,
)

If you used standard apostrophes instead of backticks, you don't need to declare the 3rd parameter in str_word_count(). Demo

$string = "Hello, I don't know who I \"am\" but I know who you are, but I don't know where I am";
var_export(array_count_values(str_word_count($string, 1)));

p.s. I see that I overlooked the strtolower() part. Just call that inside str_word_count().

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I see, thank you so much for your answer, I tried to do all by my own, but there were some functions to help me, my bad👌. \$\endgroup\$
    – Angel Abad
    Jun 23, 2022 at 23:52
4
\$\begingroup\$

Review

Mick has suggested a solution with fewer lines of code. I would like to address some points about the existing code.

Useless conditional

narrow gate

Inside the else block 0 is assigned to $wordsArray[$word], then it checks to see if $wordsArray[$word] (which is 0) is within $wordsArray - that will always be true.

else{
    array_push($array,$word);
    $counter =0;
    $wordsArray[$word] = $counter;
    if(in_array($wordsArray[$word],$wordsArray))
    { 
        $wordsArray[$word] +=1;
    }
}

It could simply assign 1 to $wordsArray[$word].

Simpler array push

stacking image

In the aforementioned code block, $word is pushed into $array:

array_push($array,$word);

When adding a single element to an array, the same can be achieved by assigning the next available key (which can be omitted):

$array[] = $word;

Post-increment operator

Also in both the if And else blocks there are lines to add one to the counts:

$wordsArray[$word] +=1;

Those could be simplified using the post-increment operator ++

$wordsArray[$word]++;

Variable names

Names like $array aren't very descriptive of what they contain. A better name might be $uniqueWords.

Function names

Many PHP programmers follow standards outlined in PHP Standards Recommendations like PSR-1 and PSR-12. PSR-1 states:

Method names MUST be declared in camelCase().

That should apply to function names as well. So idiomatic PHP code would use a name like countString instead of CountString

Spacing

PSR-12 also describes spacing in various sections. For example, see PSR-12 section 4.7:

4.7 Method and Function Calls

When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis, there MUST NOT be a space after the opening parenthesis, and there MUST NOT be a space before the closing parenthesis. In the argument list, there MUST NOT be a space before each comma, and there MUST be one space after each comma.

And also PSR-12 section 5:

5. Control Structures

The general style rules for control structures are as follows:

  • There MUST be one space after the control structure keyword
  • There MUST NOT be a space after the opening parenthesis
  • There MUST NOT be a space before the closing parenthesis
  • There MUST be one space between the closing parenthesis and the opening brace
  • The structure body MUST be indented once

So there should be a space after a foreach:

foreach($textsplit as $word){

Should be:

foreach ($textsplit as $word) {

And similarly for if - like

if(in_array($wordsArray[$word],$wordsArray))
{ 

Should be:

if (in_array($wordsArray[$word], $wordsArray)) { 

Also:

6.2. Binary operators

All binary arithmetic, comparison, assignment, bitwise, logical, string, and type operators MUST be preceded and followed by at least one space.

So Instead of this within the else block:

$counter =0;

Add a space after the =:

 $counter = 0;

And as mentioned earlier that variable is only used once after it is used so it can be eliminated.

Splitting string using regular expression

It is fine to use regular expressions but since it is just splitting on a single space can be done faster using explode() - e.g.

$textsplit = preg_split("/\s/",$string);

can be simply:

$textsplit = explode(' ', $string);

Exception handling

The code echoes the exception:

catch(Exception $ex){
    echo "It has occurred the next error ".$ex;
}

Notice that this is calling the Exception::__toString() method, which may show more details than desired - e.g. the file, line, stack trace, etc. It may be appropriate to call the getMessage() method, or else not show any details to the user and instead show a more generic message.

\$\endgroup\$
3
  • \$\begingroup\$ Man, thank you so much for those tips, give me some good advices, I appreciate that. \$\endgroup\$
    – Angel Abad
    Jun 23, 2022 at 23:58
  • \$\begingroup\$ Your "simpler solution" also is a less efficient solution, making an otherwise linear time complexity quadratic. Furthermore I wonder about "subwords": Won't this count foobar foo as 2x foo and 1x foobar whereas it should count it as 1x foobar and 1x foo? \$\endgroup\$
    – Luatic
    Jun 24, 2022 at 17:29
  • \$\begingroup\$ Thank you for bringing that up. You are correct about the potential for substrings counting multiple times. I have removed that solution. \$\endgroup\$ Jun 24, 2022 at 17:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.