带有分组项的PHP Foreach循环

I have an array of States, and what I'm trying to do is categorize them by first letter and then display the groups. I have it almost working properly, there's one final problem:

what I have so far

Instead of each state being in it's own gray box, I want all of the states in a single box separated by comma. This is my code:

<?
    $lastChar = '';
    sort($state_list, SORT_STRING | SORT_FLAG_CASE);

    foreach ($state_list as $state) {
        $char = $state[0];
        echo "<div class=\"stateTable\">";
        if ($char !== $lastChar) {
            echo "<div class=\"stateTopRow\">" . strtoupper($char) . "</div>";
                echo "<div class=\"clear\"></div>";
        $lastChar = $char;
        }
            echo "<div class=\"stateBody\">" . $state . "</div>";
                echo "<div class=\"clear\"></div>";
        echo "</div>";
    }
?>

The issue is that it's looping the entire stateBody div for each individual state. I've tried somehow including stateBody within the if statement, but that breaks the styling entirely (and rightfully so).

Is there some way to include stateBody within the if statement and then add another foreach to loop through the corresponding states? Thank you!

I would recommend a slightly different approach. Since you ultimately want them all grouped, it would be better to start out by organizing your array accordingly. Loop over it, and create a new array with sub-arrays indexed by the first letter. As you iterate the original array, append to the new one at the index of the appropriate character. It then becomes much easier to loop over it for your output.

It's often a good idea to format your data into the structure you ultimately need from the get-go, allowing you to simplify your output logic later.

// Start by sorting as you already did
sort($state_list, SORT_STRING | SORT_FLAG_CASE);

// Create a new array
$output_array = array();

// Loop over the one you have...
foreach ($state_list as $state) {
  $first = strtoupper($state[0]);
  // Create the sub-array if it doesn't exist
  if (!isset($output_array[$first])) {
    $ouput_array[$first] = array();
  }

  // Then append the state onto it
  $output_array[$first][] = $state;
}

The resultant array looks like:

Array
(
    [A] => Array
        (
            [0] => Alabama
            [1] => Arkansas
            [2] => Alaska
        )

    [C] => Array
        (
            [0] => Connecticut
        )

)

Now you just need a simple loop and an implode() to produce your output:

// For clarity, I omitted your surrounding <div> markup
// this is just the first letter and the state lists...
foreach($output_array as $state_index => $states) {
  // The index is the first letter...
  echo "<div class='stateTopRow'>$state_index</div>";

  // And the sub-array can be joined with implode()
  echo "<div class='stateBody'>" . implode(', ', $states) . "</div>";
}
<?
    $lastChar = '';
    sort($state_list, SORT_STRING | SORT_FLAG_CASE);
    $cnt = 0;

    echo "<div class=\"stateTable\">";
    foreach ($state_list as $state) {
        $char = $state[0];

        if ($char !== $lastChar) {
            if (($char !== 'a') && ($char !== 'A')) {
                echo "</div><div class=\"clear\"></div>";   // close off last state list 
            }
            echo "<div class=\"stateTopRow\">" . strtoupper($char) . "</div>";
                echo "<div class=\"clear\"></div>";
            $lastChar = $char;
            $cnt = 0;
        }
        if ($cnt == 0) {
            echo "<div class=\"stateBody\">" . $state;
        } else {
            echo ", " . $state;
        }
            $cnt++;
    }
    echo "</div>";
?>
$lastChar = '';
sort($state_list, SORT_STRING | SORT_FLAG_CASE);

echo "<div class='stateTable'>";
foreach ($state_list as $state)
{
    $char = $state[0];
    if ($char != $lastChar)
    {
        if ($lastChar != '')
            echo "</div><div class='clear' />"; // close previous state list
        $lastChar = $char;
        echo "<div class='stateTopRow'>" . strtoupper($char) . "</div>";
        echo "<div class='stateBody'>$state"; // open new state list
    }
    else // next state name
    {
        echo ", $state";
    }
}
echo "</div><div class='clear' />"; // close last list
echo "</div>";                      // close whole table

As another commenter stated, mixing up output and data organization is rarely a good idead.
Look how simpler it becomes once you separate the problems :

// sort all states alphabetically
sort($state_list, SORT_STRING | SORT_FLAG_CASE);

// split names according to their initial
foreach ($state_list as $state)
    $alpha_table[$state[0]][] = $state;

// output each letter group
foreach ($alpha_table as $letter => $list)
{
    echo "<div class='stateTopRow'>".strtoupper($letter) ."</div>". // header
         "<div class='stateBody'>"  .implode (",", $list)."</div>". // list
         "<div class='clear' />";                                   // separator
}

If tring to keep everything in one scan (for performance) I suggest the following:

<?
        $lastChar = '';
        sort($state_list, SORT_STRING | SORT_FLAG_CASE);

        foreach ($state_list as $state) {
            $char = $state[0];
             $states_arr = array();
            echo "<div class=\"stateTable\">";

                 if ($char !== $lastChar) {
                   echo "<div class=\"stateTopRow\">" . strtoupper($char) . "</div>";
                   echo "<div class=\"clear\"></div>";

                   //if not the first letter
                   if ($lastChar) {
                     echo "</div>";//end the stateDiv div
                     echo implode($states_arr,', ');//print the states with commas
                   }

                   //open the stateBody div
                   echo "<div class=\"stateBody\">";

                    $lastChar = $char;
                }

                //add state to the array but don't print yet
                $states_arr[] = $state;

                echo "<div class=\"clear\"></div>";
            echo "</div>";
        }
    ?>

If performance is not a factor, you may want to actually run 2 scans - the first to organize the data and the 2nd to display it.

However, the above method should work according to what you requested.

Hope this helps!

<?php
$state_list= array("alabama", "alaska", "arkansas");
$lastChar = '';
sort($state_list, SORT_STRING | SORT_FLAG_CASE);

foreach ($state_list as $state) {
    $char = $state[0];
    echo "<div>";
    echo "<div class=\"stateTable\">";
    if ($char !== $lastChar) {
        echo "<div class=\"stateTopRow\">" . strtoupper($char) ."</div>";
            echo "<div class=\"clear\"></div>";
    $lastChar = $char;
    }
}
$states= implode(",", $state_list);
echo $states;
?>