[PHP] Array - alle unieke combinaties

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Hi allen,

Lang geleden dat ik het Tweakers forum actief gebruikte als hulpmiddel. Ik was zelfs m'n account vergeten 8)7 vandaar mijn "eerste post" onder nieuw account, excuses hiervoor :)

De afgelopen jaren leerde ik mezelf PHP aan om die statische websites van me toch wat dynamiek te bezorgen. Ondertussen ben ik op het punt gekomen dat ik eigenhandig een webshop aan het uitwerken ben. Een beetje het warm water opnieuw uitvinden maar dan op mijn manier. Deze uitdaging lukt goed, tot nu. * I'm stuck * :O

Meerbepaald bij hetvolgende...
Zoals menig webshops kunnen bezoekers filters gebruiken om hun keuze te specifiëren. In mijn geval creëer ik een array als volgt :
PHP:
1
2
3
4
5
$filterParams = array(
    'processor' => array('Intel Core i3', 'Intel Core i5'),
    'opslagHDD' => array('500', '1000'),
    'scherm' => array('15.6', '17.3')
);

De lengte van de array is afhankelijk van de unieke gekozen filters.

Nu wens ik een nieuwe array te verkrijgen met alle combinaties om hiermee uiteindelijk mijn filter query te kunnen uitbouwen (SELECT ... FROM Artikels ...... WHERE ({filterQuery}) ...).

Momenteel kon ik volgende uitwerken, weliswaar hard-coded volgens aantal keys in mijn filterParams array..
PHP:
1
2
3
4
5
6
7
8
$combinations = array();
foreach($filterParams['processor'] as $value1) {
    foreach($filterParams['opslagHDD'] as $value2) {
        foreach($filterParams['scherm'] as $value3) {
            $combinations[] = array('processor' => $value1, 'opslagHDD' => $value2, 'scherm' => $value3);
        }
    }
}


Resulterend in volgende array met unieke combinaties:
PHP:
1
2
3
4
5
6
7
8
Array ( [processor] => Intel Core i3 [opslagHDD] => 500 [scherm] => 15.6 )
Array ( [processor] => Intel Core i3 [opslagHDD] => 500 [scherm] => 17.3 )
Array ( [processor] => Intel Core i3 [opslagHDD] => 1000 [scherm] => 15.6 )
Array ( [processor] => Intel Core i3 [opslagHDD] => 1000 [scherm] => 17.3 )
Array ( [processor] => Intel Core i5 [opslagHDD] => 500 [scherm] => 15.6 )
Array ( [processor] => Intel Core i5 [opslagHDD] => 500 [scherm] => 17.3 )
Array ( [processor] => Intel Core i5 [opslagHDD] => 1000 [scherm] => 15.6 )
Array ( [processor] => Intel Core i5 [opslagHDD] => 1000 [scherm] => 17.3 )


De code werkt prima en doet wat ik wil, namelijk alle unieke combinaties bijhouden.

Natuurlijk is het zo dat de filterParams array x-aantal verschillende filters kan bevatten, zoals: 'touchscreen', 'opslagSSD', 'geheugen', etc ...

Helaas ben ik slachtoffer van mijn slechte concentratie om dit treffelijk uit te werken :|
Via Google vond ik heel wat code terug met kernwoorden als Cartesian, permutations etc.. maar het is me net iets te ingewikkeld geworden.

Iemand die me hierbij kan assisteren?

Waarvoor dank !

Acties:
  • +1 Henk 'm!

  • HamuNaptra
  • Registratie: April 2002
  • Niet online
Dit noemt men faceted search of faceted navigation. Misschien helpen deze termen met het zoeken naar je oplossing.

Acties:
  • 0 Henk 'm!

  • mboshoven
  • Registratie: December 2009
  • Laatst online: 16-06 21:21

mboshoven

Hoofd

Even snel iets in elkaar geflanst. Hoop dat je er wat aan hebt :)

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$filterParams = array(
    'processor' => array('Intel Core i3', 'Intel Core i5'),
    'opslagHDD' => array('500', '1000'),
    'scherm' => array('15.6', '17.3')
);

$sql = "SELECT * FROM Artikels WHERE ";
$filter = array();

foreach($filterParams as $key => $values) {
    $regex = implode('|', $values);
    $filter[] = $key . " REGEXP '" . $regex . "'";
}

$sql .= implode(" && ", $filter);

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
@HamuNaptra: Zo zie ik nog maar eens dat de juiste kernwoorden een must zijn. Thank you !

