1313import com .back .web7_9_codecrete_be .global .error .exception .BusinessException ;
1414import lombok .RequiredArgsConstructor ;
1515import lombok .extern .slf4j .Slf4j ;
16+ import org .springframework .data .domain .PageRequest ;
1617import org .springframework .data .domain .Pageable ;
1718import org .springframework .stereotype .Service ;
1819import org .springframework .transaction .annotation .Transactional ;
@@ -302,9 +303,7 @@ public List<ConcertItem> recommendSimilarConcerts(long concertId) {
302303 // 유사한 제목을 가지는 공연 추천
303304 public List <ConcertItem > recommendSimilarTitleConcerts (long concertId ) {
304305 Concert concert = findConcertByConcertId (concertId );
305- String name = concert .getName ();
306- String match = "[^ㄱ-ㅎㅏ-ㅣ가-힣a-zA-Z0-9\\ s]" ;
307- name = name .replaceAll (match , "" );
306+ String name = simplifyKeyword (concert .getName ());
308307 log .info ("name: " + name );
309308 String [] words = name .split (" " );
310309 List <AutoCompleteItem > result = new ArrayList <>();
@@ -327,6 +326,78 @@ public List<ConcertItem> recommendSimilarTitleConcerts(long concertId) {
327326 return concertItemList ;
328327 }
329328
329+ // 좋아요 한 제목에서 중복으로 나타나는 단어에 가중치 부여 후 자카드 유사도에 가점 부여하여 정렬 후 공연 추천
330+ public List <ConcertItem > concertsRecommendByLike (User user ){
331+ Pageable pageable = PageRequest .of (0 , 100 );
332+ List <ConcertItem > likeList = concertRepository .getLikedConcertsList (pageable ,user .getId ());
333+ if (likeList .isEmpty ()) return new ArrayList <>(); // 좋아요 한 공연이 없을 경우 빈 공연 반환
334+ Map <String , WeightedBits > weightedBitsMap = new HashMap <>();
335+ Set <Long > idSet = new HashSet <>();
336+ for (ConcertItem item : likeList ){
337+ idSet .add (item .getId ());
338+ String name = item .getName ();
339+ String simpleName = simplifyKeyword (name );
340+ String [] words = simpleName .split (" " );
341+
342+ for (String word : words ) { // 단어별로 가중치 적용
343+ WeightedBits weightedBits = weightedBitsMap .getOrDefault (word , new WeightedBits (word ,0 ));
344+ weightedBits .plusWeight ();
345+ weightedBitsMap .put (word , weightedBits );
346+ }
347+ }
348+
349+ List <AutoCompleteItem > result = new ArrayList <>();
350+ for (String word : weightedBitsMap .keySet ()) {
351+ if (word .isEmpty ()) continue ;
352+ log .info ("word: " + word );
353+ result .addAll (concertSearchRedisTemplate .getAutoCompleteWord (word ,0 ,6 ));
354+ }
355+
356+ Set <Long > resultIdSet = new HashSet <>();
357+ for (AutoCompleteItem item : result ) {
358+ if (idSet .contains (item .getId ())) continue ; // 찜한 목록 제거
359+ resultIdSet .add (item .getId ()); // 중복 제거
360+ }
361+
362+ List <Long > idList = new ArrayList <>();
363+ for (Long id : resultIdSet ) {
364+ idList .add (id );
365+ }
366+
367+ List <ConcertItem > concertItemList = concertRepository .getConcertItemsInIdList (idList ,LocalDate .now ());
368+ concertItemList .sort (Comparator .comparingDouble (
369+ ci -> jaccardSimilarityWithWeight (weightedBitsMap ,ci .getName ().split (" " ))
370+ ));
371+ return concertItemList ;
372+ }
373+
374+ private double jaccardSimilarityWithWeight (Map <String ,WeightedBits > weightedBitsMap , String [] words ) {
375+ int intersection =0 ;
376+ for (String word : words ) {
377+ if (word .isEmpty ()) continue ;
378+ WeightedBits weightedBits = weightedBitsMap .get (word );
379+ if (weightedBits == null ) continue ;
380+ intersection += weightedBits .weight ;
381+ }
382+ int union = weightedBitsMap .size () + words .length ;
383+ return (double )union /intersection ;
384+ }
385+
386+ private class WeightedBits {
387+ String bit ;
388+ int weight ;
389+
390+ public WeightedBits (String bit , int weight ) {
391+ this .bit = bit ;
392+ this .weight = weight ;
393+ }
394+
395+ public WeightedBits plusWeight (){
396+ this .weight ++;
397+ return this ;
398+ }
399+ }
400+
330401 private double jaccardSimilarity (String [] origin , String [] target ) {
331402 Set <String > union = new HashSet <>();
332403 Set <String > intersection = new HashSet <>();
@@ -342,6 +413,13 @@ private double jaccardSimilarity(String[] origin , String[] target) {
342413 return (double ) union .size () / intersection .size ();
343414 }
344415
416+ // 특수 문자를 제거합니다.
417+ private static String simplifyKeyword (String name ) {
418+ String match = "[^ㄱ-ㅎㅏ-ㅣ가-힣a-zA-Z0-9\\ s]" ;
419+ name = name .replaceAll (match , "" );
420+ return name ;
421+ }
422+
345423 @ Transactional (readOnly = true )
346424 public void validateConcertExists (Long concertId ) {
347425 concertRepository .findById (concertId )
0 commit comments