• Jetzt anmelden. Es dauert nur 2 Minuten und ist kostenlos!

Letzte Ausgabe einer While-Schleife modifizieren

Degers

Mitglied
Hallo,

für die Ausgabe einer Auflistung mit Kommata möchte ich auf die Ausgabe eines Kommas bei der letzten Ausgabe verzichten. Wie ist mir das möglich?
Zur Verdeutlichung:
PHP:
$request = mysql_query("SELECT spalte FROM tabelle");
while($row = mysql_fetch_object($request)){
echo $row->whatever . ", ";
}

Ausgabe1, Ausgabe2, Ausgabe3, Ausgabe4,
 
PHP:
$ausgabe = '';
$request = mysql_query("SELECT spalte FROM tabelle");
while($row = mysql_fetch_object($request)){
    $ausgabe .= $row->whatever . ', ';
} 
trim($ausgabe,',');
echo $ausgabe;
 
Auch bei einer Übernahme von 1:1, von Anpassung des Spaltennamens und des Tabellennamens abgesehen, verbleibt das letzte Komma in meinen Tests, könnt noch ein Fehler vorliegen?
Ansonsten werde ich mir schonmal die Funktion trim() genauer ansehen, danke.
 
Dreh' die Sache um:
wenn $ausgabe leer ist, dann §ausgabe
sonst Komma und $ausgabe
Also das Komma nicht hintendran kleben, sondern vorne dran.
 
@achtelpetit: Welcher Sinn steckt dahinter bei jedem Schleifendurchlauf eine Abfrage zu machen?

Du meinst das so nehme ich an:
PHP:
$ausgabe = '';
$request = mysql_query("SELECT spalte FROM tabelle");
while($row = mysql_fetch_object($request)){
    if($ausgabe == ''){
        $ausgabe .= $row->whatever;
    } else {
        $ausgabe .= ', '.$row->whatever;
    }
} 
echo $ausgabe;

Wird auch funktionieren, aber ich finde es sehr unschön und demnach auch schlecht lesbar.


@Degers: Die Funktion trim() schneidet die angegebenen Zeichen des zweiten Parameters im String aus dem ersten Parameter vorne und hinten ab. Siehe auch: http://php.net/trim
 
Also letztendlich so?
PHP:
$ausgabe = '';
$request = mysql_query("SELECT spalte FROM tabelle");
while($row = mysql_fetch_object($request)){
    $ausgabe .= $row->whatever . ', ';
} 
trim($ausgabe,', ');
echo $ausgabe;

Ändert an der Ausgabe ebenfalls nichts. Eine If-Abfrage funktioniert dürfte aber tatsächlich wenig elegant und rechenintensiv sein
 
Es kann sein, dass PHP das nicht rafft mit dem Leerzeichen in der Charlist (2. Parameter bei trim). Versuchs mal so:

PHP:
$ausgabe = '';
$request = mysql_query("SELECT spalte FROM tabelle");
while($row = mysql_fetch_object($request)){
    $ausgabe .= $row->whatever . ', ';
} 
trim(trim($ausgabe),',');
echo $ausgabe;


*edit*

Habe mal getestet:

PHP:
$a = 'hallo, ich, bin, ein, string, ';
$b = trim($a, ', ');
$c = trim($a, ',');

echo '>'.$b.'<';  // Ausgabe: >hallo, ich, bin, ein, string<
echo '>'.$c.'<';  // Ausgabe: >hallo, ich, bin, ein, string, <

Es ist also kein Doppel-Trim nötig wie oben beschrieben.

Der Fehler liegt vielmehr darin, dass wir den Rückgabewert der Funktion trim() nicht wieder in $ausgabe geschrieben haben....

So muss es sein:
PHP:
$ausgabe = '';
$request = mysql_query("SELECT spalte FROM tabelle");
while($row = mysql_fetch_object($request)){
    $ausgabe .= $row->whatever . ', ';
} 
$ausgabe = trim($ausgabe,', ');
echo $ausgabe;
 