Uw query :
code:
1
WHERE processor REGEXP 'Intel Core i3|Intel Core i5' && opslagHDD REGEXP '500|1000' && scherm REGEXP '15.6|17.3'


@mboshoven: Simplicity is key, mooie code. Zou zelf nooit gedacht hebben aan regular expressions. Ik krijg er nogal snel grijze haren van zonder resultaat. Ik ga er zeker eens mee aan de slag.

De query die ik momenteel hard-gecodeerd heb, ziet er eerder als volgt uit :

Mijn query :
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
WHERE
     (processor = 'Intel Core i3' && && opslag = '500' && scherm = '15.6')
     OR
     (processor = 'Intel Core i3' && && opslag = '500' && scherm = '17.3')
     OR
     (processor = 'Intel Core i3' && && opslag = '1000' && scherm = '15.6')
     OR
     (processor = 'Intel Core i3' && && opslag = '1000' && scherm = '17.3')
     OR
     (processor = 'Intel Core i5' && && opslag = '500' && scherm = '15.6')
     OR
     (processor = 'Intel Core i5' && && opslag = '500' && scherm = '17.3')
     OR
     (processor = 'Intel Core i5' && && opslag = '1000' && scherm = '15.6')
     OR
     (processor = 'Intel Core i5' && && opslag = '1000' && scherm = '17.3')


Als ik zo vergelijk, toch wel omslachtig :X

Acties:
  • +1 Henk 'm!

  • Merethil
  • Registratie: December 2008
  • Laatst online: 09:57
Wat is er precies mis met het gebruik van "IN ()" ipv REGEXP? Ik vind het om heel eerlijk te zijn nogal een ranzige oplossing.

Je zou het zo kunnen schrijven:

SQL:
1
2
3
4
SELECT * FROM Artikels 
    WHERE processor IN ('Intel Core i3', 'Intel Core i5') 
    AND opslag IN ('500', '1000')
    AND scherm IN ('15.6', '17.3')


Wat je zou kunnen doen als opslag gewoon een integer is, is dit:

SQL:
1
WHERE opslag >= 500


Daardoor krijg je alles groter dan 500. Eventueel kan je ook BETWEEN gebruiken (of tweemaal opslag meenemen in je where):

SQL:
1
2
3
4
5
WHERE opslag >= 500 AND opslag <= 1000

---

WHERE opslag BETWEEN 500 AND 1000


Lijkt mij een betere oplossing dan regexes, ook een stuk leesbaarder lijkt me.

Acties:
  • 0 Henk 'm!

  • Guillome
  • Registratie: Januari 2001
  • Niet online

Guillome

test

Wat Merethil zegt! Absoluut.

Aanvulling:
Zoek niet op lange/tekstuele strings, maar geef elke filteritem een Id (int) of eventueel code (varchar met index) in de database. Gebruik deze.

Tabel Filters (processor, opslag etc)
Tabel FilterItems (I3, I5 etc) gekoppeld aan filters (idFilter)
Tabel relArtikelFilteritem met de koppeling. Eventueel een redudance kolom met FilterItemCode als je liever met codes werkt ipv Ids (mogelijk voor netheid in je url).

Mocht je geen losse tabellen willen gebruiken maar gewoon in je Artikel-tabel de kolommen 'processor' ed hebt (afrader!), dan zou ik daar ook echt met codes/Ids werken en index op die kolommen doen. Maar echt, ga voor de 1e optie.

[ Voor 29% gewijzigd door Guillome op 17-11-2015 16:46 ]

If then else matters! - I5 12600KF, Asus Tuf GT501, Asus Tuf OC 3080, Asus Tuf Gaming H670 Pro, 48GB, Corsair RM850X PSU, SN850 1TB, Arctic Liquid Freezer 280, ASUS RT-AX1800U router


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
@Merethil: eveneens bedankt voor de hulp. Die code lijkt me wel wat ! Ik zocht het blijkbaar way to far 8)7

@Guillome: Ik denk dat mijn database structuur op zich wel goed zit.

Deze gaat als volgt:
- tabel artikel (alle artikelen)
- tabel specs (alle mogelijkse speficaties)
- tabel artikelspecs (tussentabel met alle artikel specificaties)
- tabel filter (alle mogelijke filters, relatie met tabel specs dmv specs Id)

