[プログラマ]レポート結果の取得(Java)


問題の説明


新入社員の武志氏は、掲示板の不良ユーザーを通報し、メールで処理結果を送信するシステムを開発しようとした.ムーチが開発するシステムには、次のものがあります.
  • プレイヤー1人につき1人のプレイヤーを一度に申告できる.
  • 届出回数に制限はありません.異なるプレイヤーを通報し続けることができます.
  • 1人のプレイヤーは複数回の申告が可能であるが、同一プレイヤーに対する申告回数は1回とする.
  • k回以上申告したプレイヤーは掲示板の使用を停止し、そのプレイヤーを申告した全てのユーザにメールで停止事実を送信する.
  • ユーザーが申告したすべての内容を収集し、最後に掲示板の使用を一度に停止し、同時に停止メールを送信する.
  • 以下に、完全なユーザリストが[「muzi」、「frodo」、「apeach」、「neo」、k=2(すなわち、2回以上通報された場合は使用を停止する)の例を示す.
    ユーザIDユーザが申告したIDは、「Muzi」「Frodo」「Muzi」「Frodo」が申告したことを示している.「apeach」「frodo」「apeach」「frodo」が「frodo」を報告した.「frodo」「neo」「frodo」は「neo」を報告した.「Muzi」「neo」「Muzi」が「neo」を通報「apeach」「muzi」「apeach」「muzi」が「muzi」を通報
    各ユーザーが通報された回数は以下の通りです.
    ユーザIDが申告された回数「muzi」1「frodo」2「apeach」0「neo」2
    上記の例では、2回以上通報された「frodo」と「neo」の掲示板は使用を停止します.この場合、ユーザーごとに申告したIDと無効なIDを以下のように整理することができます.
    プレイヤーIDユーザーが申告したID停止のIDには「Muzi」「frodo」「neo」「frodo」「neo」「frodo」「neo」「apeach」「Muzi」「frodo」「neo」「neo」がありません
    したがって、「muzi」には処理結果メールが2回、「frodo」と「apeach」にはそれぞれ処理結果メールが1回受信されます.
    ユーザIDを含む文字列配列idリスト、ユーザごとに申告されたユーザID情報を含む文字列配列レポート、停止を基準とした申告回数kをパラメータとする場合は、ユーザごとに解決関数を1つ作成し、処理結果におけるメールの受信回数を列に記録して返してください.

    せいげんじょうけん

  • 2≦idリストの長さ≦1000
  • 1≦idリストの元素長≦10
  • idリストの要素は、ユーザidを表す文字列であり、アルファベット小文字のみからなる.
  • idリストに同じIDが重複していない.
  • 1≦レポート長≦200000
  • 3≦reportの元素長≦21
  • reportの要素は「ユーザidが申告するid」形式の文字列である.
  • 例えば、「Muzi Frodo」は「Muzi」が「Frodo」を通報したことを意味する.
  • idはアルファベット小文字のみからなる.
  • ユーザーIDと申告したidは1つのスペースしかありません.
  • 自己申告していない場合.
  • 1≦k≦200、kは自然数.
  • 返される配列は、idリストのid順にユーザごとに受信した結果メール数をリストする.
  • I/O例


    id_listreportkresult["muzi", "frodo", "apeach", "neo"]["muzi frodo","apeach frodo","frodo neo","muzi neo","apeach muzi"]2[2,1,1,0]["con", "ryan"]["ryan con", "ryan con", "ryan con", "ryan con"]3[0,0]

    I/O例説明

  • I/O例#1
    問題の例.
  • I/O例#2
    「ryan」は「con」を4回報告したが、与えられた条件により、1人のプレイヤーが同じプレイヤーを複数回通報した場合、1回の通報回数に処理される.そのため、「con」は一度通報された.3回以上通報されていないユーザーは、「con」と「ryan」は結果メールを受信しません.したがって、[0,0]を返します.
  • 時間制限ガイド

  • 精度テスト:10秒
  • 私の答え


    ReportedCountInfoMapは、通報者が誰を通報したか、通報された人が何度通報されたかを保存するReportedCountInfoMapを発表した.
    ReportInfoMapは、同一ユーザの通報回数を1回処理するため、重複処理のためにSetとして保存する.ReportedCountInfoMapは、通報された人が停止基準以上になった場合、そのユーザを通報した通報者に停止メールを送るため、通報回数を保存する方式で行うとしている.
    テストケースはすべて合格したが,既に申告したプレイヤーを申告者が再度申告する場合,処理が乱れているようで,改めて修正した.

    受精前

    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.HashSet;
    
    class Solution {
        public int[] solution(String[] idList, String[] report, int k){
            // @param  idList : 이용자의 ID를 담은 배열.
            // @param  report : 신고한 이용자와 신고당한 이용자의 정보를 담은 배열. ex) "a b",.. -> a가 b를 신고
            // @param  k      : 신고 횟수에 따른 정지 기준 정수값.
            // @return answer : id_list에 담긴 id 순서대로 각 유저가 받은 결과 메일 배열.
          int[] answer = new int[idList.length];
          HashMap<String, HashSet<String>> reporterInfoMap = new HashMap<>();
          HashMap<String, Integer> reportedCountInfoMap = new HashMap<>();
            
          for(String reportInfo : report){
              String reporter = reportInfo.split(" ")[0];  // 신고 한 사람
              String reported = reportInfo.split(" ")[1];  // 신고 당한 사람
              boolean flag = false;
                
              if(reporterInfoMap.containsKey(reporter)){
                  if(reporterInfoMap.get(reporter).contains(reported)){
                      // 이미 신고한 유저를 또 신고했을 경우
                      flag = true;
                  } else {
                      reporterInfoMap.get(reporter).add(reported);    
                  }
              } else {
                  reporterInfoMap.put(reporter, new HashSet<String>(){{
                      add(reported);
                  }});
              }
                
              if (flag) {
                  continue;
              } else if (reportedCountInfoMap.containsKey(reported)){
                  reportedCountInfoMap.put(reported, reportedCountInfoMap.get(reported)+1);
              } else {
                  reportedCountInfoMap.put(reported, 1);
              }
          }
            
          for (String reported : reportedCountInfoMap.keySet()){
              int reportedCount = reportedCountInfoMap.get(reported);
              if(reportedCount >= k){
                  // 메일 발송 대상
                  for(int i=0; i<idList.length; i++){
                      if(reporterInfoMap.get(idList[i]) == null){
                          answer[i] = 0;
                      } else if(reporterInfoMap.get(idList[i]).contains(reported)){
                          answer[i]++;
                      }
                  }
              }
          }
          return answer;
       }
    }

    変更後

    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.HashSet;
    
    class Solution {
        public int[] solution(String[] idList, String[] report, int k){
            // @param idList : 이용자의 ID를 담은 배열.
            // @param report : 신고한 이용자와 신고당한 이용자의 정보를 담은 배열. ex) "a b",.. -> a가 b를 신고
            // @param k      : 신고 횟수에 따른 정지 기준 정수값.
            // @return answer : id_list에 담긴 id 순서대로 각 유저가 받은 신고 결과 메일 개수 배열.
            int[] answer = new int[idList.length];
            HashMap<String, HashSet<String>> reporterInfoMap = new HashMap<>();
            HashMap<String, Integer> reportedCountInfoMap = new HashMap<>();
            HashSet<String> reportSet = new HashSet<>(Arrays.asList(report));
            
            for(String reportInfo : reportSet){
                String reporter = reportInfo.split(" ")[0];  // 신고 한 사람
                String reported = reportInfo.split(" ")[1];  // 신고 당한 사람
                reporterInfoMap.putIfAbsent(reporter, new HashSet<String>(){{
                    add(reported);
                }});
                reporterInfoMap.get(reporter).add(reported);
                reportedCountInfoMap.put(reported, reportedCountInfoMap.getOrDefault(reported, 0)+1);
            }
    
            for (String reported : reportedCountInfoMap.keySet()){
                int reportedCount = reportedCountInfoMap.get(reported);
                if(reportedCount >= k){
                    // 메일 발송 대상
                    for(int i=0; i<idList.length; i++){
                        if(reporterInfoMap.containsKey(idList[i]) && reporterInfoMap.get(idList[i]).contains(reported)) {
                            answer[i]++;
                        }
                    }
                }
            }
            return answer;
       }
    }

    他人の草(崔錫賢、progg)

    import java.util.Arrays;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.List;
    import java.util.Objects;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    public class Solution {
        public int[] solution(String[] id_list, String[] report, int k) {
            Users users = IntStream.range(0, id_list.length)
                    .mapToObj(i -> new User(i, new UserId(id_list[i])))
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Users::new));
    
            Reports reports = Arrays.stream(report)
                    .map(rawReport -> ReportParser.parse(users, rawReport))
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Reports::new));
    
            Mails mails = reports.generateMails(users, k);
    
            return users.findSortedAll().stream()
                    .mapToInt(user -> mails.findAllByUser(user).size())
                    .toArray();
        }
    
    
        private static class Mails {
            private final List<Mail> mails;
    
            public Mails(List<Mail> mails) {
                this.mails = mails;
            }
    
            public List<Mail> findAllByUser(User user) {
                return mails.stream()
                        .filter(mail -> mail.isSameUser(user))
                        .collect(Collectors.toList());
            }
        }
    
        private static class Mail {
            private final User recipient;
    
            public Mail(User recipient) {
                this.recipient = recipient;
            }
    
            public boolean isSameUser(User user) {
                return Objects.equals(recipient, user);
            }
        }
    
        private static class ReportParser {
            private static final String DELIMITER = " ";
    
            public static Report parse(Users users, String report) {
                String[] splitted = report.split(DELIMITER);
                User reporter = users.findUser(new UserId(splitted[0]));
                User reported = users.findUser(new UserId(splitted[1]));
    
                return new Report(reporter, reported);
            }
        }
    
        private static class Reports {
            private final List<Report> reports;
    
            public Reports(List<Report> reports) {
                this.reports = reports;
            }
    
            public Mails generateMails(Users users, int mailThreshold) {
                return users.findSortedAll().stream()
                        .map(user -> generateMailsOf(user, mailThreshold))
                        .flatMap(Collection::stream)
                        .collect(Collectors.collectingAndThen(Collectors.toList(), Mails::new));
            }
    
            private List<Mail> generateMailsOf(User user, int mailThreshold) {
                List<Report> userReports = reports.stream()
                        .filter(report -> report.isReported(user))
                        .distinct()
                        .collect(Collectors.toList());
    
                if (userReports.size() >= mailThreshold) {
                    return userReports.stream()
                            .map(Report::getReporter)
                            .map(Mail::new)
                            .collect(Collectors.toList());
                }
    
                return Collections.emptyList();
            }
        }
    
        private static class Report {
            private final User reporter;
            private final User reported;
    
            public Report(User reporter, User reported) {
                this.reporter = reporter;
                this.reported = reported;
            }
    
            public boolean isReported(User user) {
                return reported.equals(user);
            }
    
            public User getReporter() {
                return reporter;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                Report report = (Report) o;
                return Objects.equals(reporter, report.reporter) && Objects.equals(reported, report.reported);
            }
    
            @Override
            public int hashCode() {
                return Objects.hash(reporter, reported);
            }
        }
    
        private static class Users {
            private final List<User> users;
    
            public Users(List<User> users) {
                this.users = users;
            }
    
            public User findUser(UserId userId) {
                return users.stream()
                        .filter(user -> user.getId().equals(userId))
                        .findAny()
                        .orElseThrow(() -> new IllegalArgumentException("User Not Found. id: " + userId));
            }
    
            public List<User> findSortedAll() {
                return users.stream()
                        .sorted()
                        .collect(Collectors.toList());
            }
        }
    
        private static class User implements Comparable<User> {
            private final Integer sequence;
            private final UserId userId;
    
            public User(Integer sequence, UserId userId) {
                this.sequence = sequence;
                this.userId = userId;
            }
    
            public UserId getId() {
                return userId;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                User user = (User) o;
                return Objects.equals(userId, user.userId);
            }
    
            @Override
            public int hashCode() {
                return Objects.hash(userId);
            }
    
            @Override
            public int compareTo(User other) {
                return this.sequence.compareTo(other.sequence);
            }
        }
    
        private static class UserId {
            private final String id;
    
            public UserId(String id) {
                this.id = id;
            }
    
            @Override
            public boolean equals(Object o) {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
                UserId userId1 = (UserId) o;
                return Objects.equals(id, userId1.id);
            }
    
            @Override
            public int hashCode() {
                return Objects.hash(id);
            }
    
            @Override
            public String toString() {
                return "Id{" +
                        "id='" + id + '\'' +
                        '}';
            }
        }
    }
    他の人がコードを短く書くために最善を尽くしていることが明らかになった.この人は対象を導きに問題を解決する最良の人の選択のようだ.(しまった…)

    n/a.結論


    今回の問題の鍵は,申告者が申告したプレイヤーを再申告する場合をどのように処理するかにある.ほとんどがSetで解決している人で、たまにStream APIで解決している人も見かけます.問題そのものの難易度は高くなく、問題の指紋を読み取って近づければ、すぐに解決できる.