[PHP] Array reduceren tot ranges...

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • ReLexEd
  • Registratie: Juli 2000
  • Laatst online: 18-08 10:09

ReLexEd

2 ReLexEd or not 2 ReLexEd???

Topicstarter
Ben al eventjes aan het stoeien met een functie waar ik niet helemaal uit kom.

De functie is nodig omdat ik anders belachelijke IN-bereiken in de queries ga genereren, en die wil ik dus zoveel mogelijk reduceren.

Ik heb als basis een array met daarin ID's die opgehaald moeten worden waarin mogelijk opvolgende waardes in staan.

De functie tot dusver is prima in staat om de tussenliggende waardes weg te filteren, maar ik mis de stap nog om de bereiken daadwerkelijk te benoemen.

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
function reduceList ($arrInput) {
    print 'Data in: '.implode(', ', $arrInput).'<br />';
    $arrInput = array_unique($arrInput);
    sort($arrInput);
    foreach ($arrInput as $key => $elem) {
        $nextVal = array_search(($elem+1), $arrInput);
        $nextNextVal = array_search(($elem+2), $arrInput);
        if ( (is_numeric($nextVal)) && (is_numeric($nextNextVal)) ) {
            unset($arrInput[$nextVal]);
        }
    }
    return 'Data uit: '.implode(', ', $arrInput).'<br />';
}


Als ik hier dus de array: 1, 2, 3, 4, 5, 8, 9, 10, 20, 30, 30, 30 als input op geef, krijg ik nu: 1, 5, 8, 10, 20, 30 terug...

Het eindresultaat zou moeten zijn : 1-5, 8-10, 20, 30

Dit eindresultaat kan dan in de querie gestopt worden als: WHERE id IN (1-5, 8-10, 20, 30)

Ik hoop dat iedereen me nog kan volgen 8)7

Zelf dacht ik dat er wel een tegenhanger van de range-functie zou zijn, maar die is er niet (of ik zie 'm over het hoofd)...

WIe helpt me uit de brand?

Acties:
  • 0 Henk 'm!

  • BikkelZ
  • Registratie: Januari 2000
  • Laatst online: 21-02 08:50

BikkelZ

CMD+Z

Dat zijn gegarandeerde koppijnproblemen. Wat bij mij vaak helpt is om het uit elkaar te trekken, dus in dit geval eerst te kijken welke ranges je hebt en je data op die manier logisch te ordenen, en pas daarna er een string van bakken. Werkt dit nou lekker, kun je gaan puzzelen hoe je het in een efficientere enkele foreach-loop zet, of je laat het zo, geheel afhankelijk van de tijdsdruk ;)

Gebruik daarvoor dan het gedeelte waarbij er ontdekt wordt dat er een nieuwe range gemaakt moet worden alsmede het moment vlak na de loop.

Nu wel getest en werkend:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?
// deze array is natuurlijk van te voren gesort
$die_nummern = array(1, 2, 3, 4, 5, 8, 9, 10, 20, 30, 30, 30);

$vorig_nummer = null;
$begin_range = null;
$einde_range = null;

$ranges = array();

foreach($die_nummern as $das_nummer)
{
    if ($einde_range != null && $einde_range == $das_nummer)
    {
        // ignore
    }
    elseif ($einde_range != null && $einde_range + 1 == $das_nummer)
    {
        // dit is de volgende van de range
        // einde_range is nu das nummer
        $einde_range = $das_nummer;
    }
    else
    {
        // range onderbroken
        // als de oude range bestaat
        if ($begin_range != null && $einde_range != null)
        {
            //deze toevoegen aan de array met ranges
            $ranges[] = array('begin'=>$begin_range, 'einde'=>$einde_range); 
        }
        // en een nieuwe range beginnen
        $begin_range = $das_nummer;
        $einde_range = $das_nummer;
    }
}

// als we klaar zijn kijken we even of er nog een geldige range overgebleven is
if ($begin_range != null && $einde_range != null)
{
    //deze toevoegen aan de array met ranges
    $ranges[] = array('begin'=>$begin_range, 'einde'=>$einde_range); 
}
// en een nieuwe range beginnen
$begin_range = $das_nummer;
$einde_range = $das_nummer;