De filter query zal er uiteindelijk als volgt moeten uitzien als ik het goed interpreteer...
PHP:
1
2
3
4
5
6
7
8
9
10
INNER JOIN artikelspecs AS asp ON asp.artikelId = a.id
INNER JOIN filter AS f ON f.filterSpecsId = asp.specsId
...
(f.filterParamNaam = "processor" AND asp.value IN ('Intel Core i3', 'Intel Core i5'))
OR
(f.filterParamNaam = "opslagHDD" AND asp.value IN ('500', '1000'))
OR
(f.filterParamNaam = "scherm" AND asp.value IN ('15.6', '17.3'))
....
HAVING filterCount = 3


Vanavond uitproberen ... :)

Acties:
  • 0 Henk 'm!

  • Guillome
  • Registratie: Januari 2001
  • Niet online

Guillome

test

Ja je structuur is goed, dat is wat ik ook noemde :) Heb je indexen op asp.value? Of hoe groot is die tabel?

If then else matters! - I5 12600KF, Asus Tuf GT501, Asus Tuf OC 3080, Asus Tuf Gaming H670 Pro, 48GB, Corsair RM850X PSU, SN850 1TB, Arctic Liquid Freezer 280, ASUS RT-AX1800U router


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
PhpMyAdmin geeft aan: 3865 totaal aantal records (tabel: artikelspecs)

Een tabel die snel in aantal zal stijgen eens producten toegevoegd worden. Het is namelijk zo dat ik voor m'n webshop gebruik maak van een Icecat koppeling om artikel specificaties in te lezen. Deze hou ik allemaal bij in mijn eigen database. Ik importeer als het ware alle Icecat specs en specsgroep data in m'n artikelspecs tabel.

Indexes gebruik ik tot op heden niet. Staat op m'n TO-DO list om me eens in te verdiepen. Ik ken vaag de achtergrond om data rows sneller te vinden zonder te hoeven starten vanaf row 1 maar ik heb dit tot op heden nog niet benut. Bad practice ...

Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Verwijderd schreef op dinsdag 17 november 2015 @ 20:50:
Indexes gebruik ik tot op heden niet. Staat op m'n TO-DO list om me eens in te verdiepen. Ik ken vaag de achtergrond om data rows sneller te vinden zonder te hoeven starten vanaf row 1 maar ik heb dit tot op heden nog niet benut. Back practice ...
Zet dat maar bovenaan je lijst dan.
Index = Telefoonboek => Zoek alle Janssens => Simpel klusje *O*
Geen index = Stapel losse kaartjes* in random order => Zoek alle Janssens => Ik spreek je volgend jaar wel weer -O-
* evenveel als er namen in het telefoonboek staan natuurlijk om 't eerlijk te houden ;)

Dat is ook één van de redenen waarom die REGEXP oplossing zo vies is; die maakt 't onmogelijk om van een index gebruik te maken => Traag als dikke poep door een trechter. Los van het redelijk in 't algemeen altijd wel geldende:
Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems. - Jamie Zawinski
;)
mboshoven schreef op dinsdag 17 november 2015 @ 04:36:
Even snel iets in elkaar geflanst. Hoop dat je er wat aan hebt :)
Ondanks dat je bedoeling goed is ben je hier helaas niet bepaald behulpzaam en zien we dergelijke posts doorgaans ook liever niet. Ook hier kan ik weer een spreekwoord tegenaan gooien:
Give a man a fish and feed him for a day. Teach a man how to fish and feed him for a lifetime.
Dus als je 't voortaan een beetje kunt uitleggen en onderbouwen is dat wel zo fijn ;) d:)b

[ Voor 27% gewijzigd door RobIII op 17-11-2015 21:05 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • Marc3l
  • Registratie: December 2005
  • Laatst online: 11-09 19:25
Verwijderd schreef op dinsdag 17 november 2015 @ 17:57:

PHP:
1
2
3
4
5
6
7
8
9
10
INNER JOIN artikelspecs AS asp ON asp.artikelId = a.id
INNER JOIN filter AS f ON f.filterSpecsId = asp.specsId
...
(f.filterParamNaam = "processor" AND asp.value IN ('Intel Core i3', 'Intel Core i5'))
OR
(f.filterParamNaam = "opslagHDD" AND asp.value IN ('500', '1000'))
OR
(f.filterParamNaam = "scherm" AND asp.value IN ('15.6', '17.3'))
....
HAVING filterCount = 3


Vanavond uitproberen ... :)
Hoe toevallig, precies met hetzelfde bezig geweest voor onze shops afgelopen week.

