1+ <?php
2+ namespace Tigo \Recommendation \Collaborative ;
3+
4+ use Tigo \Recommendation \Collaborative \Base ;
5+ use Tigo \Recommendation \Traits \OperationTrait ;
6+
7+ /**
8+ * Collaborative filtering [recommendation algorithm EuclideanCollaborative].
9+ * Using the Euclidean distance formula and applying a weighted average.
10+ *
11+ * @author Tiago A C Pereira <[email protected] > 12+ */
13+ class EuclideanCollaborative extends Base
14+ {
15+
16+ use OperationTrait;
17+
18+ /**
19+ * Get recommend.
20+ * @param array $table
21+ * @param mixed $user
22+ * @param mixed $score
23+ *
24+ * @return array
25+ */
26+ public function recommend ($ table , $ user , $ score = 0 )
27+ {
28+ $ data = $ this ->average ($ table , $ user , $ score );
29+ return $ this ->filterRating ($ data );
30+ }
31+
32+ /**
33+ * Get users who rated the same product.
34+ * @param array $table
35+ * @param mixed $user
36+ * @param mixed $score
37+ *
38+ * @return array
39+ */
40+ private function userRated ($ table , $ user , $ score )
41+ {
42+ $ this ->ratedProduct ($ table , $ user );
43+ $ rated = []; //get user rating
44+ foreach ($ this ->product as $ myProduct ){
45+ foreach ($ this ->other as $ item ){
46+ if ($ myProduct [self ::PRODUCT_ID ] == $ item [self ::PRODUCT_ID ]){
47+ if ($ myProduct [self ::SCORE ] >= $ score && $ item [self ::SCORE ] >= $ score ){
48+ if (!in_array ($ item [self ::USER_ID ],$ rated )) // check if user already exists
49+ $ rated [] = $ item [self ::USER_ID ]; //add user
50+ }
51+ }
52+ }
53+ }
54+ return $ rated ;
55+ }
56+
57+ /**
58+ * Get operation|using part of the euclidean formula (p-q).
59+ * @param array $table
60+ * @param mixed $user
61+ * @param mixed $score
62+ *
63+ * @return array
64+ */
65+ private function operation ($ table , $ user , $ score )
66+ {
67+ $ rated = $ this ->userRated ($ table , $ user , $ score );
68+ $ data = [];
69+ foreach ($ this ->product as $ myProduct ){
70+ for ($ i = 0 ; $ i < count ($ rated ) ; $ i ++){
71+ foreach ($ this ->other as $ itemOther ){
72+ if ($ itemOther [self ::USER_ID ] == $ rated [$ i ] &&
73+ $ myProduct [self ::PRODUCT_ID ] == $ itemOther [self ::PRODUCT_ID ]
74+ && $ myProduct [self ::SCORE ] >= $ score && $ itemOther [self ::SCORE ] >= $ score ){
75+ $ data [$ itemOther [self ::USER_ID ]][$ myProduct [self ::PRODUCT_ID ]] = abs ($ itemOther [self ::SCORE ] - $ myProduct [self ::SCORE ]);
76+ }
77+ }
78+ }
79+ }
80+ return $ data ;
81+ }
82+
83+ /**
84+ * Using the metric distance formula and convert value to percentage.
85+ * @param array $table
86+ * @param mixed $user
87+ * @param mixed $score
88+ *
89+ * @return array
90+ */
91+ private function metricDistance ($ table , $ user , $ score )
92+ {
93+ $ data = $ this ->operation ($ table , $ user , $ score );
94+ $ element = [];
95+ foreach ($ data as $ item ){
96+ foreach ($ item as $ value ){
97+ if (!isset ($ element [key ($ data )]))
98+ $ element [key ($ data )] = 0 ;
99+ $ element [key ($ data )] += pow ($ value ,2 );
100+ }
101+ $ similarity = round (sqrt ($ element [key ($ data )]),2 ); //similarity rate
102+ $ element [key ($ data )] = round (1 /(1 + $ similarity ), 2 ); //convert value
103+ next ($ data );
104+ }
105+ return $ element ;
106+ }
107+
108+
109+ /**
110+ * Get weighted average.
111+ * @param array $table
112+ * @param mixed $user
113+ * @param mixed $score
114+ *
115+ * @return array
116+ */
117+ private function average ($ table , $ user , $ score )
118+ {
119+ $ metric = $ this ->metricDistance ($ table , $ user , $ score );
120+ $ similarity = [];
121+ $ element = [];
122+ foreach ($ metric as $ itemMetric ){
123+ foreach ($ this ->other as $ itemOther ){
124+ if ($ itemOther [self ::USER_ID ] == key ($ metric ) && $ itemOther [self ::SCORE ] >= $ score ){
125+ if (!isset ($ element [$ itemOther [self ::PRODUCT_ID ]])){
126+ $ element [$ itemOther [self ::PRODUCT_ID ]] = 0 ;
127+ $ similarity [$ itemOther [self ::PRODUCT_ID ]] = 0 ;
128+ }
129+ $ element [$ itemOther [self ::PRODUCT_ID ]] += ($ itemMetric * $ itemOther [self ::SCORE ]);
130+ $ similarity [$ itemOther [self ::PRODUCT_ID ]] += $ itemMetric ;
131+ }
132+ }
133+ next ($ metric );
134+ }
135+ return $ this ->division ($ element ,$ similarity );
136+ }
137+
138+ }
0 commit comments