// daarna gaan we de array met ranges doorlopen en omzetten naar een string
$rangestring = "(";
$comma = "";
$first = true;

foreach ($ranges as $range)
{
    $rangestring.= $comma.$range['begin'];
    if ($range['begin'] != $range['einde']) $rangestring.= "-".$range['einde'];
    if ($first)
    {
        $comma = ", ";
        $first = false;
    }
}
$rangestring.= ")";

echo $rangestring;

[ Voor 17% gewijzigd door BikkelZ op 29-04-2007 12:33 ]

iOS developer


Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

(jarig!)
ReLexEd schreef op zondag 29 april 2007 @ 11:42:
Dit eindresultaat kan dan in de querie gestopt worden als: WHERE id IN (1-5, 8-10, 20, 30)
Ik snap dit doel niet... jouw IN wordt dan toch uiteindelijk WHERE id IN (-4, -2, 20, 30) :? Of mag je in jouw database ranges opgeven op die manier?

Maar niettemin is het een onzinnige handeling omdat je gewoon losse integers mag opgeven in je IN, zoveel je wilt. Ook als ze oplopend zijn. Ik zou gewoon WHERE id IN (1, 2, 3, 4, 5, ...) er van maken (daar kan je implode leuk voor gebruiken).

Als je het dan toch per se wilt zou ik beginnen met de array te sorteren en dan gewoon een voor een de elementen aflopen, vorige bijhouden en als de vorige = huidige - 1, dan heb je een range die een groter is dan je al had, zo niet dan sla je je vorige range op en begin je opnieuw.

[ Voor 6% gewijzigd door ACM op 29-04-2007 12:27 ]


Acties:
  • 0 Henk 'm!

  • BikkelZ
  • Registratie: Januari 2000
  • Laatst online: 21-02 08:50

BikkelZ

CMD+Z

ACM schreef op zondag 29 april 2007 @ 12:26:
[...]Maar niettemin is het een onzinnige handeling omdat je gewoon losse integers mag opgeven in je IN, zoveel je wilt. Ook als ze oplopend zijn. Ik zou gewoon WHERE id IN (1, 2, 3, 4, 5, ...) er van maken (daar kan je implode leuk voor gebruiken).
Je kunt dit soort routines natuurlijk wel altijd gebruiken voor plaatsen waarbij het cosmetisch wel verantwoord is om te doen. Ik heb zoiets een keer op mijn werk gebruikt om te sorteren aan de hand van de hoeveelheid hits die ik kreeg per letter van het alfabet om rijtjes van max 25 stuks te krijgen een beetje zoals een encyclopedie gesorteerd is.

Dat je dus een rijtje met knoppen kreeg A, B-Dem, Den-Fra, Fre-Z.

iOS developer


Acties:
  • 0 Henk 'm!

  • ReLexEd
  • Registratie: Juli 2000
  • Laatst online: 18-08 10:09

ReLexEd

2 ReLexEd or not 2 ReLexEd???

Topicstarter
Ik dacht zelf ook dat ik gewoon stug de hele array kon imploden, en dat MySQL er geen probleem van zou maken, maar ben er op die manier achter gekomen, dat als het IN-segment maar groot genoeg is MySQL vanzelf de geest geeft...
Enigzins nuanceren... MySQL kan er wel mee overweg, want de querty die wordt opgebouwd komt wel door phpMyAdmin heen, maar zodra deze via adoDB wordt uitgevoerd, is het einde oefening...

Vandaar dat ik voor deze oplossing wilde gaan....

Maar ik besef me nu dat het helemaal niet met IN kan gaan werken.... het zullen dan combinaties worden van IN voor alle singles en BETWEEN's voor de ranges...
Maar of de query daar nu overzichtelijker op zal worden ;)

Thanks voor de wake-up-call! :) (Ok.. het is zondag... .vooruit... :))


Nevertheless... het stukje code van BikkelZ doet het idd vloeiend....
Toch eens kijken of ik er op die manier wel een werkende query van kan bakken...