Ik weet niet hoe je de filters wilt gaan gebruiken maar bv wilt uitkomen op een i3 met 500gb HD en een 15,6 scherm dan kom je er niet met OR maar moet je iig met AND aan de slag. Zo uit mijn hoofd ook weer een join voor elke filter groep (processor, geheugen, beeldscherm). Mocht je iets anders voor ogen hebben dan kan je dit volledig negeren :p

Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
@Marc3l: Het is wél degelijk OR die ik nodig heb icm HAVING ;)

Acties:
  • 0 Henk 'm!

  • Marc3l
  • Registratie: December 2005
  • Laatst online: 11-09 19:25
Verwijderd schreef op dinsdag 17 november 2015 @ 21:58:
@Marc3l: Het is wél degelijk OR die ik nodig heb icm HAVING ;)
Aaah excuus zag de combinatie met HAVING over het hoofd.
Al gelukt dus neem ik aan?

[ Voor 5% gewijzigd door Marc3l op 17-11-2015 22:21 ]


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
@Marc3l: ik had er eerst voor gezorgd dat ik SEO vriendelijke URLs kon bekomen in de vorm van /mijn-product.html?filter[opslag]=500GB&filter[scherm]=1920x1080&filter[geheugen]=8GB

Ik lees hierbij alle filters uit, check met DB of die al dan niet gekend zijn en tracht nu gans het boeltje uit te werken met die voor mij nieuwe benaming faceted search.

De SQL query heb ik hardcoded reeds uitgewerkt. Hierbij maak ik gebruik van GROUP BY icm HAVING. Vandaar die OR's ipv AND's.

Acties:
  • 0 Henk 'm!

  • Marc3l
  • Registratie: December 2005
  • Laatst online: 11-09 19:25
Mooi! Bedankt voor je reactie :)

Acties:
  • 0 Henk 'm!

  • ACM
  • Registratie: Januari 2000
  • Niet online

ACM

Software Architect

Werkt hier

Verwijderd schreef op dinsdag 17 november 2015 @ 17:57:
De filter query zal er uiteindelijk als volgt moeten uitzien als ik het goed interpreteer...
[de code]

Vanavond uitproberen ... :)
Je kan ook gewoon meerdere keren de filter-tabel joinen. Afhankelijk van je index-gebruik kan dat efficienter zijn:
SQL:
1
2
3
4
5
6
7
FROM
    artikel a
    JOIN artikelspecs AS asp1 ON asp1.artikelId = a.id AND asp1.value IN ('Intel Core i3', 'Intel Core i5')
    JOIN filter AS f1 ON f1.filterSpecsId = asp1.specsId AND f1.filterParamNaam = "processor" 
    JOIN artikelspecs AS asp2 ON asp2.artikelId = a.id AND asp2.value IN ('15.6', '17.3')
    JOIN filter AS f2 ON f2.filterSpecsId = asp2.specsId AND f2.filterParamNaam = "scherm" 
    ...


Of een wat duidelijker gegroepeerde variant (het woord inner is sowieso overbodig, dat is het standaardtype voor join):
SQL:
1
2
3
4
5
6
7
8
9
10
11
12
13
FROM
    artikel a
    JOIN (artikelspecs AS asp1
        JOIN filter AS f1 ON f1.filterSpecsId = asp1.specsId
                            AND f1.filterParamNaam = "processor"
                            AND asp1.value IN ('Intel Core i3', 'Intel Core i5')
    ) ON asp1.artikelId = a.id
    JOIN (artikelspecs AS asp2
        JOIN filter AS f2 ON f2.filterSpecsId = asp2.specsId
                            AND f2.filterParamNaam = "scherm" 
                            AND asp2.value IN ('15.6', '17.3')
    ) ON asp2.artikelId = a.id
    ...


Ik vermoed dat voor beide varianten het het meest efficient is als je een gecombineerde index hebt op artikelspecs(artikelId, specsId, value) en een op filter(filterParamNaam, filterSpecsId).
Als de specificaties relatief uniek zijn, dan zou juist een op artikelspecs(value, specsId, artikelId) beter kunnen zijn (en dan uiteraard ook die van filter omkeren).

Acties:
  • 0 Henk 'm!

  • Merethil
  • Registratie: December 2008
  • Laatst online: 09:57
ACM schreef op zaterdag 21 november 2015 @ 11:05:
[...]

Of een wat duidelijker gegroepeerde variant (het woord inner is sowieso overbodig, dat is het standaardtype voor join):
Dit hoeft niet per se zo te zijn bij alle DB's, tenzij ze de ANSI-standaard volgen. Maar inderdaad, MySQL, mssql, sqlite en h2 bijvoorbeeld zijn standaard inner joins als je join schrijft.
Pagina: 1