Zuletzt bearbeitet:
In der Tat, so funktioniert es, danke.
Neben der Funktionialität versuche ich für den Vorgang nun auch Verständnis zu entwickeln, hier jedoch meinerseits erstmal erledigt.
 
Man könnte auch vorher einfach die Anzahl betroffener Datensätze der MySQL Operation ermitteln. Und dann innerhalb der Schleife abfragen.

PHP:
	$request = mysql_query("SELECT spalte FROM tabelle");
	$anz = mysql_affected_rows();
	for($c=0; $c<$anz && ($row = mysql_fetch_object($request)); $c++)
	{
		echo $row->whatever;
		if(!(($c+1)<$anz) echo ', ';
	}

Man könnte das ganze natürlich auch in einer while() Schleife machen oder die if-Abfrage mit == prüfen.
 
Die Variante mit der if-Abfrage in der Schleife hat theoretisch den Vorteil, nicht alle Daten in einer weiteren Variable zwischenspeichern zu müssen, verbraucht also weniger Arbeitsspeicher.
 
In Verbindung mit der vorgesetzten Abfrage der wahrscheinlicheren Variante (d.h. mit Komma) dürfte die Last weiter reduzierbar sein. Um zu bewerten welche Variante nun günstiger ist fehlt mir die Expertise, da in meinem Fall die Ausgabe maximal zweistellig wird dürfte es für mich auch irrelevant sein.
Nichtsdestotrotz wäre es interessant zu einem Schluss zu kommen, welche Variante performanter ist (oder eine ganz Andere?)
 
Ich empfehle eine Zeitmessung, dabei muß natürlich die DB außen vor bleiben.
Mein Versuchsaufbau
PHP:
    $input = array();
    $input[] = 'Eine sehr lange Textzeile sollte hier eingefügt werden';
    
    $output = '';
    
    $start = microtime();
    
    foreach($input as $ky => $value)
    {
            $output .= ', ' . $value;
    }
    
    $output = trim($output, ', ');
    
    $stop = microtime();
    
    echo $stop - $start;
    echo '<hr/>';
    echo $output;

PHP:
    $input = array();
    $input[] = 'Eine sehr lange Textzeile sollte hier eingefügt werden';
    
    $output = '';
    
    $start = microtime();
    
    foreach($input as $ky => $value)
   {
        if ($output == '')
        {
            $output = $value;
        }
        else
        {
            $output .= ', ' . $value;
        }
    }
    
    $output = trim($output, ', ');
    
    $stop = microtime();
    
    echo $stop - $start;
    echo '<hr/>';
    echo $output;

"$input[] = 'Eine sehr lange Textzeile sollte hier eingefügt werden';" muß natürlich sehr oft untereinanderkopiert werden, vielleicht so 100 - 200 mal, sonst ist die Arbeitszeit zu kurz.

Mein Ergebnis
0.000121 (mit trim)
0.000145 (mit if then)
Differenz 3 Hunderttausendstel Sekunden zugunsten von trim.

Dieser Post hier soll keine Rechthaberei sein (zumal Trim ja tatsächlich schneller ist), ich möchte nur darauf hinweisen, daß mit solchen Digen gewöhnlich kein Blumentopf zu gewinnen ist.
Trim ist ja auch unter dem Aspekt zu betrachten, daß dafür ganze Inhalt von $output ja einmal hin- und wieder zurückgeschaufelt werden muß.

In diesem Fall scheint mir trim übrigens eleganter als mein Vorschlag zu sein, schon weil's leichter lesbar ist.
 
Unfassbar wie viel mist hier rauskommen kann wenn sich alle einigermaßen einig sind ^^

PHP:
$bFirst = true;
$sText = '';
while(/*....*/) {
    if($bFirst) {
        $sText += $mSomething;
        $bFirst = false;
    }
    $sText += ',' . $mSomething;
}

Mach davon mal ein Benchmark (aber bitte mit MEHR als einem eintrag im Array... ;)).

//Edit: Ok ich hätte besser lesen sollen... der Zweite Benchmark meines vorposters ist trotzdem mist da das Trim dort unnötig ist.
 