Het probleem zat er namelijk in, dat ik met een bak legacy-code te maken heb, en er (Yep, daar komt de bekende tijdsdruk) geen refactoring plaats kan/mag vinden...
Nou gebeurt dat gaande weg toch wel een beetje, aangezien er op bepaalde plaatsen echt enorm omslachtig te werk wordt gegaan...
Uit 30.000 records +/- 500 ID's ophalen met een redelijk heftige query, en daar vervolgens per stuk een 2e query op loslaten (+/- 5 result-rijen per stuk), die weer voor elke rij een 3e query afvuurt...
(+/- 5000 queries in totaal... tsjakka)

De pagina deed er in de oude situatie al 15 seconden over om te genereren, en dan wordt je gevraagd om er nog een extra niveau van detaillering aan toe te voegen...

Uitdagingen op dit vlak zijn leuk, maar er zijn grenzen ;))

In ieder geval bedankt voor de hulp!

[ Voor 38% gewijzigd door ReLexEd op 29-04-2007 12:49 ]


Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

(jarig!)
ReLexEd schreef op zondag 29 april 2007 @ 12:38:
Ik dacht zelf ook dat ik gewoon stug de hele array kon imploden, en dat MySQL er geen probleem van zou maken, maar ben er op die manier achter gekomen, dat als het IN-segment maar groot genoeg is MySQL vanzelf de geest geeft...
Als je in-segment heel groot wordt moet je sowieso gaan kijken of je niet wat verkeerds doet... Waar komen die integers vandaan?
Als ze zelf ook uit de database komen kan je bijna altijd beter join's of subselects gebruiken. Maar dan nog, MySQL kan prima overweg met queries tot zeker wel 1MB aan tekst...
Het probleem zat er namelijk in, dat ik met een bak legacy-code te maken heb, en er (Yep, daar komt de bekende tijdsdruk) geen refactoring plaats kan/mag vinden...
Terwijl je ondertussen alweer een hoop tijd kwijt bent vanwege de poging er een werkende query van te maken ;)
Nou gebeurt dat gaande weg toch wel een beetje, aangezien er op bepaalde plaatsen echt enorm omslachtig te werk wordt gegaan...
Uit 30.000 records +/- 500 ID's ophalen met een redelijk heftige query, en daar vervolgens per stuk een 2e query op loslaten (+/- 5 result-rijen per stuk), die weer voor elke rij een 3e query afvuurt...
(+/- 5000 queries in totaal... tsjakka)
En dat kan je niet herschrijven tot een enkele query met een zwik joins/subqueries? Want het klinkt mij nogal ondoordacht opgezet, als je 5000 queries voor een enkele pagina nodig hebt doe je meestal wat verkeerd ;)
De pagina deed er in de oude situatie al 15 seconden over om te genereren, en dan wordt je gevraagd om er nog een extra niveau van detaillering aan toe te voegen...
Wat toch niet meer is dan een andere tabel er bij joinen?

Acties:
  • 0 Henk 'm!

  • BikkelZ
  • Registratie: Januari 2000
  • Laatst online: 21-02 08:50

BikkelZ

CMD+Z

OT: als ik van regel 25 t/m46 kijk in mijn code, dan zie ik twee keer dezelfde code staan. Is er geen elegantere oplossing? Liefst zonder een functie te gebruiken?

iOS developer


Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

(jarig!)
BikkelZ schreef op zondag 29 april 2007 @ 13:24:
OT: als ik van regel 25 t/m46 kijk in mijn code, dan zie ik twee keer dezelfde code staan. Is er geen elegantere oplossing? Liefst zonder een functie te gebruiken?
Das nog best lastig, maar als je een for-loop gebruikt kan je in je if natuurlijk ook checken of je met het laatste element bezig bent en dan ook een range maken.

Of zoiets natuurlijk:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
$elements = array(1, 2, 3, 4, 6, 7, 8, 20, 21);
$ranges = array();

$i= 0;
foreach($elements as $el)
{
  if(!isset($ranges[$i]['s']) || $el > $ranges[$i]['e'] + 1)
  {
        $i++;
        $ranges[$i]['s'] = $el;
  }
  $ranges[$i]['e'] = $el;
}

[ Voor 28% gewijzigd door ACM op 29-04-2007 13:51 ]

Pagina: 1