1 package org.musicontroller.songselection;
2
3 import java.util.Date;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.Iterator;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Set;
10
11 import org.apache.log4j.Logger;
12 import org.musicontroller.core.Band;
13 import org.musicontroller.core.Keyword;
14 import org.musicontroller.core.Keywordbag;
15 import org.musicontroller.core.Song;
16 import org.musicontroller.dao.Dao;
17 import org.musicontroller.security.IUser;
18 import org.varienaja.util.DateTools;
19 import org.varienaja.util.LRUMap;
20
21 public class AdvancedRandomSongSelector implements SongSelector {
22 private static final Logger log = Logger.getLogger(AdvancedRandomSongSelector.class);
23
24
25
26
27 private static Map<String,Integer> SCORES = new LRUMap<String,Integer>("KeywordSimilarityScores",1000);
28
29 private Dao _dao;
30
31
32
33
34 private Map<Long,PlaySkipContainer> _bandPopularity;
35
36
37
38
39 private Map<Long,PlaySkipContainer> _keywordPopularity;
40
41
42
43
44
45
46
47
48
49 private Song selectSongInternal(List<Song> candidates, IUser user, LastPlayedContainer lpc) {
50 Set<Song> best = new HashSet<Song>();
51 Song bestsong = null;
52 int maxscore=Integer.MIN_VALUE;
53
54
55 for (Song song : candidates) {
56 int score = calculateScore(song,lpc,user);
57 if (score>=maxscore) {
58 if (score>maxscore) {
59 best.clear();
60 maxscore=score;
61 }
62 best.add(song);
63 }
64 }
65
66
67
68 if (best.size()==1) {
69 bestsong = best.iterator().next();
70 } else {
71 maxscore=Integer.MIN_VALUE;
72 for (Song song : best) {
73 int songscore = calculateEventScore(user,song);
74 if (songscore>maxscore) {
75 maxscore = songscore;
76 bestsong = song;
77 }
78 }
79 }
80 if (bestsong==null) {
81 return null;
82 }
83 if (log.isInfoEnabled()) {
84 Date lastYear = DateTools.lastYear();
85 log.info("Song selected: "+
86 bestsong.getBand().getName()+
87 " - "+bestsong.getName()+
88 " P:"+bestsong.getPlaycount(user,lastYear,null)+
89 " R:"+bestsong.getRequestcount(user,lastYear,null)+
90 " S:"+bestsong.getSkipcount(user,lastYear,null)
91 );
92 }
93
94 return bestsong;
95 }
96
97
98
99
100
101 public Song selectSong(List<Song> candidates, LastPlayedContainer lpc, IUser user) {
102 if(candidates==null) {
103 return null;
104 }
105 if(lpc==null) {
106 lpc = new LastPlayedContainer();
107 }
108 Song result = selectSongInternal(candidates,user,lpc);
109 return result;
110 }
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 private int calculateScore(Song song, LastPlayedContainer lpc,IUser user) {
127 StringBuilder sb = new StringBuilder();
128 int score=0;
129 sb.append(song.getBand().getName());
130 sb.append(" - ");
131 sb.append(song.getName());
132 sb.append(" ");
133
134 int playscore = calculateEventScore(user,song);
135 score = playscore;
136 if(playscore!=0) {
137 sb.append("(play:");
138 sb.append(playscore);
139 sb.append(")");
140 }
141
142
143
144 int skipscore = -1 * song.getSkipcount(user,DateTools.lastYear(),null);
145 score += skipscore;
146 if(skipscore!=0) {
147 sb.append("(skip:");
148 sb.append(skipscore);
149 sb.append(")");
150 }
151
152 int kwscore = calculateKeywordScore(song, lpc, sb);
153 score += kwscore;
154
155 int bandPenalty = getPenalty(song.getBand());
156 score += bandPenalty;
157 if(bandPenalty!=0) {
158 sb.append("(bandPenalty:");
159 sb.append(bandPenalty);
160 sb.append(")");
161 }
162
163 sb.append("[");
164 sb.append(score);
165 sb.append("]");
166 log.debug(sb.toString());
167 return score;
168 }
169
170
171
172
173
174
175
176
177
178
179
180 private int calculateKeywordScore(Song song, LastPlayedContainer lpc, StringBuilder sb) {
181 int totalkwscore = 0;
182 Keywordbag songKeywords = song.getKeywordbag();
183 if(songKeywords!=null) {
184
185
186
187
188
189 int modifier=4-lpc.getSize();
190 Iterator<LastPlayedEntry> it = lpc.getIterator();
191 while (it.hasNext()) {
192 sb.append(" (");
193 sb.append(modifier);
194 sb.append(" * ");
195 LastPlayedEntry lastsongentry = it.next();
196
197 Song lastsong = getDao().getSongById(lastsongentry.getSongid());
198 if (lastsong!=null) {
199 boolean scoreNegative = false;
200
201 int kwscore = kwbSimilarityScore(songKeywords, lastsong.getKeywordbag(), sb);
202
203 if (kwscore>0) {
204 scoreNegative = (!lastsongentry.isPlayedEntirely() || song.getBand().equals(lastsong.getBand()));
205 }
206 sb.append(scoreNegative ? "!" : "");
207 sb.append(kwscore);
208 sb.append(scoreNegative ? "!" : "");
209
210
211 kwscore *= modifier;
212 if (scoreNegative && kwscore>0) {
213 totalkwscore -= kwscore;
214 } else {
215 totalkwscore += kwscore;
216 }
217 } else {
218 log.debug("Detected SongID in lastplayedcontainer for " +
219 "which there is no song in the database. Was is " +
220 "just deleted or merged? The id is: " + lastsongentry.getSongid());
221 }
222 sb.append(") ");
223 sb.append(it.hasNext() ? "+" : "= ");
224 modifier++;
225 }
226 sb.append(totalkwscore);
227 sb.append(" sqrt(sqrt(" + totalkwscore + ")) = ");
228
229 if(totalkwscore<0) {
230 totalkwscore = -1 * isqrt(isqrt(-1 * totalkwscore));
231 } else {
232 totalkwscore = isqrt(isqrt(totalkwscore));
233 }
234 }
235 sb.append(totalkwscore);
236 return totalkwscore;
237 }
238
239
240
241
242
243
244
245
246
247 private int calculateEventScore(IUser user, Song song) {
248 int nplays = song.getPopularity(user,DateTools.lastYear(),null);
249 int evScore = nplays == 0 ? 1 : nplays < 0 ? -1*isqrt(-1 * nplays) : isqrt(nplays);
250 return evScore;
251 }
252
253
254
255
256
257
258
259 private static int next(int n, int i) {
260 return (n + i/n) >> 1;
261 }
262
263
264
265
266
267
268
269
270
271
272 private static int isqrt(int number) {
273 if(number<0) {
274 throw new IllegalArgumentException("Cannot take the sqare root of a negative integer.");
275 }
276 int n = 1;
277 int n1 = next(n, number);
278
279 while(Math.abs(n1 - n) > 1) {
280 n = n1;
281 n1 = next(n, number);
282 }
283 while((n1*n1) > number) {
284 n1 -= 1;
285 }
286 return n1;
287 }
288
289
290
291
292
293
294
295
296 public void setDao(Dao dao) {
297 _dao = dao;
298 }
299
300 public Dao getDao() {
301 return _dao;
302 }
303
304 public AdvancedRandomSongSelector() {
305 _bandPopularity = new HashMap<Long,PlaySkipContainer>();
306 _keywordPopularity = new HashMap<Long,PlaySkipContainer>();
307 }
308
309 static class PlaySkipContainer {
310 private int _playcount;
311 private int _skipcount;
312
313 public PlaySkipContainer(int playcount,int skipcount) {
314 _playcount = playcount;
315 _skipcount = skipcount;
316 }
317
318
319
320
321
322
323
324
325 public int getPopularity() {
326 if (_skipcount<3) return 0;
327 if (_playcount>=_skipcount) return 0;
328 return _playcount-_skipcount;
329 }
330
331 public void addPlay() {
332 _playcount++;
333 }
334
335 public void addSkip() {
336 _skipcount++;
337 }
338
339 public String toString() {
340 return Integer.toString(getPopularity());
341 }
342 }
343
344
345
346
347
348
349 public int getPenalty(Band band) {
350 PlaySkipContainer psc = _bandPopularity.get(band.getId());
351 if (psc==null) return 0;
352
353 return psc.getPopularity();
354 }
355
356
357
358
359
360
361 public int getPenalty(Keyword keyword) {
362 PlaySkipContainer psc = _keywordPopularity.get(keyword.getId());
363 if (psc==null) return 0;
364
365 return psc.getPopularity();
366 }
367
368
369
370
371
372
373
374
375
376
377
378
379 public void addBandKnowledge(Long bandid, int playcount, int skipcount) {
380 if (playcount>0 || skipcount>0) {
381 PlaySkipContainer psc = new PlaySkipContainer(playcount,skipcount);
382 _bandPopularity.put(bandid,psc);
383 }
384 }
385
386
387
388
389
390
391
392
393
394
395
396
397 public void addKeywordKnowledge(Long keywordid, int playcount, int skipcount) {
398 if (playcount>0 && skipcount>0) {
399 PlaySkipContainer psc = new PlaySkipContainer(playcount,skipcount);
400 _keywordPopularity.put(keywordid,psc);
401 }
402 }
403
404
405
406
407
408
409 public void addBandPlay(long bandid) {
410 PlaySkipContainer psc = _bandPopularity.get(bandid);
411 if (psc==null) {
412 psc = new PlaySkipContainer(1,0);
413 _bandPopularity.put(bandid,psc);
414 } else {
415 psc.addPlay();
416 }
417 }
418
419
420
421
422
423
424 public void addBandSkip(long bandid) {
425 PlaySkipContainer psc = _bandPopularity.get(bandid);
426 if (psc==null) {
427 psc = new PlaySkipContainer(0,1);
428 _bandPopularity.put(bandid,psc);
429 } else {
430 psc.addSkip();
431 }
432 }
433
434
435
436
437
438
439 public void addKeywordPlay(long keywordid) {
440 PlaySkipContainer psc = _keywordPopularity.get(keywordid);
441 if (psc==null) {
442 psc = new PlaySkipContainer(1,0);
443 _keywordPopularity.put(keywordid,psc);
444 } else {
445 psc.addPlay();
446 }
447 }
448
449
450
451
452
453
454 public void addKeywordSkip(long keywordid) {
455 PlaySkipContainer psc = _keywordPopularity.get(keywordid);
456 if (psc==null) {
457 psc = new PlaySkipContainer(0,1);
458 _keywordPopularity.put(keywordid,psc);
459 } else {
460 psc.addSkip();
461 }
462 }
463
464
465
466
467
468
469
470
471
472
473
474
475 private int kwbSimilarityScore(Keywordbag kwb1, Keywordbag kwb2, StringBuilder sb) {
476
477 StringBuilder hashkeyBuilder = new StringBuilder();
478 if(kwb1==null) {
479 if(kwb2==null) {
480 hashkeyBuilder.append("null,null");
481 } else {
482 hashkeyBuilder.append("null,");
483 hashkeyBuilder.append(kwb2.getId());
484 }
485 } else if (kwb2==null) {
486 hashkeyBuilder.append(kwb1.getId());
487 hashkeyBuilder.append(",null");
488 } else if (kwb1.getId()<kwb2.getId()) {
489 hashkeyBuilder.append(kwb1.getId());
490 hashkeyBuilder.append(",");
491 hashkeyBuilder.append(kwb2.getId());
492 } else {
493 hashkeyBuilder.append(kwb2.getId());
494 hashkeyBuilder.append(",");
495 hashkeyBuilder.append(kwb1.getId());
496 }
497 String key = hashkeyBuilder.toString();
498 Integer score = SCORES.get(key);
499
500 if (score!=null) {
501 return score;
502 }
503
504
505 int kwscore = 0;
506 Set<Keyword> alreadyCounted = new HashSet<Keyword>();
507 int kwcounter = 0;
508 for (Keyword kw : kwb1.getKeywords()) {
509 kwcounter++;
510 if(alreadyCounted.contains(kw)) {
511 continue;
512 }
513 kwscore += getPenalty(kw);
514 if (kwb2!=null) {
515 for(Keyword kwlast : kwb2.getKeywords()) {
516 if(alreadyCounted.contains(kwlast)) {
517 continue;
518 }
519 int similarity = kw.similarityScore(kwlast,sb);
520 if(similarity>0) {
521 sb.append("(sim:");
522 sb.append(similarity);
523 sb.append(")");
524 while(kwlast.getParent()!=null && !kwlast.getParent().equals(kwlast)) {
525 Keyword parent = kwlast.getParent();
526 sb.append("(ac:");
527 sb.append(parent);
528 sb.append(")");
529 alreadyCounted.add(parent);
530 kwlast = kwlast.getParent();
531 }
532 }
533 kwscore+=similarity;
534 }
535 }
536 }
537
538 if (kwcounter>0) {
539 kwscore = kwscore / kwcounter;
540 }
541
542 SCORES.put(key, kwscore);
543 return kwscore;
544 }
545
546
547
548
549
550 }