Nun ja, an den Umgangston in diesem Forum muß man sich wohl gewöhnen? Oder gibt's die Möglichkeit, höflich zu bleiben, selbst wenn ein Beitrag ungeschickt formuliert ist oder wenn ein Lösungsvorschlag nicht gut ist?
Gut, zum Thema:
da das Trim dort unnötig ist.
Ja, ein Fehler beim Übetragen, da hätte ich sorgfältiger prüfen sollen. jetzt habe ich folgenden Versuchsaufbau genommen:
Input füllen (bei allen 3 folgenden Versuchen genau gleich)
PHP:
$input = array();
    $i = 0;
    while($i < 50000)
    {
        $i++;
        $input[] = 'Das ist eine sehr lange Textzeile die hier eingefügt wird,
                    immer und immer wieder, bis genug Stoff im Array liegt.';
    }

1. Versuch (Prüfung auf Hilfsvariable $first)
PHP:
$output = '';
    $start = microtime();
    
    $first = TRUE;
    foreach($input as $ky => $value)
   {
        if ($first)
        {
            $output = $value;
            $first = FALSE;
        }
        else
        {
            $output .= ', ' . $value;
        }
    }
    
    $stop = microtime();
    
    echo $stop - $start;
    echo '<hr/>';
    echo $output;

2. Versuch (Ausgabe mit trim)
PHP:
// wie gehabt
foreach($input as $ky => $value)
   {
        {
            $output .= ', ' . $value;
        }
    }
    $output = trim($output, ', ');
// wie gehabt

3. Versuch (Prüfung ob $output leer)
PHP:
// wie gehabt
    foreach($input as $ky => $value)
   {
        if ($output == '')
        {
            $output = $value;
        }
        else
        {
            $output .= ', ' . $value;
        }
    }
// wie gehabt

Ergebnisse:
1. Versuch (Prüfung auf Hilfsvariable $first) = min 0.109037 - max 0.116789
2. Versuch (Ausgabe mit trim) = min 0.115195 - max 0.118286
3. Versuch (Prüfung ob $output leer) = min 0.110226 - max 0.113725

Ich glaube, die Ergebnisse sprechen für sich und ich bleibe dabei, daß hier in Bezug auf die Geschwindigkeit kein Blumentopf zu gewinnen ist; deshalb halte ich trim für die beste, weil am leichtesten lesbare Lösung.
 
PHP:
<?php

define('ITERATIONS', 1000);

if (!isset($argv[1])) {
    die('$ php -f ' . __FILE__ . ' all' . "\n");
}

if ($argv[1] === 'all') {
    passthru('php -f ' . $argv[0] . ' 1');
    passthru('php -f ' . $argv[0] . ' 2');
    passthru('php -f ' . $argv[0] . ' 3');
    exit;
}

function test1(array $input)
{
    for ($i = 0; $i < ITERATIONS; $i++) {
        $output = '';
        $first = true;

        foreach ($input as $ky => $value) {
            if ($first) {
                $output = $value;
                $first = false;
            } else {
                $output .= ', ' . $value;
            }
        }
    }

    return md5($output) . md5($i);
}

function test2(array $input)
{
    for ($i = 0; $i < ITERATIONS; $i++) {
        $output = '';

        foreach ($input as $ky => $value) {
            $output .= ', ' . $value;
        }
        
        $output = trim($output, ', ');
    }

    return md5($output) . md5($i);
}

function test3(array $input)
{
    for ($i = 0; $i < ITERATIONS; $i++) {
        $output = '';
        foreach ($input as $ky => $value) {
            if ($output === '') {
                $output = $value;
            } else {
                $output .= ', ' . $value;
            }
        }
    }

    return md5($output) . md5($i);
}

$input = array();
$i = 0;
while($i < 5000) {
    $i++;
    $input[] = 'Das ist eine sehr lange Textzeile die hier eingefügt wird,
                immer und immer wieder, bis genug Stoff im Array liegt.';
}

/* Run tests */

$start = microtime(true);
if ($argv[1] === '1') echo 'chck: ' . test1($input) . "\n";
if ($argv[1] === '2') echo 'chck: ' . test2($input) . "\n";
if ($argv[1] === '3') echo 'chck: ' . test3($input) . "\n";
printf("it/s: %d\n", (int) round(ITERATIONS / (microtime(true) - $start)));
echo 'mmry: ' . memory_get_peak_usage(), "\n";

echo "\n";

Code:
$ php -f benchmark.php all
chck: 30b68d9d3ccd7a456ad88f34aaaaf47aa9b7ba70783b617e9998dc4dd82eb3c5
it/s: 127
mmry: 2686648

chck: 30b68d9d3ccd7a456ad88f34aaaaf47aa9b7ba70783b617e9998dc4dd82eb3c5
it/s: 119
mmry: 3148552

chck: 30b68d9d3ccd7a456ad88f34aaaaf47aa9b7ba70783b617e9998dc4dd82eb3c5
it/s: 127
mmry: 2686596

it/s = iterations per second

Die Resultate schwanken. test1 und test3 (die Lösungen ohne trim) sind beim geposteten Testaufbau auf meinem Rechner tendenziell gleich schnell und tendenziell unsignifikant schneller als die Lösung mit trim. Sie verbrauchen außerdem weniger Speicher.

Aber: Je kleiner das Eingabe-Array, desto mehr holt trim auf. Bei einer Größe von 50 Einträgen ist trim in meinen Tests schneller.

Ich habe diverse halbgare Theorien dazu, kann es aber nicht wirklich erklären. Grob: Die nativen PHP-Funktionen sind in C implementiert und deshalb sehr schnell. Sie haben allerdings einen gewissen Overhead, der durch die „Datenübergabe“ an die C-Funktion und den Speicherverbrauch der C-Funktion selbst erzeugt wird. Je größer die Eingabedaten, desto größer dieser Overhead, desto größer vermutlich auch die Laufzeit.

Da C-Code aber massiv schneller ist als PHP-Code, ist die mit Abstand schnellste Lösung an dieser Stelle der Verzicht auf PHP-Konstrukte (for-Schleife, if-Bedingungen, …). Das heißt:

PHP:
function test4(array $input)
{
    for ($i = 0; $i < ITERATIONS; $i++) {
        $output = implode(', ', $input);
    }

    return md5($output) . md5($i);
}

Code:
chck: 30b68d9d3ccd7a456ad88f34aaaaf47aa9b7ba70783b617e9998dc4dd82eb3c5
it/s: 505
mmry: 3023888

Das wäre im Vergleich zu den Lösungen ohne Funktionsaufrufe die „Time-Memory Tradeoff“-Lösung. Geringere Laufzeit, höherer Speicherverbrauch.

Die fällt in dem Szenario, in dem direkt die DB-Resultate verarbeitet werden, aber flach, da sie ein Array mit Eingabedaten verlangt.

Mein Favorit unter den Lösungen mit geringem Speicherverbrauch ist test1. Die Lösung verlässt sich nicht darauf, dass die zu füllende Variable nur beim ersten Durchlauf einen bestimmten Wert hat. Das macht sie vom Schema her allgemeiner einsetzbar.

Das Fazit ist aber: Traut keinem Benchmark, den ihr nicht selbst gefälscht habt. ;)
 
@mermshaus
Schade, daß meine Kenntnisse nicht reichen, um Dein Beispiel zu lesen.
Aber eine Frage:
Mein Favorit unter den Lösungen mit geringem Speicherverbrauch ist test1. Die Lösung verlässt sich nicht darauf, dass die zu füllende Variable nur beim ersten Durchlauf einen bestimmten Wert hat.
Würdest Du mir das vielleicht noch erläutern?
 
Ich habe den Benchmark so angelegt, dass er über die Konsole aufgerufen werden muss, aber in einer Tour alle Tests durchläuft. Pro Test wird PHP jeweils neu gestartet, da ich glaube, eine gewisse Tendenz zu erkennen, dass bei mehreren Tests in einem PHP-Aufruf der erste tendenziell etwas langsamer abläuft, da PHP Speicher allozieren beziehungsweise sich „einschaukeln“ (ja, sehr exakter Fachbegriff) muss. Da erscheint es mir fairer, jedem Test nach Möglichkeit dieselben Startbedingungen mitzugeben. (Ob es wirklich dieselben Startbedingungen sind, kann ich nicht sagen. Es kann gut sein, dass das Betriebssystem auch dort noch irgendwie zwischenfunkt, keine Ahnung.) Du kannst ihn aber auch kurz umschreiben, wenn du keine ordentliche Konsole zur Verfügung hast, indem du zu Beginn des Scripts einfach der Reihe nach $argv[1] = '1'; usw. setzt.

Zu test1:

Na ja, ich gebe zu, das Argument ist eher akademisch und nicht so wirklich zwingend.

Direkte Ausgabe:

PHP:
<?php

$a = array('a', 'b', 1, 'c', 2, 3);
#$a = array(1);                            // Weitere Test-Arrays
#$a = array('a', 'b', 'c');

$flag = false;

// Falls $a Zahlen enthält, diese ausgeben
foreach ($a as $v) {
    if (!$flag) {
        if (is_int($v)) {
            echo 'Folgende Zahlen stehen im Array: ';
            echo $v;
            $flag = true;
        }
    } else {
        if (is_int($v)) {
            echo ', ' . $v;
        }
    }
}

if (!$flag) {
    echo 'Keine Zahlen im Array gefunden';
}

echo "\n";

Ausgabe in Variable zwischenspeichern:

PHP:
<?php

$a = array('a', 'b', 1, 'c', 2, 3);
#$a = array(1);                            // Weitere Test-Arrays
#$a = array('a', 'b', 'c');

$flag = false;
$output = '';

// Falls $a Zahlen enthält, diese ausgeben
foreach ($a as $v) {
    if (!$flag) {
        if (is_int($v)) {
            $output .= 'Folgende Zahlen stehen im Array: ';
            $output .= $v;
            $flag = true;
        }
    } else {
        if (is_int($v)) {
            $output .= ', ' . $v;
        }
    }
}

if (!$flag) {
    $output .= 'Keine Zahlen im Array gefunden';
}

$output .= "\n";

echo $output;

Es ist keine tiefgreifende Änderung am „Schema“ des Codes notwendig. Das zweite Beispiel kann natürlich auch so geschrieben werden:

PHP:
<?php

$a = array('a', 'b', 1, 'c', 2, 3);
#$a = array(1);                            // Weitere Test-Arrays
#$a = array('a', 'b', 'c');

$output = '';

// Falls $a Zahlen enthält, diese ausgeben
foreach ($a as $v) {
    if ($output === '') {
        if (is_int($v)) {
            $output .= 'Folgende Zahlen stehen im Array: ';
            $output .= $v;
        }
    } else {
        if (is_int($v)) {
            $output .= ', ' . $v;
        }
    }
}

if ($output === '') {
    $output .= 'Keine Zahlen im Array gefunden';
}

$output .= "\n";

echo $output;

Das Argument wäre, dass $output hier zwei unabhängige Funktionen erfüllt: Die Variable fungiert einmal als Flag und gleichzeitig als Aufnahmewert für die Rückgabe. Sollte der Code nun auf direkte Ausgabe per echo umgestellt werden, müsste ganz sauber die Flag-Variable wieder hinzugefügt werden.

Oder noch einmal anders formuliert:

PHP:
<?php

$a = array('first', 'second', 'third');

$firstProcessed = false;
$output = '';

foreach ($a as $v) {
    if (!firstProcessed) {
        $output .= doSomethingWithFirstValueThatMightReturnAnEmptyString($v);
        $firstProcessed = true;
    } else {
        $output .= doSomethingElse($v);
    }
}

Wie gesagt, es ist ziemlich akademisch. Ich denke, ich „missbrauche“ auch die Variable, die mit der Ausgabe gefüllt wird, öfter als nicht für Flag-Zwecke.
 
Zurück
Oben