├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── deploy.sh ├── docker-compose.yml ├── pom.xml └── src └── main ├── java └── com │ └── codedrills │ ├── CodeDrillsApplication.java │ ├── CodeDrillsController.java │ ├── model │ ├── Contest.java │ ├── Handle.java │ ├── Problem.java │ ├── Site.java │ ├── Tag.java │ ├── Verdict.java │ ├── analysis │ │ ├── AnalysisContext.java │ │ ├── AnalysisResult.java │ │ ├── ComparatorResult.java │ │ ├── Rating.java │ │ └── TagScore.java │ ├── cc │ │ ├── CCProblem.java │ │ └── CCProblemFetchResponse.java │ ├── cf │ │ ├── CFAuthor.java │ │ ├── CFBaseResponse.java │ │ ├── CFMember.java │ │ ├── CFProblem.java │ │ ├── CFProblemSet.java │ │ ├── CFProblemSetResponse.java │ │ ├── CFProblemStat.java │ │ ├── CFSubmission.java │ │ ├── CFTag.java │ │ ├── CFUser.java │ │ ├── CFUserInfoResponse.java │ │ └── CFUserStatusResponse.java │ ├── exceptions │ │ ├── CodedrillsException.java │ │ └── InvalidInputException.java │ ├── recommendation │ │ ├── PracticeProblem.java │ │ ├── PracticeRecommendations.java │ │ ├── ProblemDifficulty.java │ │ ├── ProblemStatus.java │ │ ├── Recommendation.java │ │ └── RecommendationContext.java │ └── stats │ │ └── UserStats.java │ ├── service │ ├── AnalysisService.java │ ├── ComparatorService.java │ ├── DataFetcher.java │ ├── ProblemsService.java │ ├── ProfileService.java │ ├── contests │ │ ├── CalendarService.java │ │ └── ContestsService.java │ ├── db │ │ ├── ProblemsDao.java │ │ └── RecommendationsDao.java │ ├── recommenders │ │ ├── AbstractRecommender.java │ │ ├── DailyPracticeRecommender.java │ │ ├── EasyMediumHardRecommender.java │ │ ├── ICPCRecommender.java │ │ ├── MiniContestRecommender.java │ │ ├── RandomRecommender.java │ │ ├── RecommenderInterface.java │ │ ├── RecommenderService.java │ │ ├── StrongWeakRecommender.java │ │ ├── TagRecommender.java │ │ └── WarmupRecommender.java │ └── sites │ │ ├── AbstractSiteService.java │ │ ├── AbstractTagProblemFetcher.java │ │ ├── Codechef.java │ │ ├── Codeforces.java │ │ ├── SiteInterface.java │ │ ├── SiteService.java │ │ └── Spoj.java │ └── util │ ├── HandleHelper.java │ ├── Helper.java │ ├── SiteHelper.java │ ├── TagHelper.java │ └── VerdictHelper.java └── resources ├── application-dev.properties ├── application-prod.properties ├── application.properties ├── favicon.ico ├── logback.xml ├── static ├── css │ └── common.css └── images │ ├── banners │ ├── codechef.png │ ├── codeforces.png │ ├── facebook.png │ ├── google.png │ ├── hackerearth.png │ ├── hackerrank.png │ ├── ipsc.png │ └── topcoder.png │ ├── code-drills.png │ ├── difficulty │ ├── easy.png │ ├── hard.png │ └── medium.png │ ├── fb.gif │ ├── github-logo.png │ ├── icons │ ├── codechef.png │ ├── codeforces.png │ └── spoj.png │ ├── inprogress.svg │ ├── ln.gif │ ├── logo.svg │ └── sorry.jpg └── templates ├── common ├── head.ftl ├── macros.ftl ├── navbar.ftl ├── recommendation.ftl ├── tail.ftl └── tip_macros.ftl ├── comparator.ftl ├── compare.ftl ├── content ├── tips.ftl └── tips │ ├── fast_cin_cout_cpp.ftl │ └── include_all_libraries_cpp.ftl ├── contests.ftl ├── error.ftl ├── faq.ftl ├── partial ├── analysis.ftl ├── input.ftl └── recommendations.ftl ├── profile.ftl ├── recommendation_list.ftl └── recommender.ftl /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '28 17 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################## 2 | ## Java 3 | ############################## 4 | .mtj.tmp/ 5 | *.class 6 | *.jar 7 | *.war 8 | *.ear 9 | *.nar 10 | hs_err_pid* 11 | 12 | ############################## 13 | ## Maven 14 | ############################## 15 | target/ 16 | pom.xml.tag 17 | pom.xml.releaseBackup 18 | pom.xml.versionsBackup 19 | pom.xml.next 20 | pom.xml.bak 21 | release.properties 22 | dependency-reduced-pom.xml 23 | buildNumber.properties 24 | .mvn/timing.properties 25 | .mvn/wrapper/maven-wrapper.jar 26 | 27 | ############################## 28 | ## Gradle 29 | ############################## 30 | bin/ 31 | build/ 32 | .gradle 33 | .gradletasknamecache 34 | gradle-app.setting 35 | !gradle-wrapper.jar 36 | 37 | ############################## 38 | ## IntelliJ 39 | ############################## 40 | out/ 41 | .idea/ 42 | .idea_modules/ 43 | *.iml 44 | *.ipr 45 | *.iws 46 | 47 | ############################## 48 | ## Eclipse 49 | ############################## 50 | .settings/ 51 | bin/ 52 | tmp/ 53 | .metadata 54 | .classpath 55 | .project 56 | *.tmp 57 | *.bak 58 | *.swp 59 | *~.nib 60 | local.properties 61 | .loadpath 62 | .factorypath 63 | 64 | ############################## 65 | ## NetBeans 66 | ############################## 67 | nbproject/private/ 68 | build/ 69 | nbbuild/ 70 | dist/ 71 | nbdist/ 72 | nbactions.xml 73 | nb-configuration.xml 74 | 75 | ############################## 76 | ## Visual Studio Code 77 | ############################## 78 | .vscode/ 79 | .code-workspace 80 | 81 | ############################## 82 | ## OS X 83 | ############################## 84 | .DS_Store 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Local development instructions 4 | 5 | To run the project locally, you first need to fork the repository and clone the repository locally. The project uses 6 | MySQL 5 as a database. You need to install Docker to run the project. Follow 7 | the [link](https://docs.docker.com/get-docker/) to install Docker. After installing docker the following steps are 8 | required. 9 | 10 | ### Run the containerised MySQL database 11 | 12 | 1. To start the database in docker run this command in the root path of the project. 13 | ``` 14 | docker-compose up -d 15 | ``` 16 | The details can also be found in the `docker-compose.yml` file. It also runs `adminer` a lightweight tool that we can 17 | use to access the data stored in the database using a GUI. 18 | 2. To stop the database running in docker run this command in the root path of the project. 19 | ``` 20 | docker-compose rm -a -s -f -v 21 | ``` 22 | `-a` flag marks all the services started in docker for removal. 23 | `-s` flag stops the services started in docker. 24 | `-f` flag force stops the services started in docker. 25 | `-v` flag removes all the associated volumes used by the services stopped in docker. 26 | 27 | ### Configuring the editor 28 | 29 | You can use any editor of your choice which supports development in Java but recommended IDE is Intellij IDEA. To run 30 | the project in dev mode you need to add `-Dspring.profiles.active=dev` to the VM options. This is recommended for local 31 | development. 32 | 33 | After performing all the steps run the project locally using Intellij using the run button. The project runs on 34 | port `8080` by default, although it can be changed if required by editing the `application.properties` file present in 35 | the `resources` folder. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [Codedrills Recommender](https://recommender.codedrills.io) is a problem recommendation project supported by [Codedrills](https://codedrills.io). It parses publicly available information from the coding profiles provided, analyses it and recommends problems tagged by topics to make you a better problem solver. 2 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # code-drills deployment script 4 | # This script DOES not pulls the git repo. It only packages, copies and deploys 5 | 6 | # function to check success of last command 7 | check() { 8 | if [ $? -eq 0 ]; then 9 | echo 10 | echo "$1 successful" 11 | echo 12 | else 13 | echo 14 | echo "$1 failed!" 15 | echo 16 | exit -1 17 | fi 18 | } 19 | 20 | # Display branch 21 | branch=`git symbolic-ref --short HEAD` 22 | echo "Deploying branch $branch" 23 | sleep 2 24 | 25 | # compile and create a package with prod profile 26 | mvn clean package 27 | check "Packaging" 28 | 29 | # tag with current time stamp 30 | echo "Pushing tag" 31 | tagname=prod-$(date +%Y%m%d.%H%M%S) 32 | git tag -a $tagname -m "Deployment script" 33 | check "Tag" 34 | #git push origin $tagname 35 | #check "Push tag" 36 | 37 | 38 | # copy the created jar file 39 | sudo cp target/code-drills.jar /var/code-drills/ 40 | check "Copying jar" 41 | 42 | # stop currently running service 43 | sudo /etc/init.d/code-drills stop 44 | check "Stopping service" 45 | 46 | # start newly copied service 47 | sudo /etc/init.d/code-drills start 48 | check "Starting service" 49 | 50 | # sleep a bit 51 | echo "Sleeping for 60 seconds" 52 | sleep 60 53 | check "Sleeping" 54 | 55 | # ping the site 56 | echo "pinging for status" 57 | response=`curl https://recommender.codedrills.io/status` 58 | if [ "$response" != "OK" ]; then 59 | echo "Status not OK" 60 | exit -1 61 | fi 62 | echo 63 | 64 | # done! 65 | echo "code-drills succesfully deployed" 66 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | dev-db: 5 | image: mysql:5 6 | command: --default-authentication-plugin=mysql_native_password 7 | restart: always 8 | environment: 9 | MYSQL_ROOT_PASSWORD: password 10 | MYSQL_DATABASE: code_drills 11 | MYSQL_USER: cd_local_user 12 | MYSQL_PASSWORD: '{cd_use_secure}' 13 | ports: 14 | - "3310:3306" 15 | 16 | adminer: 17 | image: adminer 18 | restart: always 19 | ports: 20 | - "6000:8080" 21 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.codedrills 7 | code-drills 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | code-drills 12 | Main code drills site backend 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 1.4.2.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 26 | 27 | 28 | 29 | org.springframework.boot 30 | spring-boot-starter-web 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-freemarker 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | 42 | 43 | 44 | org.apache.httpcomponents 45 | httpclient 46 | 4.5 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-data-jpa 52 | 53 | 54 | 55 | com.google.code.gson 56 | gson 57 | 2.8.0 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-devtools 63 | true 64 | 65 | 66 | 67 | 68 | com.google.guava 69 | guava 70 | 20.0 71 | 72 | 73 | 74 | 75 | org.jsoup 76 | jsoup 77 | 1.8.3 78 | 79 | 80 | 81 | net.sf.biweekly 82 | biweekly 83 | 0.6.0 84 | 85 | 86 | 87 | commons-io 88 | commons-io 89 | 2.5 90 | 91 | 92 | 93 | commons-httpclient 94 | commons-httpclient 95 | 3.1 96 | 97 | 98 | 99 | mysql 100 | mysql-connector-java 101 | 102 | 103 | 104 | 105 | 106 | gson 107 | https://mvnrepository.com/artifact/com.google.code.gson/gson 108 | 109 | 110 | 111 | 112 | code-drills 113 | 114 | 115 | org.springframework.boot 116 | spring-boot-maven-plugin 117 | 118 | true 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/CodeDrillsApplication.java: -------------------------------------------------------------------------------- 1 | package com.codedrills; 2 | 3 | import org.apache.http.conn.ssl.NoopHostnameVerifier; 4 | import org.apache.http.impl.client.CloseableHttpClient; 5 | import org.apache.http.impl.client.HttpClients; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 10 | import org.springframework.scheduling.annotation.EnableScheduling; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | @SpringBootApplication 14 | @EnableScheduling 15 | public class CodeDrillsApplication { 16 | 17 | @Bean 18 | public RestTemplate restTemplate() { 19 | CloseableHttpClient httpClient = HttpClients.custom() 20 | .setSSLHostnameVerifier(new NoopHostnameVerifier()) 21 | .build(); 22 | HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); 23 | requestFactory.setHttpClient(httpClient); 24 | return new RestTemplate(requestFactory); 25 | } 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(CodeDrillsApplication.class, args); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/CodeDrillsController.java: -------------------------------------------------------------------------------- 1 | package com.codedrills; 2 | 3 | import com.codedrills.model.analysis.AnalysisResult; 4 | import com.codedrills.model.analysis.ComparatorResult; 5 | import com.codedrills.model.exceptions.CodedrillsException; 6 | import com.codedrills.model.recommendation.Recommendation; 7 | import com.codedrills.service.ComparatorService; 8 | import com.codedrills.service.ProfileService; 9 | import com.codedrills.service.contests.ContestsService; 10 | import com.codedrills.service.recommenders.RecommenderService; 11 | import com.codedrills.util.HandleHelper; 12 | import org.apache.log4j.Logger; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.beans.factory.annotation.Value; 15 | import org.springframework.stereotype.Controller; 16 | import org.springframework.web.bind.annotation.*; 17 | import org.springframework.web.servlet.ModelAndView; 18 | 19 | import javax.servlet.http.HttpServletRequest; 20 | import java.io.IOException; 21 | import java.security.GeneralSecurityException; 22 | import java.util.TimeZone; 23 | 24 | @Controller 25 | public class CodeDrillsController { 26 | private static Logger logger = Logger.getLogger(CodeDrillsController.class); 27 | 28 | @Value("${is_prod}") 29 | private boolean isProd; 30 | 31 | @Autowired 32 | private ProfileService profileService; 33 | @Autowired 34 | private ContestsService contestsService; 35 | @Autowired 36 | private RecommenderService recommenderService; 37 | @Autowired 38 | private ComparatorService comparatorService; 39 | 40 | @SuppressWarnings("SameReturnValue") 41 | @ResponseBody 42 | @GetMapping("/status") 43 | public String status() { 44 | logger.info("Status request received"); 45 | return "OK"; 46 | } 47 | 48 | @GetMapping("/") 49 | public ModelAndView index() { 50 | logger.info("index request received"); 51 | return recommenderView(); 52 | } 53 | 54 | @GetMapping("/recommender") 55 | public ModelAndView recommender() { 56 | logger.info("recommender request received"); 57 | return recommenderView(); 58 | } 59 | 60 | @GetMapping("/tools/comparator") 61 | public ModelAndView comparator() { 62 | logger.info("comparator request received"); 63 | ModelAndView modelAndView = newModelAndView(); 64 | modelAndView.setViewName("comparator"); 65 | modelAndView.addObject("handlesRegex", HandleHelper.HANDLES_REGEX); 66 | return modelAndView; 67 | } 68 | 69 | @GetMapping("/contests") 70 | public ModelAndView contests(TimeZone timeZone, @RequestParam(required = false) Integer utc) throws GeneralSecurityException, IOException { 71 | logger.info(String.format("contests request received with timezone %s, utc %s", timeZone, utc)); 72 | ModelAndView modelAndView = newModelAndView(); 73 | modelAndView.setViewName("contests"); 74 | TimeZone useTZ; 75 | if(utc != null || timeZone == null) { 76 | useTZ = TimeZone.getTimeZone("UTC"); 77 | } else { 78 | useTZ = timeZone; 79 | modelAndView.addObject("timezone", useTZ.getID()); 80 | } 81 | 82 | modelAndView.addObject("contests", contestsService.fetchContests(useTZ)); 83 | return modelAndView; 84 | } 85 | 86 | @GetMapping("/faq") 87 | public ModelAndView faq() { 88 | logger.info("faq request received"); 89 | ModelAndView modelAndView = newModelAndView(); 90 | modelAndView.setViewName("faq"); 91 | return modelAndView; 92 | } 93 | 94 | @GetMapping("/about") 95 | public ModelAndView about() { 96 | logger.info("about request received"); 97 | ModelAndView modelAndView = newModelAndView(); 98 | modelAndView.setViewName("about"); 99 | return modelAndView; 100 | } 101 | 102 | public ModelAndView recommenderView() { 103 | ModelAndView modelAndView = newModelAndView(); 104 | modelAndView.addObject("handlesRegex", HandleHelper.HANDLES_REGEX); 105 | modelAndView.setViewName("recommender"); 106 | return modelAndView; 107 | } 108 | 109 | private ModelAndView newModelAndView() { 110 | ModelAndView modelAndView = new ModelAndView(); 111 | modelAndView.addObject("isProd", isProd); 112 | return modelAndView; 113 | } 114 | 115 | @GetMapping("/profile") 116 | public ModelAndView profile(@RequestParam String handles) { 117 | logger.info(String.format("Analysis request received for %s", handles)); 118 | 119 | ModelAndView modelAndView = newModelAndView(); 120 | 121 | AnalysisResult result = profileService.analyzeAndRecommend(handles); 122 | 123 | modelAndView.setViewName("profile"); 124 | logger.info(String.format("Analysis completed for %s", handles)); 125 | modelAndView.addObject("result", result); 126 | 127 | return modelAndView; 128 | } 129 | 130 | 131 | @GetMapping("/tools/compare") 132 | public ModelAndView compare(@RequestParam String handlesA, @RequestParam String handlesB) { 133 | logger.info(String.format("Analysis request received for %s vs %s", handlesA, handlesB)); 134 | 135 | ModelAndView modelAndView = newModelAndView(); 136 | 137 | ComparatorResult comparatorResult = comparatorService.compare(handlesA, handlesB); 138 | 139 | modelAndView.setViewName("compare"); 140 | logger.info(String.format("Comparision completed for %s vs %s", handlesA, handlesB)); 141 | modelAndView.addObject("result", comparatorResult); 142 | 143 | return modelAndView; 144 | } 145 | 146 | @GetMapping("/recommendations/{id}") 147 | public ModelAndView recommendations(@PathVariable String id) { 148 | ModelAndView modelAndView = newModelAndView(); 149 | Recommendation recommendation = recommenderService.getRecommendation(id); 150 | modelAndView.addObject("recommendation", recommendation); 151 | modelAndView.setViewName("recommendation_list"); 152 | 153 | return modelAndView; 154 | } 155 | 156 | @GetMapping("/resources/tips") 157 | public ModelAndView tips() { 158 | ModelAndView modelAndView = newModelAndView(); 159 | modelAndView.setViewName("content/tips"); 160 | 161 | return modelAndView; 162 | } 163 | 164 | @GetMapping("/resources/tips/{tip}") 165 | public ModelAndView aTip(@PathVariable String tip) { 166 | ModelAndView modelAndView = newModelAndView(); 167 | modelAndView.setViewName(String.format("content/tips/%s", tip)); 168 | 169 | return modelAndView; 170 | } 171 | 172 | @ExceptionHandler(Exception.class) 173 | public ModelAndView handleError(HttpServletRequest req, Exception ex) { 174 | ModelAndView modelAndView = newModelAndView(); 175 | modelAndView.setViewName("error"); 176 | if(ex instanceof CodedrillsException) { 177 | modelAndView.addObject("errorText", ex.getMessage()); 178 | } else { 179 | logger.error(String.format("Error while processing request %s", req.getRequestURI()), ex); 180 | } 181 | 182 | return modelAndView; 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/Contest.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model; 2 | 3 | import biweekly.component.VEvent; 4 | 5 | import java.time.LocalDateTime; 6 | import java.time.ZoneId; 7 | import java.time.ZonedDateTime; 8 | import java.time.format.DateTimeFormatter; 9 | import java.util.TimeZone; 10 | import java.util.stream.Stream; 11 | 12 | public class Contest { 13 | private static final DateTimeFormatter outputDTF = DateTimeFormatter.ofPattern("EEE dd MMM hh:mm a z"); 14 | private String site; 15 | private String title; 16 | private String description; 17 | private String url; 18 | private ZonedDateTime startTime; 19 | private ZonedDateTime endTime; 20 | 21 | public Contest(VEvent event) { 22 | title = event.getSummary().getValue(); 23 | // Temp fix for codechef long contest bug 24 | description = event.getDescription().getValue(); 25 | url = event.getLocation().getValue(); 26 | startTime = ZonedDateTime.of(LocalDateTime.ofInstant(event.getDateStart().getValue().toInstant(), ZoneId.of("UTC")), ZoneId.of("UTC")); 27 | endTime = ZonedDateTime.of(LocalDateTime.ofInstant(event.getDateEnd().getValue().toInstant(), ZoneId.of("UTC")), ZoneId.of("UTC")); 28 | 29 | site = null; 30 | site = Stream.of("topcoder", "codeforces", "codechef", "hackerrank", "hackerearth", "google", "ipsc") 31 | .filter(s -> url.contains(s)) 32 | .findFirst() 33 | .orElse(null); 34 | } 35 | 36 | public boolean isLive() { 37 | return ZonedDateTime.now(ZoneId.of("UTC")).isAfter(startTime); 38 | } 39 | 40 | public boolean endsLater() { 41 | return ZonedDateTime.now(ZoneId.of("UTC")).isBefore(endTime); 42 | } 43 | 44 | public String getTitle() { 45 | return title; 46 | } 47 | 48 | public String getDescription() { 49 | return description; 50 | } 51 | 52 | public String getUrl() { 53 | return url; 54 | } 55 | 56 | public ZonedDateTime getStartTime() { 57 | return startTime; 58 | } 59 | 60 | public ZonedDateTime getEndTime() { 61 | return endTime; 62 | } 63 | 64 | public String getStartTimeString() { 65 | return startTime.format(outputDTF); 66 | } 67 | 68 | public String getEndTimeString() { 69 | return endTime.format(outputDTF); 70 | } 71 | 72 | public ZonedDateTime getStartTimeUTC() { 73 | return startTime.withZoneSameInstant(ZoneId.of("UTC")); 74 | } 75 | 76 | public ZonedDateTime getEndTimeUTC() { 77 | return endTime.withZoneSameInstant(ZoneId.of("UTC")); 78 | } 79 | 80 | public String getSite() { 81 | return site; 82 | } 83 | 84 | public String getLiveOrUpcoming() { 85 | return isLive()? "live": "upcoming"; 86 | } 87 | 88 | public void setTimezone(TimeZone timezone) { 89 | startTime = startTime.withZoneSameInstant(timezone.toZoneId()); 90 | endTime = endTime.withZoneSameInstant(timezone.toZoneId()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/Handle.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model; 2 | 3 | import com.codedrills.util.HandleHelper; 4 | 5 | public class Handle implements Comparable { 6 | Site site; 7 | String handle; 8 | 9 | public Handle(Site site, String handle) { 10 | this.site = site; 11 | this.handle = handle.toLowerCase().trim(); 12 | } 13 | 14 | public Site getSite() { 15 | return site; 16 | } 17 | 18 | public String getHandle() { 19 | return handle; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return getSite().getShortName() + "/" + getHandle(); 25 | } 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if(this == o) return true; 30 | if(o == null || getClass() != o.getClass()) return false; 31 | 32 | Handle handle1 = (Handle) o; 33 | 34 | if(getSite() != handle1.getSite()) return false; 35 | return getHandle().equals(handle1.getHandle()); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | int result = getSite().hashCode(); 41 | result = 31 * result + getHandle().hashCode(); 42 | return result; 43 | } 44 | 45 | public String getProfileUrl() { 46 | return HandleHelper.buildProfileUrl(this); 47 | } 48 | 49 | @Override 50 | public int compareTo(Handle h) { 51 | if(equals(h)) return 0; 52 | if(site.equals(h.getSite())) return handle.compareTo(h.getHandle()); 53 | return site.compareTo(h.getSite()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/Problem.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model; 2 | 3 | import com.codedrills.model.recommendation.ProblemDifficulty; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.NotNull; 7 | import java.util.*; 8 | 9 | import static com.google.common.base.Strings.isNullOrEmpty; 10 | 11 | @Entity 12 | @Table(name="problems") 13 | public class Problem { 14 | @Id @NotNull 15 | private final String uid; 16 | 17 | @NotNull 18 | @Column(length = 64) 19 | private final String url; 20 | 21 | @NotNull 22 | @Column(length = 128) 23 | private final String name; 24 | 25 | @NotNull 26 | @ElementCollection(targetClass = Tag.class) 27 | @Enumerated(EnumType.ORDINAL) 28 | private final Set tags; 29 | 30 | @NotNull 31 | private final int solved; 32 | 33 | @NotNull 34 | @Enumerated(EnumType.ORDINAL) 35 | private final Site site; 36 | 37 | @Transient 38 | private double score; 39 | 40 | @Transient 41 | private ProblemDifficulty difficulty; 42 | 43 | public Problem() { 44 | uid = null; 45 | url = null; 46 | name = null; 47 | tags = null; 48 | solved = -1; 49 | site = null; 50 | score = -1; 51 | } 52 | 53 | public Problem(Site site, String uid, String url, String name, Collection tags, int solved) { 54 | this.site = site; 55 | this.uid = uid; 56 | this.url = url; 57 | this.name = name; 58 | this.tags = new HashSet<>(tags); 59 | this.solved = solved; 60 | } 61 | 62 | public Site getSite() { 63 | return site; 64 | } 65 | 66 | public String getUrl() { 67 | return url; 68 | } 69 | 70 | public Set getTags() { 71 | return tags; 72 | } 73 | 74 | public String getUid() { 75 | return uid; 76 | } 77 | 78 | public String getName() { 79 | return name; 80 | } 81 | 82 | public int getSolved() { 83 | return solved; 84 | } 85 | 86 | public double getScore() { 87 | return score; 88 | } 89 | 90 | public void setScore(double score) { 91 | this.score = score; 92 | } 93 | 94 | public ProblemDifficulty getDifficulty() { 95 | return difficulty; 96 | } 97 | 98 | public void setDifficulty(ProblemDifficulty difficulty) { 99 | this.difficulty = difficulty; 100 | } 101 | 102 | public boolean isValid() { 103 | return !isNullOrEmpty(url) && !isNullOrEmpty(name) && !isNullOrEmpty(uid); 104 | } 105 | 106 | public class CompareByUrl implements Comparator { 107 | @Override 108 | public int compare(Problem p1, Problem p2) { 109 | return p1.getUrl().compareTo(p2.getUrl()); 110 | } 111 | } 112 | 113 | @Override 114 | public boolean equals(Object o) { 115 | if(this == o) return true; 116 | if(o == null || getClass() != o.getClass()) return false; 117 | 118 | Problem problem = (Problem) o; 119 | 120 | if(!getUid().equals(problem.getUid())) return false; 121 | if(!getUrl().equals(problem.getUrl())) return false; 122 | if(!getName().equals(problem.getName())) return false; 123 | return getSite() == problem.getSite(); 124 | } 125 | 126 | @Override 127 | public int hashCode() { 128 | int result = getUid().hashCode(); 129 | result = 31 * result + getUrl().hashCode(); 130 | result = 31 * result + getName().hashCode(); 131 | result = 31 * result + getSite().hashCode(); 132 | return result; 133 | } 134 | 135 | public static int compareByScore(Problem p1, Problem p2) { 136 | if(p1 == null && p2 == null) return 0; 137 | if(p1 == null) return -1; 138 | if(p2 == null) return 1; 139 | return new Double(p1.getScore()).compareTo(p2.getScore()); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/Site.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model; 2 | 3 | import java.util.List; 4 | 5 | import static java.util.Arrays.asList; 6 | import static java.util.Collections.singletonList; 7 | 8 | // WARNING ORDER MATTERS ONLY APPEND 9 | public enum Site { 10 | CODECHEF(singletonList("cc")), 11 | CODEFORCES(asList("cf", "")), 12 | SPOJ(singletonList("sp")); 13 | 14 | private List aliases; 15 | 16 | 17 | Site(List aliases) { 18 | this.aliases = aliases; 19 | } 20 | 21 | public List getAliases() { 22 | return aliases; 23 | } 24 | 25 | public String getShortName() { 26 | return getAliases().get(0); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/Tag.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model; 2 | 3 | import static com.codedrills.model.Tag.TagType.TECHNIQUE; 4 | import static com.codedrills.model.Tag.TagType.TOPIC; 5 | 6 | // WARNING ORDER MATTERS ONLY APPEND 7 | public enum Tag { 8 | BinarySearch("Binary Search", TECHNIQUE), 9 | BruteForce("Brute Force", TECHNIQUE), 10 | DataStructures("Data Structures", TECHNIQUE), 11 | DivideConquer("Divide & Conquer", TECHNIQUE), 12 | DynamicProgramming("Dynamic Programming", TECHNIQUE), 13 | Implementation("Implementation", TECHNIQUE), 14 | Greedy("Greedy", TECHNIQUE), 15 | 16 | Adhoc("Adhoc", TOPIC), 17 | Combinatorics("Combinatorics", TOPIC), 18 | FFT("FFT", TOPIC), 19 | Flows("Flows & Matching", TOPIC), 20 | Games("Games", TOPIC), 21 | Geometry("Geometry", TOPIC), 22 | GraphsTrees("Graphs & Trees", TOPIC), 23 | Math("Math", TOPIC), 24 | Matrices("Matrices", TOPIC), 25 | NumberTheory("Number Theory", TOPIC), 26 | Probabilities("Probabilities", TOPIC), 27 | Strings("Strings", TOPIC), 28 | Hashing("Hashing", TOPIC), 29 | Bitmasks("Bitmasks", TOPIC), 30 | 31 | UNTAGGED("UNTAGGED", TagType.UNTAGGED); 32 | 33 | private String fullName; 34 | private TagType type; 35 | 36 | Tag(String fullName, TagType type) { 37 | this.fullName = fullName; 38 | this.type = type; 39 | } 40 | 41 | public String getFullName() { 42 | return fullName; 43 | } 44 | 45 | public TagType getType() { 46 | return type; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getFullName(); 52 | } 53 | 54 | public enum TagType { 55 | TECHNIQUE, 56 | TOPIC, 57 | UNTAGGED 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/Verdict.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model; 2 | 3 | public enum Verdict { 4 | AC, 5 | WA, 6 | TLE, 7 | RTE, 8 | CE, 9 | MLE, 10 | OTHER 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/analysis/AnalysisContext.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.analysis; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Problem; 5 | import com.codedrills.model.Tag; 6 | import com.codedrills.model.stats.UserStats; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import static java.util.Optional.ofNullable; 14 | 15 | public class AnalysisContext { 16 | private final UserStats userStats = new UserStats(); 17 | private final List handles; 18 | private List sortedSolved; 19 | private Map> sortedSolvedByTag = new HashMap<>(); 20 | 21 | public AnalysisContext(List handles) { 22 | this.handles = handles; 23 | } 24 | 25 | public UserStats getUserStats() { 26 | return userStats; 27 | } 28 | 29 | public void addStats(UserStats userStats) { 30 | getUserStats().merge(userStats); 31 | } 32 | 33 | public void setSortedSolved(List sortedSolved) { 34 | this.sortedSolved = sortedSolved; 35 | } 36 | 37 | public List getSortedSolved() { 38 | return sortedSolved; 39 | } 40 | 41 | public void addTag(Problem po, Tag t) { 42 | userStats.addTag(t); 43 | ofNullable(po) 44 | .ifPresent(p -> sortedSolvedByTag.computeIfAbsent(t, __ -> new ArrayList<>()).add(p)); 45 | } 46 | 47 | public Map> getSortedSolvedByTag() { 48 | return sortedSolvedByTag; 49 | } 50 | 51 | public void addStrong(List strong) { 52 | userStats.setStrong(strong); 53 | } 54 | 55 | public void addWeak(List weak) { 56 | userStats.setWeak(weak); 57 | } 58 | 59 | public List getHandles() { 60 | return handles; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/analysis/AnalysisResult.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.analysis; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.recommendation.PracticeRecommendations; 5 | import com.codedrills.model.stats.UserStats; 6 | 7 | import java.util.Map; 8 | 9 | public class AnalysisResult { 10 | private String handles; 11 | private UserStats userStats; 12 | private PracticeRecommendations practiceRecommendations; 13 | 14 | public AnalysisResult(String handles, UserStats userStats, PracticeRecommendations practiceRecommendations) { 15 | this.handles = handles; 16 | this.userStats = userStats; 17 | this.practiceRecommendations = practiceRecommendations; 18 | } 19 | 20 | public UserStats getUserStats() { 21 | return userStats; 22 | } 23 | 24 | public PracticeRecommendations getPracticeRecommendations() { 25 | return practiceRecommendations; 26 | } 27 | 28 | public String getHandles() { 29 | return handles; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/analysis/ComparatorResult.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.analysis; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Problem; 5 | import com.codedrills.model.Tag; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | public class ComparatorResult { 11 | private String handlesA; 12 | private String handlesB; 13 | private List strongerA, strongerB; 14 | private List onlySolvedByA, onlySolvedByB; 15 | private Map ratingsA, ratingsB; 16 | private boolean same; 17 | 18 | public ComparatorResult() { 19 | } 20 | 21 | public ComparatorResult(String handlesA, String handlesB) { 22 | this.handlesA = handlesA; 23 | this.handlesB = handlesB; 24 | } 25 | 26 | public String getHandlesA() { 27 | return handlesA; 28 | } 29 | 30 | public void setHandlesA(String handlesA) { 31 | this.handlesA = handlesA; 32 | } 33 | 34 | public String getHandlesB() { 35 | return handlesB; 36 | } 37 | 38 | public void setHandlesB(String handlesB) { 39 | this.handlesB = handlesB; 40 | } 41 | 42 | public List getStrongerA() { 43 | return strongerA; 44 | } 45 | 46 | public void setStrongerA(List strongerA) { 47 | this.strongerA = strongerA; 48 | } 49 | 50 | public List getStrongerB() { 51 | return strongerB; 52 | } 53 | 54 | public void setStrongerB(List strongerB) { 55 | this.strongerB = strongerB; 56 | } 57 | 58 | public List getOnlySolvedByA() { 59 | return onlySolvedByA; 60 | } 61 | 62 | public void setOnlySolvedByA(List onlySolvedByA) { 63 | this.onlySolvedByA = onlySolvedByA; 64 | } 65 | 66 | public List getOnlySolvedByB() { 67 | return onlySolvedByB; 68 | } 69 | 70 | public void setOnlySolvedByB(List onlySolvedByB) { 71 | this.onlySolvedByB = onlySolvedByB; 72 | } 73 | 74 | public Map getRatingsA() { 75 | return ratingsA; 76 | } 77 | 78 | public void setRatingsA(Map ratingsA) { 79 | this.ratingsA = ratingsA; 80 | } 81 | 82 | public Map getRatingsB() { 83 | return ratingsB; 84 | } 85 | 86 | public void setRatingsB(Map ratingsB) { 87 | this.ratingsB = ratingsB; 88 | } 89 | 90 | public boolean areSame() { 91 | return same; 92 | } 93 | 94 | public void setSame(boolean same) { 95 | this.same = same; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/analysis/Rating.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.analysis; 2 | 3 | public class Rating { 4 | private Double current, highest; 5 | 6 | public Rating(Double current, Double highest) { 7 | this.current = current; 8 | this.highest = highest; 9 | } 10 | 11 | public Double getCurrent() { 12 | return current; 13 | } 14 | 15 | public Double getHighest() { 16 | return highest; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/analysis/TagScore.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.analysis; 2 | 3 | import com.codedrills.model.Tag; 4 | 5 | public class TagScore { 6 | private final Tag tag; 7 | private final double score; 8 | private final int count; 9 | 10 | public TagScore(Tag tag, double score, int count) { 11 | this.tag = tag; 12 | this.score = score; 13 | this.count = count; 14 | } 15 | 16 | public Tag getTag() { 17 | return tag; 18 | } 19 | 20 | public double getScore() { 21 | return score; 22 | } 23 | 24 | public int getCount() { 25 | return count; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cc/CCProblem.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cc; 2 | 3 | import java.util.List; 4 | 5 | public class CCProblem { 6 | private String code; 7 | List tags; 8 | String author; 9 | int attempted_by; 10 | int solved_by; 11 | String name; 12 | 13 | public String getCode() { 14 | return code; 15 | } 16 | 17 | public List getTags() { 18 | return tags; 19 | } 20 | 21 | public String getAuthor() { 22 | return author; 23 | } 24 | 25 | public int getAttempted_by() { 26 | return attempted_by; 27 | } 28 | 29 | public int getSolved_by() { 30 | return solved_by; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cc/CCProblemFetchResponse.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cc; 2 | 3 | import java.util.Map; 4 | 5 | public class CCProblemFetchResponse { 6 | private Map all_problems; 7 | private String message; 8 | 9 | public Map getAll_problems() { 10 | return all_problems; 11 | } 12 | 13 | public String getMessage() { 14 | return message; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFAuthor.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | import java.util.List; 4 | 5 | public class CFAuthor { 6 | private int contestId; 7 | private List members; 8 | 9 | public int getContestId() { 10 | return contestId; 11 | } 12 | 13 | public List getMembers() { 14 | return members; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFBaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | public class CFBaseResponse { 4 | private String status; 5 | 6 | public String getStatus() { 7 | return status; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFMember.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | public class CFMember { 4 | private String handle; 5 | 6 | public String getHandle() { 7 | return handle; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFProblem.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | import java.util.List; 4 | 5 | public class CFProblem { 6 | private int contestId; 7 | private String index; 8 | private String name; 9 | private String type; 10 | private Double points; 11 | List tags; 12 | 13 | public int getContestId() { 14 | return contestId; 15 | } 16 | 17 | public String getIndex() { 18 | return index; 19 | } 20 | 21 | public String getName() { 22 | return name; 23 | } 24 | 25 | public String getType() { 26 | return type; 27 | } 28 | 29 | public Double getPoints() { 30 | return points; 31 | } 32 | 33 | public List getTags() { 34 | return tags; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFProblemSet.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | import java.util.List; 4 | 5 | public class CFProblemSet { 6 | List problems; 7 | List problemStatistics; 8 | 9 | public List getProblems() { 10 | return problems; 11 | } 12 | 13 | public List getProblemStatistics() { 14 | return problemStatistics; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFProblemSetResponse.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | public class CFProblemSetResponse extends CFBaseResponse { 4 | CFProblemSet result; 5 | 6 | public CFProblemSet getResult() { 7 | return result; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFProblemStat.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | public class CFProblemStat { 4 | private int contestId; 5 | private String index; 6 | private int solvedCount; 7 | 8 | public int getContestId() { 9 | return contestId; 10 | } 11 | 12 | public String getIndex() { 13 | return index; 14 | } 15 | 16 | public int getSolvedCount() { 17 | return solvedCount; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFSubmission.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | public class CFSubmission { 4 | private String id; 5 | private int contestId; 6 | private long creationTimeSeconds; 7 | private long relativeTimeSeconds; 8 | CFProblem problem; 9 | CFAuthor author; 10 | String programmingLanguage; 11 | String verdict; 12 | String testset; 13 | int passedTestCount; 14 | int timeConsummedMillis; 15 | int memoryConsumedBytes; 16 | 17 | public String getProgrammingLanguage() { 18 | return programmingLanguage; 19 | } 20 | 21 | public String getVerdict() { 22 | return verdict; 23 | } 24 | 25 | public String getTestset() { 26 | return testset; 27 | } 28 | 29 | public int getPassedTestCount() { 30 | return passedTestCount; 31 | } 32 | 33 | public int getTimeConsummedMillis() { 34 | return timeConsummedMillis; 35 | } 36 | 37 | public int getMemoryConsumedBytes() { 38 | return memoryConsumedBytes; 39 | } 40 | 41 | public String getId() { 42 | return id; 43 | } 44 | 45 | public int getContestId() { 46 | return contestId; 47 | } 48 | 49 | public long getCreationTimeSeconds() { 50 | return creationTimeSeconds; 51 | } 52 | 53 | public long getRelativeTimeSeconds() { 54 | return relativeTimeSeconds; 55 | } 56 | 57 | public CFProblem getProblem() { 58 | return problem; 59 | } 60 | 61 | public CFAuthor getAuthor() { 62 | return author; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFTag.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | /** 4 | * Created by balajiganapathi on 4/5/17. 5 | */ 6 | public class CFTag { 7 | private String name; 8 | private int position; 9 | private int id; 10 | 11 | public String getName() { 12 | return name; 13 | } 14 | 15 | public int getPosition() { 16 | return position; 17 | } 18 | 19 | public int getId() { 20 | return id; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFUser.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | 4 | public class CFUser { 5 | private String lastName; 6 | private String handle; 7 | private Integer maxRating; 8 | private String titlePhoto; 9 | private String friendOfCount; 10 | private String maxRank; 11 | private String avatar; 12 | private String lastOnlineTimeSeconds; 13 | private String country; 14 | private String city; 15 | private String rank; 16 | private String organization; 17 | private String contribution; 18 | private String registrationTimeSeconds; 19 | private Integer rating; 20 | private String firstName; 21 | 22 | public String getLastName() { 23 | return lastName; 24 | } 25 | 26 | public String getHandle() { 27 | return handle; 28 | } 29 | 30 | public Integer getMaxRating() { 31 | return maxRating; 32 | } 33 | 34 | public String getTitlePhoto() { 35 | return titlePhoto; 36 | } 37 | 38 | public String getFriendOfCount() { 39 | return friendOfCount; 40 | } 41 | 42 | public String getMaxRank() { 43 | return maxRank; 44 | } 45 | 46 | public String getAvatar() { 47 | return avatar; 48 | } 49 | 50 | public String getLastOnlineTimeSeconds() { 51 | return lastOnlineTimeSeconds; 52 | } 53 | 54 | public String getCountry() { 55 | return country; 56 | } 57 | 58 | public String getCity() { 59 | return city; 60 | } 61 | 62 | public String getRank() { 63 | return rank; 64 | } 65 | 66 | public String getOrganization() { 67 | return organization; 68 | } 69 | 70 | public String getContribution() { 71 | return contribution; 72 | } 73 | 74 | public String getRegistrationTimeSeconds() { 75 | return registrationTimeSeconds; 76 | } 77 | 78 | public Integer getRating() { 79 | return rating; 80 | } 81 | 82 | public String getFirstName() { 83 | return firstName; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFUserInfoResponse.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | import java.util.List; 4 | 5 | public class CFUserInfoResponse extends CFBaseResponse { 6 | List result; 7 | 8 | public List getResult() { 9 | return result; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/cf/CFUserStatusResponse.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.cf; 2 | 3 | import java.util.List; 4 | 5 | public class CFUserStatusResponse extends CFBaseResponse { 6 | private List result; 7 | 8 | public List getResult() { 9 | return result; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/exceptions/CodedrillsException.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.exceptions; 2 | 3 | public class CodedrillsException extends RuntimeException { 4 | public CodedrillsException() { 5 | } 6 | 7 | public CodedrillsException(String message) { 8 | super(message); 9 | } 10 | 11 | public CodedrillsException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | 15 | public CodedrillsException(Throwable cause) { 16 | super(cause); 17 | } 18 | 19 | public CodedrillsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 20 | super(message, cause, enableSuppression, writableStackTrace); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/exceptions/InvalidInputException.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.exceptions; 2 | 3 | public class InvalidInputException extends CodedrillsException { 4 | public InvalidInputException() { 5 | } 6 | 7 | public InvalidInputException(String message) { 8 | super(message); 9 | } 10 | 11 | public InvalidInputException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/recommendation/PracticeProblem.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.recommendation; 2 | 3 | import com.codedrills.model.Problem; 4 | 5 | import javax.persistence.*; 6 | 7 | @Entity(name = "practice_problems") 8 | public class PracticeProblem { 9 | @Id @GeneratedValue 10 | private long id; 11 | @ManyToOne 12 | private Recommendation recommendation; 13 | @ManyToOne 14 | private Problem problem; 15 | @Enumerated(value = EnumType.ORDINAL) 16 | private ProblemStatus status; 17 | 18 | public PracticeProblem() { 19 | } 20 | 21 | public PracticeProblem(Recommendation recommendation, Problem problem, ProblemStatus status) { 22 | this.recommendation = recommendation; 23 | this.problem = problem; 24 | this.status = status; 25 | } 26 | 27 | public Recommendation getRecommendation() { 28 | return recommendation; 29 | } 30 | 31 | public Problem getProblem() { 32 | return problem; 33 | } 34 | 35 | public ProblemStatus getStatus() { 36 | return status; 37 | } 38 | 39 | public void setStatus(ProblemStatus status) { 40 | this.status = status; 41 | } 42 | 43 | public long getId() { 44 | return id; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/recommendation/PracticeRecommendations.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.recommendation; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | public class PracticeRecommendations { 7 | private final List recommendations; 8 | 9 | public PracticeRecommendations(List recommendations) { 10 | this.recommendations = recommendations; 11 | } 12 | 13 | public List getRecommendations() { 14 | return recommendations.stream() 15 | .sorted(Recommendation::compareByRank) 16 | .collect(Collectors.toList()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/recommendation/ProblemDifficulty.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.recommendation; 2 | 3 | public enum ProblemDifficulty { 4 | EASY, 5 | MEDIUM, 6 | HARD; 7 | 8 | public static ProblemDifficulty getDifficulty(double score) { 9 | if(score <= 40) return EASY; 10 | if(score <= 80) return MEDIUM; 11 | return HARD; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/recommendation/ProblemStatus.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.recommendation; 2 | 3 | public enum ProblemStatus { 4 | SOLVED, 5 | ATTEMPTED, 6 | UNSOLVED 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/recommendation/Recommendation.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.recommendation; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.util.Helper; 5 | 6 | import javax.persistence.CascadeType; 7 | import javax.persistence.Entity; 8 | import javax.persistence.Id; 9 | import javax.persistence.OneToMany; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | @Entity(name = "recommendations") 14 | public class Recommendation { 15 | @Id 16 | private String id; 17 | private String handles; 18 | private String name; 19 | private String description; 20 | @OneToMany(cascade = CascadeType.ALL) 21 | private List practiceProblems; 22 | private int rank; 23 | 24 | public Recommendation() { 25 | } 26 | 27 | public Recommendation(String name, String description, List practiceProblems, int rank) { 28 | this.id = Helper.randomId(); 29 | this.name = name; 30 | this.description = description; 31 | this.rank = rank; 32 | 33 | this.practiceProblems = practiceProblems.stream() 34 | .sorted(Problem::compareByScore) 35 | .map(p -> new PracticeProblem(this, p, ProblemStatus.UNSOLVED)) 36 | .collect(Collectors.toList()); 37 | } 38 | 39 | public String getName() { 40 | return name; 41 | } 42 | 43 | public String getDescription() { 44 | return description; 45 | } 46 | 47 | public List getPracticeProblems() { 48 | return practiceProblems; 49 | } 50 | 51 | public int getRank() { 52 | return rank; 53 | } 54 | 55 | public static int compareByRank(Recommendation pr1, Recommendation pr2) { 56 | if(pr1.getRank() != pr2.getRank()) return new Integer(pr1.getRank()).compareTo(pr2.getRank()); 57 | return pr1.getName().compareTo(pr2.getName()); 58 | } 59 | 60 | public String getId() { 61 | return id; 62 | } 63 | 64 | public String getHandles() { 65 | return handles; 66 | } 67 | 68 | public void setHandles(String handles) { 69 | this.handles = handles; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/recommendation/RecommendationContext.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.recommendation; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.Tag; 5 | import com.codedrills.model.analysis.AnalysisContext; 6 | import com.codedrills.model.stats.UserStats; 7 | import com.codedrills.util.HandleHelper; 8 | 9 | import java.util.*; 10 | import java.util.stream.Collectors; 11 | 12 | public class RecommendationContext { 13 | private final Map problemsByUid; 14 | private final Map> problemsByTags; 15 | private final Map> sortedSolvedByTags; 16 | private final Map> sortedUnsolvedByTags; 17 | 18 | private final UserStats userStats; 19 | private final List unsolved; 20 | private final List solved; 21 | private final List solvedSorted; 22 | private final List unsolvedSorted; 23 | private final String handles; 24 | 25 | private List recommendations = new ArrayList<>(); 26 | 27 | public RecommendationContext(Map problemsByUid, Map> problemsByTags, AnalysisContext analysisContext) { 28 | this.problemsByUid = problemsByUid; 29 | this.problemsByTags = problemsByTags; 30 | this.userStats = analysisContext.getUserStats(); 31 | this.handles = HandleHelper.canonicalHandles(analysisContext.getHandles()); 32 | 33 | Set solvedUids = new HashSet<>(userStats.getSolvedProblemUids()); 34 | Set unsolvedUids = new HashSet<>(problemsByUid.keySet()); 35 | unsolvedUids.removeAll(solvedUids); 36 | 37 | solved = solvedUids.stream() 38 | .map(problemsByUid::get) 39 | .filter(p -> p != null) 40 | .collect(Collectors.toList()); 41 | 42 | unsolved = unsolvedUids.stream() 43 | .map(problemsByUid::get) 44 | .filter(p -> p != null) 45 | .collect(Collectors.toList()); 46 | 47 | solvedSorted = solved 48 | .stream() 49 | .sorted(Problem::compareByScore) 50 | .collect(Collectors.toList()); 51 | 52 | unsolvedSorted = unsolved 53 | .stream() 54 | .sorted(Problem::compareByScore) 55 | .collect(Collectors.toList()); 56 | 57 | sortedSolvedByTags = analysisContext.getSortedSolvedByTag(); 58 | sortedUnsolvedByTags = new HashMap<>(); 59 | unsolvedSorted.stream() 60 | .forEach(p -> p.getTags().stream() 61 | .forEach(t -> sortedUnsolvedByTags.computeIfAbsent(t, __ -> new ArrayList<>()).add(p))); 62 | } 63 | 64 | public void addRecommendation(Recommendation recommendation) { 65 | if(recommendation == null || recommendation.getPracticeProblems().isEmpty()) return; 66 | recommendation.getPracticeProblems().sort( 67 | (p1, p2) -> new Double(p1.getProblem().getScore()).compareTo(p2.getProblem().getScore()) 68 | ); 69 | recommendation.setHandles(handles); 70 | recommendations.add(recommendation); 71 | } 72 | 73 | public Map getProblemsByUid() { 74 | return problemsByUid; 75 | } 76 | 77 | public UserStats getUserStats() { 78 | return userStats; 79 | } 80 | 81 | public List getRecommendations() { 82 | return recommendations; 83 | } 84 | 85 | public List getUnsolved() { 86 | return unsolved; 87 | } 88 | 89 | public List getSolved() { 90 | return solved; 91 | } 92 | 93 | public Map> getProblemsByTags() { 94 | return problemsByTags; 95 | } 96 | 97 | public List getSolvedSorted() { 98 | return solvedSorted; 99 | } 100 | 101 | public List getUnsolvedSorted() { 102 | return unsolvedSorted; 103 | } 104 | 105 | public Map> getSortedSolvedByTags() { 106 | return sortedSolvedByTags; 107 | } 108 | 109 | public Map> getSortedUnsolvedByTags() { 110 | return sortedUnsolvedByTags; 111 | } 112 | } -------------------------------------------------------------------------------- /src/main/java/com/codedrills/model/stats/UserStats.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.model.stats; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Tag; 5 | import com.codedrills.model.Verdict; 6 | import com.codedrills.model.analysis.Rating; 7 | import com.codedrills.model.analysis.TagScore; 8 | 9 | import java.util.*; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | import static java.util.Collections.emptyList; 13 | 14 | public class UserStats { 15 | private Set solvedProblemUids = new HashSet<>(); 16 | private ConcurrentHashMap verdictCount = new ConcurrentHashMap<>(); 17 | private ConcurrentHashMap tagCount = new ConcurrentHashMap<>(); 18 | private List strong = emptyList(), weak = emptyList(); 19 | private Map ratings = new TreeMap<>(); 20 | 21 | public Set getSolvedProblemUids() { 22 | return solvedProblemUids; 23 | } 24 | 25 | public Map getVerdictCount() { 26 | return verdictCount; 27 | } 28 | 29 | public Map getTagCount() { 30 | return tagCount; 31 | } 32 | 33 | public void addVerdict(Verdict verdict, int cnt) { 34 | verdictCount.put(verdict, verdictCount.getOrDefault(verdict, 0) + cnt); 35 | } 36 | 37 | public void addSolved(String uid) { 38 | solvedProblemUids.add(uid); 39 | } 40 | 41 | public void merge(UserStats userStats) { 42 | solvedProblemUids.addAll(userStats.getSolvedProblemUids()); 43 | userStats.getVerdictCount() 44 | .entrySet() 45 | .forEach(e -> verdictCount.put(e.getKey(), verdictCount.getOrDefault(e.getKey(), 0) + e.getValue())); 46 | userStats.getRatings() 47 | .entrySet() 48 | .forEach(e -> ratings.put(e.getKey(), e.getValue())); 49 | } 50 | 51 | public void addTag(Tag t) { 52 | tagCount.put(t, tagCount.getOrDefault(t, 0) + 1); 53 | } 54 | 55 | // Used from analysis.ftl 56 | public Integer totalSubmissions() { 57 | return verdictCount.values() 58 | .stream() 59 | .mapToInt(i -> i) 60 | .sum(); 61 | } 62 | 63 | public Integer totalSolved() { 64 | return solvedProblemUids.size(); 65 | } 66 | 67 | public List getStrong() { 68 | return strong; 69 | } 70 | 71 | public void setStrong(List strong) { 72 | this.strong = strong; 73 | } 74 | 75 | public List getWeak() { 76 | return weak; 77 | } 78 | 79 | public void setWeak(List weak) { 80 | this.weak = weak; 81 | } 82 | 83 | public Map getRatings() { 84 | return ratings; 85 | } 86 | 87 | public void addRating(Handle handle, Rating rating) { 88 | ratings.put(handle, rating); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/AnalysisService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Problem; 5 | import com.codedrills.model.Tag; 6 | import com.codedrills.model.analysis.AnalysisContext; 7 | import com.codedrills.model.analysis.TagScore; 8 | import com.codedrills.model.stats.UserStats; 9 | import com.codedrills.service.sites.SiteService; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.*; 14 | import java.util.stream.Collectors; 15 | 16 | import static com.codedrills.model.Tag.UNTAGGED; 17 | import static java.util.Collections.emptyList; 18 | import static java.util.Collections.singleton; 19 | 20 | @Service 21 | public class AnalysisService { 22 | private static final int TAG_SOLVED_COUNT_THRESHOLD = 7; 23 | private static final int STRONG_WEAK_THRESHOLD = 30; 24 | @Autowired 25 | private ProblemsService problemsService; 26 | @Autowired 27 | private SiteService siteService; 28 | 29 | public AnalysisContext analyzeUser(List handles) { 30 | AnalysisContext analysisContext = new AnalysisContext(handles); 31 | 32 | List userStatsList = siteService.fetchSubmissionStats(handles); 33 | userStatsList.stream() 34 | .forEach(analysisContext::addStats); 35 | 36 | analyzeTags(analysisContext); 37 | addStrongWeakAreas(analysisContext); 38 | 39 | return analysisContext; 40 | } 41 | 42 | private void analyzeTags(AnalysisContext analysisContext) { 43 | List sortedSolved = analysisContext.getUserStats().getSolvedProblemUids() 44 | .stream() 45 | .map(problemsService::getProblemById) 46 | .filter(p -> p != null) 47 | .sorted(Problem::compareByScore) 48 | .collect(Collectors.toList()); 49 | 50 | analysisContext.setSortedSolved(sortedSolved); 51 | 52 | analysisContext.getUserStats().getSolvedProblemUids() 53 | .stream() 54 | .map(problemsService::getProblemById) 55 | .sorted(Problem::compareByScore) 56 | .map(Optional::ofNullable) 57 | .forEach(po -> 58 | po.map(p -> p.getTags()).orElseGet(() -> singleton(UNTAGGED)) 59 | .stream() 60 | .forEach(t -> analysisContext.addTag(po.orElse(null), t)) 61 | ); 62 | } 63 | 64 | private void addStrongWeakAreas(AnalysisContext analysisContext) { 65 | if(analysisContext.getUserStats().getSolvedProblemUids().size() <= STRONG_WEAK_THRESHOLD) { 66 | analysisContext.addStrong(emptyList()); 67 | analysisContext.addWeak(emptyList()); 68 | return; 69 | } 70 | 71 | List tagScores = Arrays.stream(Tag.values()) 72 | .filter(t -> !t.equals(UNTAGGED)) 73 | .map(t -> { 74 | List sortedSolved = analysisContext.getSortedSolvedByTag().getOrDefault(t, emptyList()); 75 | int getLast = Math.min(sortedSolved.size(), Math.max(5, 20 * sortedSolved.size() / 100)); 76 | double score = sortedSolved.stream() 77 | .skip(sortedSolved.size() - getLast) 78 | .mapToDouble(Problem::getScore) 79 | .average() 80 | .orElse(0); 81 | 82 | return new TagScore(t, score, sortedSolved.size()); 83 | }) 84 | .filter(t -> t.getCount() > TAG_SOLVED_COUNT_THRESHOLD) 85 | .sorted((ts1, ts2) -> { 86 | if(new Double(ts1.getScore()).equals(ts2.getScore())) { 87 | if(ts1.getTag().getType() == Tag.TagType.TECHNIQUE) return -1; 88 | return 1; 89 | } 90 | return new Double(ts1.getScore()).compareTo(ts2.getScore()); 91 | }) 92 | .collect(Collectors.toList()); 93 | 94 | int weakLen = Math.max(0, tagScores.size() - 5); 95 | List weak = tagScores.stream() 96 | .limit(weakLen) 97 | .collect(Collectors.toList()); 98 | 99 | List strong = tagScores.stream() 100 | .collect(Collectors.toList()); 101 | 102 | Collections.reverse(strong); 103 | 104 | analysisContext.addStrong(strong); 105 | analysisContext.addWeak(weak); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/ComparatorService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.Tag; 5 | import com.codedrills.model.analysis.AnalysisContext; 6 | import com.codedrills.model.analysis.ComparatorResult; 7 | import com.codedrills.model.analysis.TagScore; 8 | import com.codedrills.util.HandleHelper; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Set; 16 | import java.util.stream.Collectors; 17 | 18 | import static java.util.Collections.emptyList; 19 | import static java.util.stream.Collectors.groupingBy; 20 | 21 | @Service 22 | public class ComparatorService { 23 | @Autowired 24 | AnalysisService analysisService; 25 | @Autowired 26 | ProblemsService problemsService; 27 | 28 | public ComparatorResult compare(String handlesA, String handlesB) { 29 | handlesA = canonize(handlesA); 30 | handlesB = canonize(handlesB); 31 | ComparatorResult comparatorResult = new ComparatorResult(handlesA, handlesB); 32 | 33 | comparatorResult.setSame(handlesA.equals(handlesB)); 34 | if(comparatorResult.areSame()) return comparatorResult; 35 | 36 | AnalysisContext analysisA = analysisService.analyzeUser(HandleHelper.splitHandles(handlesA)); 37 | AnalysisContext analysisB = analysisService.analyzeUser(HandleHelper.splitHandles(handlesB)); 38 | 39 | setStrong(comparatorResult, analysisA.getUserStats().getStrong(), analysisB.getUserStats().getStrong()); 40 | setSolved(comparatorResult, analysisA.getUserStats().getSolvedProblemUids(), analysisB.getUserStats().getSolvedProblemUids()); 41 | 42 | comparatorResult.setRatingsA(analysisA.getUserStats().getRatings()); 43 | comparatorResult.setRatingsB(analysisB.getUserStats().getRatings()); 44 | 45 | return comparatorResult; 46 | } 47 | 48 | private void setSolved(ComparatorResult comparatorResult, Set solvedProblemUidsA, Set solvedProblemUidsB) { 49 | List solvedA = solvedProblemUidsA.stream() 50 | .filter(u -> !solvedProblemUidsB.contains(u)) 51 | .map(problemsService::getProblemById) 52 | .filter(p -> p != null) 53 | .collect(Collectors.toList()); 54 | 55 | List solvedB = solvedProblemUidsB.stream() 56 | .filter(u -> !solvedProblemUidsA.contains(u)) 57 | .map(problemsService::getProblemById) 58 | .filter(p -> p != null) 59 | .collect(Collectors.toList()); 60 | 61 | comparatorResult.setOnlySolvedByA(solvedA); 62 | comparatorResult.setOnlySolvedByB(solvedB); 63 | } 64 | 65 | private void setStrong(ComparatorResult comparatorResult, List strongA, List strongB) { 66 | Map strongAMap = strongA.stream() 67 | .collect(Collectors.toMap(t -> t.getTag(), t -> t.getScore())); 68 | 69 | Map strongBMap = strongB.stream() 70 | .collect(Collectors.toMap(t -> t.getTag(), t -> t.getScore())); 71 | 72 | Map> strongerA = Arrays.stream(Tag.values()) 73 | .filter(t -> strongAMap.containsKey(t) || strongBMap.containsKey(t)) 74 | .filter(t -> isStronger(strongAMap.get(t), strongBMap.get(t)) != null) 75 | .collect(groupingBy(t -> isStronger(strongAMap.get(t), strongBMap.get(t)))); 76 | 77 | comparatorResult.setStrongerA(strongerA.getOrDefault(true, emptyList())); 78 | comparatorResult.setStrongerB(strongerA.getOrDefault(false, emptyList())); 79 | } 80 | 81 | private Boolean isStronger(Double scoreA, Double scoreB) { 82 | if(scoreA == null) return false; 83 | if(scoreB == null) return true; 84 | if(scoreA.equals(scoreB)) return null; 85 | return scoreA > scoreB; 86 | } 87 | 88 | private String canonize(String handles) { 89 | return HandleHelper.canonicalHandles(HandleHelper.validateAndSplit(handles)); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/DataFetcher.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service; 2 | 3 | import com.google.common.cache.Cache; 4 | import com.google.common.cache.CacheBuilder; 5 | import org.apache.log4j.Logger; 6 | import org.jsoup.Jsoup; 7 | import org.jsoup.nodes.Document; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import java.io.IOException; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.concurrent.ExecutionException; 17 | import java.util.concurrent.TimeUnit; 18 | 19 | @Service 20 | public class DataFetcher { 21 | private static Logger logger = Logger.getLogger(DataFetcher.class); 22 | private final RestTemplate restTemplate; 23 | private final static Map> cacheMap = new HashMap<>(); 24 | 25 | static { 26 | cacheMap.put(CacheDuration.SHORT, 27 | CacheBuilder.newBuilder() 28 | .maximumSize(25) 29 | .expireAfterWrite(5, TimeUnit.MINUTES) 30 | .build()); 31 | cacheMap.put(CacheDuration.LONG, 32 | CacheBuilder.newBuilder() 33 | .maximumSize(25) 34 | .expireAfterWrite(6, TimeUnit.HOURS) 35 | .build()); 36 | } 37 | 38 | @Autowired 39 | public DataFetcher(RestTemplate restTemplate) { 40 | this.restTemplate = restTemplate; 41 | } 42 | 43 | public String fetchJson(String url, CacheDuration cacheDuration) { 44 | Optional> cacheO = Optional.ofNullable(cacheMap.get(cacheDuration)); 45 | try { 46 | return (String) cacheO 47 | .map(c -> { 48 | try { 49 | return c.get(url, () -> fetchStringFor(url)); 50 | } catch(ExecutionException ex) { 51 | throw new RuntimeException(ex.getCause().getMessage()); 52 | } 53 | }) 54 | .orElseGet(() -> fetchStringFor(url)); 55 | } catch(Exception ex) { 56 | logger.error(String.format("Error while fetching %s", url), ex); 57 | throw new RuntimeException(ex.getMessage()); 58 | } 59 | } 60 | 61 | public Document fetchDoc(String url, CacheDuration cacheDuration) { 62 | Optional> cacheO = Optional.ofNullable(cacheMap.get(cacheDuration)); 63 | try { 64 | return (Document) cacheO 65 | .map(c -> { 66 | try { 67 | return c.get(url, () -> fetchDocFor(url)); 68 | } catch(ExecutionException ex) { 69 | throw new RuntimeException(ex.getCause().getMessage()); 70 | } 71 | }) 72 | .orElseGet(() -> fetchDocFor(url)); 73 | } catch(Exception ex) { 74 | logger.error(String.format("Error while fetching %s", url), ex); 75 | throw new RuntimeException(ex.getMessage()); 76 | } 77 | } 78 | 79 | private String fetchStringFor(String url) { 80 | try { 81 | return restTemplate.getForObject(url, String.class); 82 | } catch(Exception ex) { 83 | throw new RuntimeException(ex); 84 | } 85 | } 86 | 87 | private Document fetchDocFor(String url) { 88 | try { 89 | return Jsoup.connect(url).get(); 90 | } catch(IOException e) { 91 | throw new RuntimeException(e); 92 | } 93 | } 94 | 95 | public enum CacheDuration { 96 | SHORT, 97 | LONG, 98 | NONE 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/ProblemsService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.Site; 5 | import com.codedrills.model.Tag; 6 | import com.codedrills.model.recommendation.ProblemDifficulty; 7 | import com.codedrills.service.db.ProblemsDao; 8 | import com.codedrills.service.sites.SiteService; 9 | import org.apache.log4j.Logger; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.ApplicationListener; 12 | import org.springframework.context.event.ContextRefreshedEvent; 13 | import org.springframework.scheduling.annotation.Scheduled; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.util.*; 18 | import java.util.concurrent.ConcurrentHashMap; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.IntStream; 21 | 22 | @Service 23 | @Transactional 24 | public class ProblemsService implements ApplicationListener { 25 | private static Logger logger = Logger.getLogger(ProblemsService.class); 26 | 27 | private final static long REFETCH_DELAY = 12L * 60 * 60 * 1000; 28 | private static final long INITIAL_REFETCH_DELAY = 1L * 60 * 60 * 1000; 29 | private final ConcurrentHashMap problemsByUid = new ConcurrentHashMap<>(); 30 | private final ConcurrentHashMap> problemsByTag = new ConcurrentHashMap<>(); 31 | 32 | @Autowired 33 | private SiteService siteService; 34 | 35 | @Autowired 36 | private ProblemsDao problemsDao; 37 | 38 | 39 | @Override 40 | public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { 41 | if(problemsByUid.isEmpty()) { 42 | loadProblems(); 43 | } 44 | } 45 | 46 | public void loadProblems() { 47 | logger.info("Loading problems from db"); 48 | List problems = problemsDao.findAll(); 49 | addProblems(problems); 50 | processBySite(); 51 | logger.info(String.format("Fetched %d problems from db", problems.size())); 52 | if(problems.isEmpty()) { 53 | refetchProblems(); 54 | } 55 | } 56 | 57 | @Scheduled(fixedDelay = REFETCH_DELAY, initialDelay = INITIAL_REFETCH_DELAY) 58 | public void refetchProblems() { 59 | logger.info("Refetching problems"); 60 | int previousSize = problemsByUid.size(); 61 | List problems = siteService.fetchProblemData(); 62 | addProblems(problems); 63 | saveProblems(problems); 64 | processBySite(); 65 | logger.info(String.format("Total %d(+%d) problems", problemsByUid.size(), problemsByUid.size() - previousSize)); 66 | } 67 | 68 | private void saveProblems(List problems) { 69 | problems.stream() 70 | .forEach(problemsDao::save); 71 | } 72 | 73 | private void processBySite() { 74 | Map> problemsBySite = new HashMap<>(); 75 | problemsByUid.values() 76 | .stream() 77 | .forEach(p -> problemsBySite.computeIfAbsent(p.getSite(), __ -> new ArrayList<>()).add(p)); 78 | 79 | problemsBySite.values() 80 | .stream() 81 | .forEach(this::postProcess); 82 | } 83 | 84 | private void addProblems(List problems) { 85 | Map problemsByUid = new HashMap<>(); 86 | problems 87 | .stream() 88 | .forEach(p -> { 89 | String uid = p.getUid(); 90 | if(problemsByUid.containsKey(uid)) { 91 | problemsByUid.get(uid).getTags().addAll(p.getTags()); 92 | } else { 93 | problemsByUid.put(uid, p); 94 | } 95 | }); 96 | 97 | this.problemsByUid.putAll(problemsByUid); 98 | problemsByUid.values() 99 | .stream() 100 | .forEach(p -> p.getTags().stream() 101 | .forEach(t -> problemsByTag.computeIfAbsent(t, __ -> new HashSet<>()).add(p))); 102 | 103 | } 104 | 105 | private void postProcess(List problems) { 106 | normalize(problems); 107 | } 108 | 109 | /* Sort problems by descending order of submissions. score = relative order of ranks */ 110 | private void normalize(List problems) { 111 | final List sortedProblems = problems.stream() 112 | .sorted(Comparator.comparingInt(Problem::getSolved).reversed()) 113 | .collect(Collectors.toList()); 114 | 115 | IntStream.range(0, problems.size()) 116 | .forEach(i -> { 117 | Problem p = sortedProblems.get(i); 118 | p.setScore((i+1) * 100.0 / problems.size()); 119 | p.setDifficulty(ProblemDifficulty.getDifficulty(p.getScore())); 120 | }); 121 | } 122 | 123 | public Map filteredProblemsByUid(Set sites) { 124 | Map problems = new HashMap<>(); 125 | problemsByUid.entrySet() 126 | .stream() 127 | .filter(e -> sites.contains(e.getValue().getSite())) 128 | .forEach(e -> problems.put(e.getKey(), e.getValue())); 129 | 130 | return problems; 131 | } 132 | 133 | public Map> filteredProblemsByTag(Set sites) { 134 | Map> problems = new HashMap<>(); 135 | 136 | problemsByTag.entrySet().stream() 137 | .forEach(e -> problems.put(e.getKey(), 138 | e.getValue().stream() 139 | .filter(p -> sites.contains(p.getSite())) 140 | .collect(Collectors.toSet()) 141 | ) 142 | ); 143 | 144 | return problems; 145 | } 146 | 147 | public ConcurrentHashMap getProblemsByUid() { 148 | return problemsByUid; 149 | } 150 | 151 | public ConcurrentHashMap> getProblemsByTag() { 152 | return problemsByTag; 153 | } 154 | 155 | public Problem getProblemById(String id) { 156 | return problemsByUid.get(id); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/ProfileService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.analysis.AnalysisContext; 5 | import com.codedrills.model.analysis.AnalysisResult; 6 | import com.codedrills.model.recommendation.PracticeRecommendations; 7 | import com.codedrills.model.recommendation.RecommendationContext; 8 | import com.codedrills.service.recommenders.RecommenderService; 9 | import com.codedrills.util.HandleHelper; 10 | import org.apache.log4j.Logger; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | 14 | import java.util.*; 15 | 16 | import static com.codedrills.util.HandleHelper.canonicalHandles; 17 | 18 | @Service 19 | public class ProfileService { 20 | private static Logger logger = Logger.getLogger(ProfileService.class); 21 | 22 | @Autowired 23 | AnalysisService analysisService; 24 | @Autowired 25 | RecommenderService recommenderService; 26 | 27 | public AnalysisResult analyzeAndRecommend(String handlesQuery) { 28 | 29 | List handles = HandleHelper.validateAndSplit(handlesQuery); 30 | 31 | logger.info(String.format("Analysis started for %s", handlesQuery)); 32 | AnalysisContext analysisContext = analysisService.analyzeUser(handles); 33 | logger.info(String.format("Analysis completed for %s", handlesQuery)); 34 | 35 | logger.info(String.format("Recommendations started for %s", handlesQuery)); 36 | RecommendationContext recommendationContext = recommenderService.recommendations(analysisContext); 37 | PracticeRecommendations practiceRecommendations = new PracticeRecommendations(recommendationContext.getRecommendations()); 38 | logger.info(String.format("Recommendations completed for %s", handlesQuery)); 39 | 40 | return new AnalysisResult(canonicalHandles(handles), analysisContext.getUserStats(), practiceRecommendations); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/contests/CalendarService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.contests; 2 | 3 | import biweekly.Biweekly; 4 | import biweekly.ICalendar; 5 | import biweekly.component.VEvent; 6 | import com.codedrills.model.Contest; 7 | import com.google.common.base.Charsets; 8 | import org.apache.commons.io.IOUtils; 9 | import org.apache.log4j.Logger; 10 | import org.springframework.scheduling.annotation.Scheduled; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.net.HttpURLConnection; 14 | import java.net.URL; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | 18 | 19 | @Service 20 | public class CalendarService { 21 | private static Logger logger = Logger.getLogger(CalendarService.class); 22 | private static String CALENDAR_URL = "https://calendar.google.com/calendar/ical/codedrills%40gmail.com/public/basic.ics"; 23 | List contests; 24 | 25 | public CalendarService() { 26 | fetchContests(); 27 | } 28 | 29 | @Scheduled(fixedDelay = 30 * 60 * 1000, initialDelay = 60 * 1000) 30 | public void fetchContests() { 31 | logger.info("Fetching calendar"); 32 | ICalendar ical = Biweekly.parse(executeGET(CALENDAR_URL)).first(); 33 | List events = ical.getEvents(); 34 | contests = events.stream() 35 | .map(e -> new Contest(e)) 36 | .collect(Collectors.toList()); 37 | } 38 | 39 | public static String executeGET(String targetURL) { 40 | HttpURLConnection connection = null; 41 | 42 | try { 43 | URL url = new URL(targetURL); 44 | connection = (HttpURLConnection) url.openConnection(); 45 | 46 | return IOUtils.toString(connection.getInputStream(), Charsets.UTF_8); 47 | } catch(Exception ex) { 48 | logger.error("Error when fetching calendar", ex); 49 | return null; 50 | } finally { 51 | if(connection != null) { 52 | connection.disconnect(); 53 | } 54 | } 55 | } 56 | 57 | public List getContests() { 58 | return contests; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/contests/ContestsService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.contests; 2 | 3 | import com.codedrills.model.Contest; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.io.IOException; 8 | import java.security.GeneralSecurityException; 9 | import java.util.*; 10 | import java.util.stream.Collectors; 11 | 12 | @Service 13 | public class ContestsService { 14 | @Autowired 15 | CalendarService calendarService; 16 | 17 | public Map> fetchContests(TimeZone useTZ) { 18 | List contests = calendarService.getContests() 19 | .stream() 20 | .filter(c -> c.endsLater()) 21 | .collect(Collectors.toList()); 22 | 23 | Map> typeContestsMap = contests.stream() 24 | .collect(Collectors.groupingBy(c -> c.getLiveOrUpcoming())); 25 | 26 | Map> orderedContestList = new LinkedHashMap<>(); 27 | orderedContestList.put("live", changeToTimezone(nullToEmptyList(typeContestsMap.get("live")), useTZ)); 28 | orderedContestList.put("upcoming", changeToTimezone(nullToEmptyList(typeContestsMap.get("upcoming")), useTZ)); 29 | 30 | orderedContestList.values() 31 | .stream() 32 | .forEach(l -> l.sort(Comparator.comparing(Contest::getStartTime))); 33 | return orderedContestList; 34 | } 35 | 36 | private List changeToTimezone(List contests, TimeZone tz) { 37 | return contests.stream() 38 | .peek(c -> c.setTimezone(tz)) 39 | .collect(Collectors.toList()); 40 | } 41 | 42 | private List nullToEmptyList(List list) { 43 | return Optional.ofNullable(list) 44 | .orElseGet(Collections::emptyList); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/db/ProblemsDao.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.db; 2 | 3 | import com.codedrills.model.Problem; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface ProblemsDao extends CrudRepository { 11 | @SuppressWarnings("unchecked") 12 | Problem save(Problem problem); 13 | 14 | List findAll(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/db/RecommendationsDao.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.db; 2 | 3 | import com.codedrills.model.recommendation.Recommendation; 4 | import org.springframework.data.repository.CrudRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface RecommendationsDao extends CrudRepository { 9 | Recommendation save(Recommendation recommendation); 10 | 11 | Recommendation findOne(String id); 12 | 13 | boolean exists(String id); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/AbstractRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.recommendation.RecommendationContext; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | import java.util.stream.Stream; 10 | 11 | public abstract class AbstractRecommender implements RecommenderInterface { 12 | @Override 13 | public abstract void recommend(RecommendationContext context); 14 | 15 | protected List uniformSelect(List solved, List unsolved, int len) { 16 | 17 | if(solved.size() <= 3) { 18 | return unsolved.stream() 19 | .limit(len) 20 | .collect(Collectors.toList()); 21 | } 22 | 23 | List easy = getEasy(solved, unsolved).collect(Collectors.toList()); 24 | List medium = getMedium(solved, unsolved).collect(Collectors.toList()); 25 | List hard = getHard(solved, unsolved).collect(Collectors.toList()); 26 | 27 | int easyLen = Math.min(len / 3, easy.size()); 28 | int hardLen = Math.min(len / 3, hard.size()); 29 | 30 | int maxLen = Math.min(len, easy.size() + medium.size() + hard.size()); 31 | int rem = Math.max(0, maxLen - easyLen - hardLen - medium.size()); 32 | int fromHard = Math.min(rem, hard.size() - hardLen); 33 | rem -= fromHard; 34 | hardLen += fromHard; 35 | 36 | int fromEasy = Math.min(rem, easy.size() - easyLen); 37 | rem -= fromEasy; 38 | easyLen += fromEasy; 39 | 40 | 41 | return Stream.concat( 42 | Stream.concat( 43 | easy.stream().limit(easyLen), 44 | hard.stream().limit(hardLen) 45 | ), 46 | medium.stream() 47 | ) 48 | .distinct() 49 | .limit(len) 50 | .collect(Collectors.toList()); 51 | } 52 | 53 | protected Stream getEasy(List solved, List unsolved) { 54 | return selectProblems(0, 70, solved, unsolved); 55 | } 56 | 57 | protected Stream getHard(List solved, List unsolved) { 58 | return selectProblems(94, 100, solved, unsolved); 59 | } 60 | 61 | protected Stream getMedium(List solved, List unsolved) { 62 | return selectProblems(71, 93, solved, unsolved); 63 | } 64 | 65 | private Stream selectProblems(double start, double end, List solved, List unsolved) { 66 | double lowScore = getIth(solved, start); 67 | double highScore = getIth(solved, end); 68 | 69 | List slice = unsolved.stream() 70 | .filter(p -> p.getScore() >= lowScore && p.getScore() <= highScore) 71 | .collect(Collectors.toList()); 72 | Collections.shuffle(slice); 73 | 74 | return slice.stream(); 75 | } 76 | 77 | private double getIth(List solved, double start) { 78 | int idx = (int) Math.floor(solved.size() * start / 100.0); 79 | if(idx < 0) idx = 0; 80 | if(idx >= solved.size()) return 100; 81 | 82 | return solved.get(idx).getScore(); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/DailyPracticeRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.recommendation.Recommendation; 4 | import com.codedrills.model.recommendation.RecommendationContext; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.stream.Collectors; 8 | import java.util.stream.Stream; 9 | 10 | @Service 11 | public class DailyPracticeRecommender extends AbstractRecommender { 12 | @Override 13 | public void recommend(RecommendationContext context) { 14 | Recommendation recommendation = new Recommendation( 15 | "Daily Practice", 16 | "One easy and medium problem for quick daily practice", 17 | Stream.concat( 18 | getEasy(context.getSolvedSorted(), context.getUnsolvedSorted()).limit(1), 19 | getMedium(context.getSolvedSorted(), context.getUnsolvedSorted()).limit(1) 20 | ).limit(2).collect(Collectors.toList()), 21 | 200 22 | ); 23 | 24 | context.addRecommendation(recommendation); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/EasyMediumHardRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.recommendation.Recommendation; 4 | import com.codedrills.model.recommendation.RecommendationContext; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.stream.Collectors; 8 | 9 | @Service 10 | public class EasyMediumHardRecommender extends AbstractRecommender { 11 | @Override 12 | public void recommend(RecommendationContext context) { 13 | context.addRecommendation(new Recommendation( 14 | "Easy", 15 | "Easy problems", 16 | getEasy(context.getSolvedSorted(), context.getUnsolvedSorted()).limit(10).collect(Collectors.toList()), 17 | 425 18 | ) 19 | ); 20 | 21 | context.addRecommendation(new Recommendation( 22 | "Medium", 23 | "Medium problems", 24 | getMedium(context.getSolvedSorted(), context.getUnsolvedSorted()).limit(10).collect(Collectors.toList()), 25 | 450 26 | ) 27 | ); 28 | 29 | context.addRecommendation(new Recommendation( 30 | "Hard", 31 | "Hard problems", 32 | getHard(context.getSolvedSorted(), context.getUnsolvedSorted()).limit(10).collect(Collectors.toList()), 33 | 475 34 | ) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/ICPCRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.recommendation.Recommendation; 4 | import com.codedrills.model.recommendation.RecommendationContext; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class ICPCRecommender extends AbstractRecommender { 9 | @Override 10 | public void recommend(RecommendationContext context) { 11 | context.addRecommendation(new Recommendation( 12 | "ICPC", 13 | "Recommendations for ICPC team practice. Best used when all team handles are entered", 14 | uniformSelect(context.getSolvedSorted(), context.getUnsolvedSorted(), 10), 15 | 500 16 | )); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/MiniContestRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.recommendation.Recommendation; 4 | import com.codedrills.model.recommendation.RecommendationContext; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | public class MiniContestRecommender extends AbstractRecommender { 9 | @Override 10 | public void recommend(RecommendationContext context) { 11 | context.addRecommendation(new Recommendation( 12 | "Mini Contest", 13 | "5 problems - ideal for a 2 hour solo/team practice", 14 | uniformSelect(context.getSolvedSorted(), context.getUnsolvedSorted(), 5), 15 | 600 16 | ) 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/RandomRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.recommendation.Recommendation; 5 | import com.codedrills.model.recommendation.RecommendationContext; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | @Service 14 | public class RandomRecommender extends AbstractRecommender { 15 | @Override 16 | public void recommend(RecommendationContext context) { 17 | List unsolved = new ArrayList<>(context.getUnsolved()); 18 | Collections.shuffle(unsolved); 19 | List rec = unsolved.stream() 20 | .limit(10) 21 | .collect(Collectors.toList()); 22 | 23 | Recommendation recommendation = new Recommendation( "Random", 24 | "Random practice problems", 25 | rec, 26 | 1000); 27 | context.addRecommendation(recommendation); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/RecommenderInterface.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.recommendation.RecommendationContext; 4 | 5 | public interface RecommenderInterface { 6 | void recommend(RecommendationContext context); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/RecommenderService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.analysis.AnalysisContext; 5 | import com.codedrills.model.exceptions.InvalidInputException; 6 | import com.codedrills.model.recommendation.ProblemStatus; 7 | import com.codedrills.model.recommendation.Recommendation; 8 | import com.codedrills.model.recommendation.RecommendationContext; 9 | import com.codedrills.service.AnalysisService; 10 | import com.codedrills.service.ProblemsService; 11 | import com.codedrills.service.db.RecommendationsDao; 12 | import com.codedrills.util.HandleHelper; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.util.List; 18 | import java.util.Set; 19 | import java.util.stream.Collectors; 20 | 21 | import static com.google.common.base.Strings.isNullOrEmpty; 22 | 23 | @Service 24 | @Transactional 25 | public class RecommenderService { 26 | private final List recommenders; 27 | private final ProblemsService problemsService; 28 | @Autowired 29 | private RecommendationsDao recommendationsDao; 30 | @Autowired 31 | AnalysisService analysisService; 32 | 33 | @Autowired 34 | public RecommenderService(List recommenders, ProblemsService problemsService) { 35 | this.recommenders = recommenders; 36 | this.problemsService = problemsService; 37 | } 38 | 39 | public RecommendationContext computeRecommendations(RecommendationContext context) { 40 | recommenders.stream() 41 | .forEach(r -> r.recommend(context)); 42 | 43 | return context; 44 | } 45 | 46 | public RecommendationContext recommendations(AnalysisContext analysisContext) { 47 | RecommendationContext recommendationContext = new RecommendationContext( 48 | problemsService.filteredProblemsByUid(HandleHelper.getSites(analysisContext.getHandles())), 49 | problemsService.filteredProblemsByTag(HandleHelper.getSites(analysisContext.getHandles())), 50 | analysisContext 51 | ); 52 | 53 | computeRecommendations(recommendationContext); 54 | 55 | recommendationContext.getRecommendations() 56 | .stream() 57 | .forEach(recommendationsDao::save); 58 | 59 | return recommendationContext; 60 | } 61 | 62 | public Recommendation getRecommendation(String id) { 63 | if(isNullOrEmpty(id)) { 64 | throw new InvalidInputException("Given recommendation id is invalid"); 65 | } 66 | if(!recommendationsDao.exists(id)) { 67 | throw new InvalidInputException("No such recommendation exists"); 68 | } 69 | 70 | Recommendation recommendation = recommendationsDao.findOne(id); 71 | AnalysisContext analysisContext = analysisService.analyzeUser(HandleHelper.splitHandles(recommendation.getHandles())); 72 | Set solvedIds = analysisContext.getSortedSolved().stream() 73 | .map(Problem::getUid) 74 | .collect(Collectors.toSet()); 75 | 76 | recommendation.getPracticeProblems() 77 | .stream() 78 | .forEach(p -> { 79 | Problem problem = problemsService.getProblemById(p.getProblem().getUid()); 80 | p.getProblem().setScore(problem.getScore()); 81 | p.getProblem().setDifficulty(problem.getDifficulty()); 82 | if(solvedIds.contains(p.getProblem().getUid())) { 83 | p.setStatus(ProblemStatus.SOLVED); 84 | } else { 85 | p.setStatus(ProblemStatus.UNSOLVED); 86 | } 87 | } 88 | ); 89 | 90 | recommendation.getPracticeProblems().sort((p1, p2) -> Problem.compareByScore(p1.getProblem(), p2.getProblem())); 91 | 92 | 93 | return recommendation; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/StrongWeakRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.Tag; 5 | import com.codedrills.model.analysis.TagScore; 6 | import com.codedrills.model.recommendation.Recommendation; 7 | import com.codedrills.model.recommendation.RecommendationContext; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | import static java.util.Collections.emptyList; 14 | 15 | @Service 16 | public class StrongWeakRecommender extends AbstractRecommender { 17 | 18 | @Override 19 | public void recommend(RecommendationContext context) { 20 | context.addRecommendation( 21 | new Recommendation( 22 | "Strong Topics", 23 | "Double down on your strong topics by solving problems in your strong area", 24 | recommendForTags(context, getTags(context.getUserStats().getStrong())), 25 | 700 26 | ) 27 | ); 28 | 29 | context.addRecommendation( 30 | new Recommendation( 31 | "Weak Topics", 32 | "Improve your range of topics by solving problems in your weak area", 33 | recommendForTags(context, getTags(context.getUserStats().getWeak())), 34 | 300 35 | ) 36 | ); 37 | } 38 | 39 | private List getTags(List tagScores) { 40 | return tagScores.stream() 41 | .map(TagScore::getTag) 42 | .collect(Collectors.toList()); 43 | } 44 | 45 | private List recommendForTags(RecommendationContext context, List tags) { 46 | List solved = tags.stream() 47 | .map(t -> context.getSortedSolvedByTags().getOrDefault(t, emptyList())) 48 | .flatMap(List::stream) 49 | .distinct() 50 | .sorted(Problem::compareByScore) 51 | .collect(Collectors.toList()); 52 | 53 | List unsolved = tags.stream() 54 | .map(t -> context.getSortedUnsolvedByTags().getOrDefault(t, emptyList())) 55 | .flatMap(List::stream) 56 | .distinct() 57 | .sorted(Problem::compareByScore) 58 | .collect(Collectors.toList()); 59 | 60 | return uniformSelect(solved, unsolved, 10); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/TagRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.Tag; 5 | import com.codedrills.model.recommendation.Recommendation; 6 | import com.codedrills.model.recommendation.RecommendationContext; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import static java.util.Collections.emptyList; 14 | 15 | @Service 16 | public class TagRecommender extends AbstractRecommender { 17 | private final static Map tagTypeRank; 18 | 19 | static { 20 | tagTypeRank = new HashMap<>(); 21 | tagTypeRank.put(Tag.TagType.TECHNIQUE, 825); 22 | tagTypeRank.put(Tag.TagType.TOPIC, 850); 23 | tagTypeRank.put(Tag.TagType.UNTAGGED, 900); 24 | } 25 | 26 | @Override 27 | public void recommend(RecommendationContext context) { 28 | context.getProblemsByTags().entrySet() 29 | .stream() 30 | .forEach(e -> recommendForTag(context, e.getKey())); 31 | } 32 | 33 | private void recommendForTag(RecommendationContext context, Tag tag) { 34 | List solved = context.getSortedSolvedByTags().getOrDefault(tag, emptyList()); 35 | List unsolved = context.getSortedUnsolvedByTags().getOrDefault(tag, emptyList()); 36 | 37 | Recommendation recommendation = new Recommendation( 38 | tag.getFullName(), 39 | "Practice problems for " + tag.getFullName(), 40 | uniformSelect(solved, unsolved, 10), 41 | tagTypeRank.get(tag.getType()) 42 | ); 43 | 44 | context.addRecommendation(recommendation); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/recommenders/WarmupRecommender.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.recommenders; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.recommendation.Recommendation; 5 | import com.codedrills.model.recommendation.RecommendationContext; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | @Service 13 | public class WarmupRecommender extends AbstractRecommender { 14 | @Override 15 | public void recommend(RecommendationContext context) { 16 | List from = context.getUnsolved() 17 | .stream() 18 | .limit(20) 19 | .collect(Collectors.toList()); 20 | Collections.shuffle(from); 21 | 22 | List problems = from.stream() 23 | .limit(2) 24 | .collect(Collectors.toList()); 25 | 26 | Recommendation recommendation = new Recommendation( 27 | "Warmup", 28 | "Warmup problems for before a contest or a long practice session", 29 | problems, 30 | 100 31 | ); 32 | 33 | context.addRecommendation(recommendation); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/sites/AbstractSiteService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.sites; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.model.Site; 5 | import com.codedrills.model.Tag; 6 | import com.codedrills.service.DataFetcher; 7 | import com.codedrills.util.TagHelper; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | public abstract class AbstractSiteService implements SiteInterface { 14 | @Autowired 15 | protected DataFetcher dataFetcher; 16 | 17 | abstract public Site site(); 18 | 19 | abstract public List fetchProblems(); 20 | 21 | protected List mapTags(List tagsStr) { 22 | List tags = tagsStr.stream() 23 | .map(t -> TagHelper.mapTag(site(), t)) 24 | .distinct() 25 | .filter(t -> !t.equals(Tag.UNTAGGED)) 26 | .collect(Collectors.toList()); 27 | 28 | if(tags.isEmpty()) tags.add(Tag.UNTAGGED); 29 | 30 | return tags; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/sites/AbstractTagProblemFetcher.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.sites; 2 | 3 | import com.codedrills.model.Problem; 4 | import com.codedrills.util.Helper; 5 | import com.codedrills.util.TagHelper; 6 | import org.apache.log4j.Logger; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | @Component 15 | public abstract class AbstractTagProblemFetcher extends AbstractSiteService { 16 | private static Logger logger = Logger.getLogger(AbstractTagProblemFetcher.class); 17 | @Value("${tags_limit}") 18 | private int tagFetchLimit; 19 | private List pendingTags; 20 | 21 | protected abstract List fetchForTag(String tag); 22 | 23 | @Override 24 | public List fetchProblems() { 25 | logger.info(String.format("Fetching problems from %s", site().getShortName())); 26 | pendingTags = TagHelper.tagsOfSite(site()) 27 | .stream() 28 | .limit(tagFetchLimit) 29 | .collect(Collectors.toList()); 30 | 31 | List problems = new ArrayList<>(); 32 | 33 | int trials = 10; 34 | while(pendingTags.size() > 0 && trials > 0) { 35 | --trials; 36 | problems.addAll(tryFetch()); 37 | Helper.sleep(5 * 1000); 38 | } 39 | 40 | return problems; 41 | } 42 | 43 | private List tryFetch() { 44 | List errorTags = new ArrayList<>(); 45 | List problems = pendingTags 46 | .stream() 47 | .peek(__ -> Helper.sleep(100)) 48 | .map(t -> { 49 | try { 50 | return fetchForTag(t); 51 | } catch(Exception ex) { 52 | logger.warn(String.format("Error while fetching for tag %s for site %s", t, site().getShortName()), ex); 53 | errorTags.add(t); 54 | return new ArrayList(); 55 | } 56 | }) 57 | .flatMap(List::stream) 58 | .collect(Collectors.toList()); 59 | 60 | logger.info(String.format("For site %s, fetched %d tags", site().getShortName(), pendingTags.size() - errorTags.size())); 61 | pendingTags = errorTags; 62 | return problems; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/sites/Codechef.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.sites; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Problem; 5 | import com.codedrills.model.Site; 6 | import com.codedrills.model.analysis.Rating; 7 | import com.codedrills.model.cc.CCProblemFetchResponse; 8 | import com.codedrills.model.stats.UserStats; 9 | import com.codedrills.util.Helper; 10 | import com.codedrills.util.VerdictHelper; 11 | import org.apache.log4j.Logger; 12 | import org.jsoup.nodes.Document; 13 | import org.jsoup.select.Elements; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.HashSet; 17 | import java.util.List; 18 | import java.util.Set; 19 | import java.util.stream.Collectors; 20 | 21 | import static com.codedrills.model.Site.CODECHEF; 22 | import static com.codedrills.service.DataFetcher.CacheDuration.NONE; 23 | import static com.codedrills.service.DataFetcher.CacheDuration.SHORT; 24 | 25 | @Service 26 | public class Codechef extends AbstractTagProblemFetcher { 27 | private static Logger logger = Logger.getLogger(Codechef.class); 28 | private static String FETCH_PROBLEM_URL = "https://www.codechef.com/get/tags/problems/%s"; 29 | private static String PROBLEM_URL = "https://www.codechef.com/problems/%s"; 30 | private static String USER_URL = "https://www.codechef.com/users/%s"; 31 | private static Set verdicts = new HashSet<>(VerdictHelper.verdictsFor(CODECHEF)); 32 | 33 | @Override 34 | public Site site() { 35 | return CODECHEF; 36 | } 37 | 38 | @Override 39 | public UserStats fetchUserStats(Handle handle) { 40 | String user = handle.getHandle(); 41 | logger.info(String.format("Fetching cc stats %s ", user)); 42 | try { 43 | Document doc = dataFetcher.fetchDoc(String.format(USER_URL, user), SHORT); 44 | UserStats userStats = new UserStats(); 45 | 46 | parseSubmissions(userStats, doc); 47 | parseRating(handle, userStats, doc); 48 | 49 | logger.info(String.format("Fetching completed cc stats %s ", user)); 50 | return userStats; 51 | } catch(Exception ex) { 52 | logger.warn(String.format("Error while fetching cc submissions for %s", user), ex); 53 | throw new RuntimeException(ex.getCause()); 54 | } 55 | } 56 | 57 | private void parseSubmissions(UserStats userStats, Document doc) { 58 | 59 | Elements solvedElements = doc.select("section.problems-solved div.content a"); 60 | solvedElements.stream() 61 | .map(e -> e.ownText()) 62 | .map(this::computeUid) 63 | .forEach(userStats::addSolved); 64 | 65 | String body = doc.html(); 66 | int idx1 = body.indexOf("{name:'"); 67 | int idx2 = body.indexOf("}]", idx1); 68 | String array = body.substring(idx1, idx2); 69 | String[] parts = array.split(","); 70 | 71 | for(int i = 0; i < parts.length - 1; ++i) { 72 | String v = getKey(parts[i]); 73 | if(verdicts.contains(v)) { 74 | userStats.addVerdict(VerdictHelper.mapVerdict(site(), v), Integer.parseInt(getVal(parts[i + 1]))); 75 | ++i; 76 | } 77 | } 78 | } 79 | 80 | private void parseRating(Handle handle, UserStats userStats, Document doc) { 81 | Elements ratingElements = doc.select("aside.sidebar div.rating-header div.rating-number"); 82 | Double current = Helper.parseRatingNumber(ratingElements.get(0).text()); 83 | 84 | ratingElements = doc.select("aside.sidebar div.rating-header small"); 85 | String text = ratingElements.get(0).text().split(" ")[2]; 86 | text = text.substring(0, text.length() - 1); 87 | 88 | Double highest = Helper.parseRatingNumber(text); 89 | 90 | userStats.addRating(handle, new Rating(current, highest)); 91 | 92 | } 93 | 94 | private String getKey(String part) { 95 | if(!part.contains(":")) return ""; 96 | String key = part.split(":")[1]; 97 | if(key.startsWith("'")) key = key.substring(1); 98 | if(key.endsWith("'")) key = key.substring(0, key.length() - 1); 99 | return key; 100 | } 101 | 102 | private String getVal(String part) { 103 | String count = part.trim().split(":")[1]; 104 | if(count.endsWith("}")) count = count.substring(0, count.length() - 1); 105 | return count; 106 | } 107 | 108 | @Override 109 | protected List fetchForTag(String tag) { 110 | logger.info(String.format("Fetching cc problems for tag %s", tag)); 111 | 112 | CCProblemFetchResponse response; 113 | response = Helper.gson.fromJson( 114 | dataFetcher.fetchJson(String.format(FETCH_PROBLEM_URL, tag), NONE), 115 | CCProblemFetchResponse.class 116 | ); 117 | 118 | return response.getAll_problems() 119 | .values() 120 | .stream() 121 | .map(cc -> new Problem( 122 | site(), 123 | computeUid(cc.getCode()), 124 | String.format(PROBLEM_URL, cc.getCode()), 125 | cc.getName(), 126 | mapTags(cc.getTags()), 127 | cc.getSolved_by() 128 | ) 129 | ) 130 | .collect(Collectors.toList()); 131 | } 132 | 133 | private String computeUid(String code) { 134 | return "cc:" + code; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/sites/Codeforces.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.sites; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Problem; 5 | import com.codedrills.model.Site; 6 | import com.codedrills.model.Verdict; 7 | import com.codedrills.model.analysis.Rating; 8 | import com.codedrills.model.cf.*; 9 | import com.codedrills.model.exceptions.InvalidInputException; 10 | import com.codedrills.model.stats.UserStats; 11 | import com.codedrills.service.DataFetcher; 12 | import com.codedrills.util.VerdictHelper; 13 | import org.apache.log4j.Logger; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.*; 17 | import java.util.function.Function; 18 | import java.util.stream.Collectors; 19 | 20 | import static com.codedrills.model.Site.CODEFORCES; 21 | import static com.codedrills.service.DataFetcher.CacheDuration.NONE; 22 | import static com.codedrills.service.DataFetcher.CacheDuration.SHORT; 23 | import static com.codedrills.util.Helper.gson; 24 | import static java.util.Optional.ofNullable; 25 | 26 | @Service 27 | public class Codeforces extends AbstractSiteService { 28 | private static Logger logger = Logger.getLogger(Codeforces.class); 29 | 30 | public final static List slrs = Arrays.asList(683, 470, 188, 153, 130, 100, 72, 64); 31 | public final static List blacklistedHandles = Arrays.asList("vjudge1", "vjudge2", "vjudge3", "vjudge4", "vjudge5"); 32 | 33 | private final static String PROBLEM_SET_URL = "https://codeforces.com/api/problemset.problems"; 34 | private final static String PROBLEM_URL = "https://codeforces.com/problemset/problem/%s/%s"; 35 | private final static String USER_STATUS_URL = "https://codeforces.com/api/user.status?handle=%s&from=%d&count=%d"; 36 | private final static String USER_INFO_URL = "https://codeforces.com/api/user.info?handles=%s"; 37 | private final static int MAX_SUB_FETCH_COUNT = 3000; 38 | private final static int MAX_SUB_COUNT = 5 * MAX_SUB_FETCH_COUNT; 39 | 40 | 41 | @Override 42 | public Site site() { 43 | return CODEFORCES; 44 | } 45 | 46 | @Override 47 | public List fetchProblems() { 48 | logger.info("Fetching problems from cf"); 49 | CFProblemSetResponse response = gson.fromJson( 50 | dataFetcher.fetchJson(PROBLEM_SET_URL, NONE), 51 | CFProblemSetResponse.class 52 | ); 53 | return processProblemSet(response); 54 | } 55 | 56 | @Override 57 | public UserStats fetchUserStats(Handle handle) { 58 | String user = handle.getHandle(); 59 | if(blacklistedHandles.contains(user.toLowerCase())) { 60 | throw new InvalidInputException(String.format("Can't process cf handle %s", user)); 61 | } 62 | logger.info(String.format("Fetching cf stats %s ", user)); 63 | List submissions = new ArrayList<>(); 64 | int start = 1; 65 | do { 66 | logger.info(String.format("Fetching cf stats %s(%d-) ", user, start)); 67 | CFUserStatusResponse response = gson.fromJson( 68 | dataFetcher.fetchJson(String.format(USER_STATUS_URL, user, start, MAX_SUB_FETCH_COUNT), SHORT), 69 | CFUserStatusResponse.class 70 | ); 71 | submissions.addAll(response.getResult()); 72 | int cur = response.getResult().size(); 73 | start += cur; 74 | if(cur < MAX_SUB_FETCH_COUNT) { 75 | break; 76 | } 77 | if(submissions.size() >= MAX_SUB_COUNT) { 78 | throw new InvalidInputException(String.format("Submissions for cf handle %s is too large to process", user)); 79 | } 80 | } while(true); 81 | 82 | CFUserInfoResponse infoResponse = gson.fromJson( 83 | dataFetcher.fetchJson(String.format(USER_INFO_URL, user), DataFetcher.CacheDuration.SHORT), 84 | CFUserInfoResponse.class 85 | ); 86 | 87 | UserStats userStats = processSubmissions(submissions); 88 | CFUser cfUser = infoResponse.getResult().get(0); 89 | userStats.addRating(handle, new Rating( 90 | ofNullable(cfUser.getRating()).map(Double::new).orElse(null), 91 | ofNullable(cfUser.getMaxRating()).map(Double::new).orElse(null) 92 | )); 93 | 94 | logger.info(String.format("Fetching completed cf stats %s ", user)); 95 | 96 | return userStats; 97 | } 98 | 99 | private UserStats processSubmissions(List result) { 100 | UserStats userStats = new UserStats(); 101 | result.stream() 102 | .forEach(s -> { 103 | Verdict verdict = VerdictHelper.mapVerdict(site(), s.getVerdict()); 104 | userStats.addVerdict(verdict, 1); 105 | if(verdict.equals(Verdict.AC)) { 106 | userStats.addSolved(computeId(s.getProblem().getContestId(), s.getProblem().getIndex())); 107 | } 108 | }); 109 | 110 | return userStats; 111 | } 112 | 113 | private List processProblemSet(CFProblemSetResponse response) { 114 | Map cfproblems = response.getResult().getProblems() 115 | .stream() 116 | .filter(c -> c.getType().equals("PROGRAMMING")) 117 | .filter(c -> !slrs.contains(c.getContestId())) 118 | .collect(Collectors.toMap(c -> computeId(c.getContestId(), c.getIndex()), Function.identity())); 119 | 120 | Map cfstats = response.getResult().getProblemStatistics() 121 | .stream() 122 | .collect(Collectors.toMap(c -> computeId(c.getContestId(), c.getIndex()), Function.identity())); 123 | 124 | return cfproblems.entrySet() 125 | .stream() 126 | .map(e -> { 127 | String id = e.getKey(); 128 | CFProblem cfProblem = e.getValue(); 129 | return new Problem( 130 | site(), 131 | id, 132 | String.format(PROBLEM_URL, cfProblem.getContestId(), cfProblem.getIndex()), 133 | cfProblem.getName(), 134 | mapTags(cfProblem.getTags()), 135 | cfstats.get(id).getSolvedCount() 136 | ); 137 | }) 138 | .collect(Collectors.toList()); 139 | } 140 | 141 | private String computeId(int contestId, String index) { 142 | return String.format("cf:%s/%s", contestId, index); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/sites/SiteInterface.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.sites; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Problem; 5 | import com.codedrills.model.Site; 6 | import com.codedrills.model.stats.UserStats; 7 | 8 | import java.util.List; 9 | 10 | public interface SiteInterface { 11 | Site site(); 12 | 13 | List fetchProblems(); 14 | 15 | UserStats fetchUserStats(Handle handle); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/sites/SiteService.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.sites; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Problem; 5 | import com.codedrills.model.Site; 6 | import com.codedrills.model.stats.UserStats; 7 | import org.apache.log4j.Logger; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | import java.util.stream.Collectors; 15 | 16 | @Service 17 | public class SiteService { 18 | private static Logger logger = Logger.getLogger(SiteService.class); 19 | 20 | private final List siteInterfaces; 21 | private final Map interfaceMap; 22 | 23 | @Autowired 24 | public SiteService(List siteInterfaces) { 25 | this.siteInterfaces = siteInterfaces; 26 | interfaceMap = new HashMap<>(); 27 | siteInterfaces.stream() 28 | .forEach(s -> interfaceMap.put(s.site(), s)); 29 | } 30 | 31 | public List fetchProblemData() { 32 | logger.info("Fetching problem data"); 33 | return siteInterfaces 34 | .stream() 35 | .map(SiteInterface::fetchProblems) 36 | .flatMap(List::stream) 37 | .filter(Problem::isValid) 38 | .distinct() 39 | .collect(Collectors.toList()); 40 | } 41 | 42 | public List fetchSubmissionStats(List handles) { 43 | return handles 44 | .parallelStream() 45 | .unordered() 46 | .map(h -> interfaceMap.get(h.getSite()).fetchUserStats(h)) 47 | .collect(Collectors.toList()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/service/sites/Spoj.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.service.sites; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Problem; 5 | import com.codedrills.model.Site; 6 | import com.codedrills.model.analysis.Rating; 7 | import com.codedrills.model.stats.UserStats; 8 | import com.codedrills.util.Helper; 9 | import com.codedrills.util.TagHelper; 10 | import org.apache.log4j.Logger; 11 | import org.jsoup.nodes.Document; 12 | import org.jsoup.nodes.Element; 13 | import org.jsoup.select.Elements; 14 | import org.springframework.stereotype.Service; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | import static com.codedrills.model.Verdict.*; 20 | import static com.codedrills.service.DataFetcher.CacheDuration.NONE; 21 | import static com.codedrills.service.DataFetcher.CacheDuration.SHORT; 22 | import static java.util.Collections.singletonList; 23 | 24 | @Service 25 | public class Spoj extends AbstractTagProblemFetcher { 26 | private static Logger logger = Logger.getLogger(Codechef.class); 27 | private static String FETCH_PROBLEM_URL = "https://www.spoj.com/problems/tag/%s"; 28 | private static String PROBLEM_URL = "https://www.spoj.com/problems/%s"; 29 | private static String USER_URL = "https://www.spoj.com/users/%s"; 30 | 31 | @Override 32 | public UserStats fetchUserStats(Handle handle) { 33 | String user = handle.getHandle(); 34 | logger.info(String.format("Fetching sp stats %s ", user)); 35 | try { 36 | Document doc = dataFetcher.fetchDoc(String.format(USER_URL, user), SHORT); 37 | UserStats userStats = new UserStats(); 38 | 39 | parseSubmissions(userStats, doc); 40 | parsePoints(handle, userStats, doc); 41 | 42 | logger.info(String.format("Fetching completed sp stats %s ", user)); 43 | return userStats; 44 | } catch(Exception ex) { 45 | logger.warn(String.format("Error while fetching sp submissions for %s", user), ex); 46 | throw new RuntimeException(ex.getCause()); 47 | } 48 | } 49 | 50 | private void parsePoints(Handle handle, UserStats userStats, Document doc) { 51 | Elements ratingElements = doc.select("div#user-profile-left p"); 52 | String text = ratingElements.stream() 53 | .filter(e -> e.text().endsWith("points)")) 54 | .findAny() 55 | .map(e -> e.text()) 56 | .get(); 57 | 58 | String[] parts = text.split(" "); 59 | text = parts[parts.length - 2]; 60 | text = text.substring(1); 61 | 62 | Double points = Helper.parseRatingNumber(text); 63 | userStats.addRating(handle, new Rating(points, null)); 64 | } 65 | 66 | private void parseSubmissions(UserStats userStats, Document doc) { 67 | Elements solvedElements = doc.select("div#user-profile-tables table tbody tr td a"); 68 | solvedElements.stream() 69 | .map(e -> e.ownText()) 70 | .map(this::computeUid) 71 | .forEach(userStats::addSolved); 72 | 73 | String body = doc.html(); 74 | int idx1 = body.indexOf("['Submissions',"); 75 | int idx2 = body.indexOf("]", idx1); 76 | String array = body.substring(idx1 + 1, idx2); 77 | String[] parts = array.split(","); 78 | 79 | 80 | userStats.addVerdict(AC, Integer.parseInt(parts[1].trim())); 81 | userStats.addVerdict(WA, Integer.parseInt(parts[2].trim())); 82 | userStats.addVerdict(TLE, Integer.parseInt(parts[3].trim())); 83 | userStats.addVerdict(RTE, Integer.parseInt(parts[4].trim())); 84 | userStats.addVerdict(CE, Integer.parseInt(parts[5].trim())); 85 | } 86 | 87 | 88 | @Override 89 | public Site site() { 90 | return Site.SPOJ; 91 | } 92 | 93 | protected List fetchForTag(String tag) { 94 | logger.info(String.format("Fetching sp problems for tag %s", tag)); 95 | 96 | try { 97 | Document doc = dataFetcher.fetchDoc(String.format(FETCH_PROBLEM_URL, tag), NONE); 98 | Elements rows = doc.select("table.problems tbody tr"); 99 | List problems = new ArrayList<>(); 100 | 101 | for(Element row : rows) { 102 | Elements r = row.select("td"); 103 | Element nameElement = r.get(1).select("a").get(0); 104 | Element solvedElement = r.get(3).select("a").get(0); 105 | String name = nameElement.ownText(); 106 | String link = nameElement.attr("href"); 107 | String[] parts = link.split("/"); 108 | String id = parts[parts.length - 1]; 109 | int solved = Integer.parseInt(solvedElement.ownText()); 110 | 111 | Problem problem = new Problem(site(), 112 | computeUid(id), 113 | String.format(PROBLEM_URL, id), 114 | name, 115 | singletonList(TagHelper.mapTag(site(), tag)), 116 | solved 117 | ); 118 | problems.add(problem); 119 | } 120 | 121 | return problems; 122 | } catch(Exception ex) { 123 | logger.warn(String.format("Error while fetching sp problems for %s", tag), ex); 124 | throw new RuntimeException(ex.getCause()); 125 | } 126 | } 127 | 128 | private String computeUid(String id) { 129 | return String.format("%s/%s", site().getShortName(), id); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/util/HandleHelper.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.util; 2 | 3 | import com.codedrills.model.Handle; 4 | import com.codedrills.model.Site; 5 | import com.codedrills.model.exceptions.InvalidInputException; 6 | 7 | import java.util.*; 8 | import java.util.regex.Pattern; 9 | import java.util.stream.Collectors; 10 | 11 | import static java.util.Objects.isNull; 12 | 13 | public class HandleHelper { 14 | public static String HANDLES_REGEX = "^( )*(((sp/|cc/|cf/|/)*(\\w|\\.|-)+(,| )+)*((sp/|cc/|cf/|/)*(\\w|\\.|-)+))( )*$"; 15 | public static Pattern HANDLES_PATTERN = Pattern.compile(HANDLES_REGEX); 16 | public static long HANDLES_LIMIT = 10; 17 | private static Map profileUrlFormat; 18 | static { 19 | profileUrlFormat = new HashMap<>(); 20 | profileUrlFormat.put(Site.CODECHEF, "https://www.codechef.com/users/%s"); 21 | profileUrlFormat.put(Site.CODEFORCES, "http://codeforces.com/profile/%s"); 22 | profileUrlFormat.put(Site.SPOJ, "http://www.spoj.com/users/%s/"); 23 | } 24 | 25 | public static List splitHandles(String handlesQuery) { 26 | List plainHandles = Arrays.stream(handlesQuery.split("[ ,]")) 27 | .filter(s -> !isNull(s) && !s.trim().isEmpty()) 28 | .collect(Collectors.toList()); 29 | 30 | List handles = new ArrayList<>(); 31 | plainHandles.stream() 32 | .forEach(u -> { 33 | 34 | String[] parts = u.split("/"); 35 | List prefixes = new ArrayList<>(); 36 | prefixes.addAll(Arrays.asList(Arrays.copyOfRange(parts, 0, parts.length - 1))); 37 | if(prefixes.isEmpty()) { 38 | prefixes.add(""); 39 | } 40 | 41 | String handle = parts[parts.length - 1].trim(); 42 | 43 | prefixes.stream() 44 | .map(String::trim) 45 | .forEach(s -> { 46 | Site site = SiteHelper.getSite(s); 47 | handles.add(new Handle(site, handle)); 48 | }); 49 | }); 50 | 51 | return handles; 52 | } 53 | 54 | public static String canonicalHandles(List handles) { 55 | Map> siteByHandles = new HashMap<>(); 56 | handles.stream() 57 | .forEach(h -> { 58 | siteByHandles.computeIfAbsent(h.getHandle(), __ -> new LinkedList<>()).add(h.getSite()); 59 | }); 60 | 61 | return siteByHandles.entrySet().stream() 62 | .map(e -> { 63 | String prefixes = e.getValue().stream() 64 | .map(Site::getShortName) 65 | .distinct() 66 | .sorted() 67 | .collect(Collectors.joining("/", "", "/")); 68 | return prefixes + e.getKey(); 69 | }) 70 | .distinct() 71 | .sorted() 72 | .collect(Collectors.joining(" ")); 73 | } 74 | 75 | public static List validateAndSplit(String handlesQuery) { 76 | if(!HandleHelper.HANDLES_PATTERN.matcher(handlesQuery).matches()) { 77 | throw new InvalidInputException("The handle(s) you entered were not in the correct format"); 78 | } 79 | 80 | List handles = HandleHelper.splitHandles(handlesQuery); 81 | 82 | if(handles.size() > HANDLES_LIMIT) { 83 | throw new InvalidInputException(String.format("Number of handles to be analyzed can't be more than %d", HANDLES_LIMIT)); 84 | } 85 | 86 | return handles; 87 | 88 | } 89 | 90 | public static String buildProfileUrl(Handle handle) { 91 | return String.format(profileUrlFormat.get(handle.getSite()), handle.getHandle()); 92 | } 93 | 94 | public static Set getSites(List handles) { 95 | return handles 96 | .stream() 97 | .map(Handle::getSite) 98 | .collect(Collectors.toSet()); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/util/Helper.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.util; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | 6 | import java.text.DateFormat; 7 | import java.util.UUID; 8 | 9 | public class Helper { 10 | public static Gson gson = new GsonBuilder() 11 | .create(); 12 | 13 | 14 | public static void sleep(long ms) { 15 | try { 16 | Thread.sleep(ms); 17 | } catch(InterruptedException e) { 18 | throw new RuntimeException("Interrupted while sleeping"); 19 | } 20 | } 21 | 22 | public static String randomId() { 23 | return UUID.randomUUID().toString(); 24 | } 25 | 26 | public static Double parseRatingNumber(String text) { 27 | Double rating; 28 | try { 29 | rating = Double.parseDouble(text); 30 | } catch(NumberFormatException ex) { 31 | rating = null; 32 | } 33 | 34 | return rating; 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/util/SiteHelper.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.util; 2 | 3 | import com.codedrills.model.Site; 4 | 5 | import java.util.Arrays; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class SiteHelper { 10 | private static Map invMap; 11 | 12 | static { 13 | invMap = new HashMap<>(); 14 | Arrays.stream(Site.values()) 15 | .forEach(s -> s.getAliases() 16 | .stream() 17 | .forEach(a -> invMap.put(a, s))); 18 | } 19 | 20 | public static Site getSite(String alias) { 21 | return invMap.get(alias); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/util/TagHelper.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.util; 2 | 3 | import com.codedrills.model.Site; 4 | import com.codedrills.model.Tag; 5 | import com.google.common.collect.HashMultimap; 6 | import com.google.common.collect.Multimap; 7 | 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.stream.Collectors; 12 | 13 | import static com.codedrills.model.Tag.*; 14 | import static com.codedrills.model.Tag.Math; 15 | import static java.util.Arrays.asList; 16 | 17 | public class TagHelper { 18 | private static Multimap> tagMap; 19 | private static Map invMap; 20 | 21 | static { 22 | tagMap = HashMultimap.create(); 23 | // cf 24 | tagMap.put(Strings, asList("cf:strings", "cf:string suffix structures")); 25 | tagMap.put(GraphsTrees, asList("cf:trees", "cf:graphs", "cf:dfs and similar", "cf:shortest paths")); 26 | tagMap.put(Flows, asList("cf:flows", "cf:graph matchings")); 27 | tagMap.put(Games, asList("cf:games")); 28 | tagMap.put(Probabilities, asList("cf:probabilities")); 29 | tagMap.put(Combinatorics, asList("cf:combinatorics")); 30 | tagMap.put(Geometry, asList("cf:geometry")); 31 | tagMap.put(Math, asList("cf:math")); 32 | tagMap.put(NumberTheory, asList("cf:number theory")); 33 | tagMap.put(Matrices, asList("cf:matrices")); 34 | tagMap.put(Implementation, asList("cf:ternary search")); 35 | tagMap.put(DynamicProgramming, asList("cf:dp")); 36 | tagMap.put(Implementation, asList("cf:two pointers")); 37 | tagMap.put(Greedy, asList("cf:greedy")); 38 | tagMap.put(DivideConquer, asList("cf:divide and conquer")); 39 | tagMap.put(DataStructures, asList("cf:dsu", "cf:data structures")); 40 | tagMap.put(Bitmasks, asList("cf:bitmasks")); 41 | tagMap.put(BinarySearch, asList("cf:binary search")); 42 | tagMap.put(Implementation, asList("cf:meet in the middle")); 43 | tagMap.put(Implementation, asList("cf:implementation")); 44 | tagMap.put(Implementation, asList("cf:constructive algorithms")); 45 | tagMap.put(BruteForce, asList("cf:brute force")); 46 | tagMap.put(Adhoc, asList("cf:sortings")); 47 | tagMap.put(Hashing, asList("cf:hashing")); 48 | tagMap.put(FFT, asList("cf:fft")); 49 | 50 | // cc 51 | tagMap.put(Strings, asList("cc:z-algorithm", "cc:zfunction", "cc:kmp", "cc:aho-corasick", "cc:manacher", "cc:string", "cc:trie", "cc:tries", "cc:grammar-parsing", "cc:string-parsing", "cc:string-process", "cc:strings", "cc:suffix-array", "cc:suffix-auto", "cc:suffix-trees", "cc:suffix_automata", "cc:regex")); 52 | tagMap.put(GraphsTrees, asList("cc:prim", "cc:unrooted-trees", "cc:colouring", "cc:treeroot", "cc:trees", "cc:rooted-trees", "cc:lca", "cc:spanningtree", "cc:kirchhoff", "cc:shortest-path", "cc:hamiltonian", "cc:traversal", "cc:kruskal", "cc:tree", "cc:tree-based", "cc:floyd-warshall", "cc:graph", "cc:graph-sqrt", "cc:graph-theory", "cc:graph-traversal", "cc:graphs", "cc:euler-tour", "cc:eulerian", "cc:dual-graph", "cc:dominator", "cc:edge-cover", "cc:directed", "cc:dfs", "cc:digraph", "cc:dijkstra", "cc:dfs-order", "cc:bipartiteness", "cc:central-nodes", "cc:connected", "cc:centroid-decomp", "cc:bfs", "cc:bellman-ford", "cc:basic-graph", "cc:bridges")); 53 | tagMap.put(Flows, asList("cc:flow", "cc:dilworth", "cc:bipartite", "cc:max-flow", "cc:maxflow", "cc:min-cost-flow", "cc:mincut-maxflow", "cc:minimum-cut", "cc:maximum-flow", "cc:matching")); 54 | tagMap.put(Games, asList("cc:zero-sum-game", "cc:sprague-grundy", "cc:nim", "cc:impartial-game", "cc:hackenbush", "cc:game", "cc:game-theory", "cc:games")); 55 | tagMap.put(Probabilities, asList("cc:probability", "cc:expectation", "cc:expected-value")); 56 | tagMap.put(Combinatorics, asList("cc:pigeonhole", "cc:lucas-theorem", "cc:inclusn-exclusn", "cc:derangement", "cc:permutation", "cc:permutations", "cc:combinations", "cc:combinatorial", "cc:combinatorics", "cc:burnside", "cc:binomial")); 57 | tagMap.put(Geometry, asList("cc:voronoi", "cc:geometry", "cc:polygons", "cc:convex-hull", "cc:convex-polygon", "cc:trigonometry", "cc:line-clipping", "cc:line-sweep", "cc:angles", "cc:sweepline")); 58 | tagMap.put(Math, asList("cc:numeral-systems", "cc:lagrange", "cc:polynomial", "cc:divisors", "cc:recurrence", "cc:recurrences", "cc:fibonacci", "cc:log", "cc:linear-comb", "cc:math", "cc:mathematics", "cc:maths", "cc:integrals", "cc:division", "cc:interpolation", "cc:gcd", "cc:binary", "cc:catalan", "cc:binary-numbers", "cc:counting", "cc:big-integer", "cc:biginteger", "cc:cross-product", "cc:basic-math", "cc:basic-maths", "cc:basic_math", "cc:arithmetic", "cc:algebra", "cc:advanced-math")); 59 | tagMap.put(NumberTheory, asList("cc:sieve", "cc:prime", "cc:rabin-karp", "cc:prime-factor", "cc:seive", "cc:primegenerator", "cc:primenumbers", "cc:primitive-root", "cc:pollard-rho", "cc:number-theory", "cc:euclids-algo", "cc:lcm", "cc:chinese-rem", "cc:miller-rabin", "cc:crt", "cc:bezout-identity", "cc:mobius_function", "cc:modular", "cc:modular-arith", "cc:modular-inv", "cc:modulo", "cc:modulo-ops")); 60 | tagMap.put(Matrices, asList("cc:matrices", "cc:matrix", "cc:matrix-expo", "cc:linear-algebra", "cc:gauss-elim")); 61 | tagMap.put(Implementation, asList("cc:optimization", "cc:construction", "cc:precalculation", "cc:precomputation", "cc:precomputing", "cc:prefix-minimum", "cc:prefix-sum", "cc:prefix-sums", "cc:preprocessing", "cc:constructions", "cc:interactive", "cc:constructive", "cc:computation", "cc:intervals", "cc:basics", "cc:basic-prog", "cc:basic", "cc:implemenatation", "cc:implementation", "cc:basic-implement", "cc:array", "cc:array-string", "cc:arrays", "cc:backtracking")); 62 | tagMap.put(DynamicProgramming, asList("cc:tree-dp", "cc:memoization", "cc:knapsack", "cc:lis", "cc:lcp", "cc:lcs", "cc:kadane", "cc:edit-distance", "cc:dp", "cc:dp+bitmask", "cc:dynamic-prog", "cc:dp+lcs", "cc:dp+probability", "cc:digit-dp")); 63 | tagMap.put(Implementation, asList("cc:two-pointers")); 64 | tagMap.put(Greedy, asList("cc:greedy", "cc:greedy-algo")); 65 | tagMap.put(DivideConquer, asList("cc:divide-and-conq")); 66 | tagMap.put(DataStructures, asList("cc:union-find", "cc:sqrt-decomp", "cc:stack", "cc:sparse-tables", "cc:splay-tree", "cc:splay-trees", "cc:segment-tree", "cc:segment-trees", "cc:segtree", "cc:treap", "cc:treaps", "cc:rmq", "cc:range-queries", "cc:range-sum", "cc:range-tree", "cc:range-trees", "cc:persistence", "cc:link-cut-tree", "cc:list", "cc:maps", "cc:lazypropagation", "cc:kd-tree", "cc:fenwick", "cc:fenwick-tree", "cc:heap", "cc:heaps", "cc:heavy-decomp", "cc:heavy-light", "cc:hld", "cc:dsu", "cc:data-structure", "cc:disjoint-set", "cc:decomposition", "cc:deque", "cc:binarytrees", "cc:binaryheap", "cc:binary-tree", "cc:2d-fenwick", "cc:balanced", "cc:balanced-tree")); 67 | tagMap.put(Bitmasks, asList("cc:bit", "cc:bitmasking", "cc:bitmasks", "cc:bits", "cc:bitset", "cc:bitwise")); 68 | tagMap.put(BinarySearch, asList("cc:binarysearch", "cc:binary-search")); 69 | tagMap.put(Implementation, asList("cc:meet-in-middle")); 70 | tagMap.put(BruteForce, asList("cc:brute", "cc:brute-force", "cc:bruteforce")); 71 | tagMap.put(Adhoc, asList("cc:calendar-calc", "cc:sort", "cc:sorting", "cc:arbitrary", "cc:palindrome", "cc:palindromes", "cc:parentheses", "cc:parity", "cc:mergesort", "cc:hamming-dist", "cc:logic", "cc:loop", "cc:looping", "cc:loops", "cc:finding-pattern", "cc:exponentiation", "cc:fast-expo", "cc:fastmodexp", "cc:conditions", "cc:counting_sort", "cc:2d", "cc:ad", "cc:adhoc", "cc:ad-hoc")); 72 | tagMap.put(Hashing, asList("cc:rolling-hash", "cc:hash-map", "cc:hashing", "cc:hashmaps", "cc:string-hashing")); 73 | tagMap.put(FFT, asList("cc:fourier", "cc:fft", "cc:karatsuba")); 74 | 75 | //sp 76 | tagMap.put(Strings, asList("sp:text-processing", "sp:parsing", "sp:validation", "sp:string-matchinig", "sp:regular-expressions", "sp:kmp-algorithm")); 77 | tagMap.put(GraphsTrees, asList("sp:graph", "sp:graph-theory", "sp:shortest-path", "sp:hamiltonian-circuit", "sp:euler-circuit", "sp:domination", "sp:graph-coloring", "sp:ramsey-numbers", "sp:scc", "sp:longest-path", "sp:vertex-cover", "sp:mst", "sp:independent-set")); 78 | tagMap.put(GraphsTrees, asList("sp:dfs", "sp:bfs", "sp:flood-fill", "sp:kruskal-s-algorithm", "sp:prim-s-algorithm", "sp:dijkstra-s-algorithm")); 79 | tagMap.put(Flows, asList("sp:flow", "sp:max-flow", "sp:min-cut", "sp:max-cut", "sp:matching")); 80 | tagMap.put(Games, asList("sp:game-theory", "sp:game", "sp:board-game", "sp:carrom", "sp:chess")); 81 | tagMap.put(Probabilities, asList("sp:probability-theory")); 82 | tagMap.put(Combinatorics, asList("sp:combinatorics", "sp:partition", "sp:stirling")); 83 | tagMap.put(Geometry, asList("sp:geometry", "sp:convex-hull", "sp:plane-geometry", "closest-pair", "sp:topology", "sp:metric-space", "sp:knot-theory")); 84 | tagMap.put(Math, asList("sp:math", "sp:simple-math", "sp:big-numbers", "sp:factorial", "sp:positional-systems", "sp:algebra")); 85 | tagMap.put(Math, asList("sp:calculus", "sp:differential-equation", "real-function", "zero-of-function", "linear-algebra", "linear-system")); 86 | tagMap.put(Math, asList("sp:linear-programming", "sp:newton-raphson")); 87 | tagMap.put(NumberTheory, asList("sp:number-theory", "sp:gcd", "sp:totient", "sp:prime-numbers", "sp:factorisation", "sp:lcm")); 88 | tagMap.put(NumberTheory, asList("sp:sieve-of-eratosthenes")); 89 | tagMap.put(Matrices, asList("sp:matrix")); 90 | tagMap.put(Implementation, asList("sp:implementation")); 91 | tagMap.put(DynamicProgramming, asList("sp:knapsack", "sp:lcs", "sp:edit-distance", "sp:dynamic-programming")); 92 | tagMap.put(Greedy, asList("sp:greedy")); 93 | tagMap.put(DivideConquer, asList("sp:divide-and-conquer")); 94 | tagMap.put(DataStructures, asList("sp:rmq", "sp:data-structure", "sp:tree", "sp:binary-tree", "sp:avl-tree", "sp:red-black-tree", "sp:segment-tree", "sp:splay-tree", "sp:trie")); 95 | tagMap.put(DataStructures, asList("sp:heap", "sp:binomial-heap", "sp:fibonacci-heap", "sp:disjoint-set")); 96 | tagMap.put(Bitmasks, asList("sp:bitmasks")); 97 | tagMap.put(BinarySearch, asList("sp:binary-search")); 98 | tagMap.put(Implementation, asList("sp:prefix-sum")); 99 | tagMap.put(Implementation, asList("sp:sorting")); 100 | tagMap.put(Implementation, asList("sp:array", "sp:associative-array", "sp:2d-array", "sp:stack", "sp:queue", "sp:priority-queue")); 101 | tagMap.put(Implementation, asList("sp:list", "sp:singly-linked-list", "sp:doubly-linked-list")); 102 | tagMap.put(Implementation, asList("sp:recursion", "sp:branch-and-bound", "sp:backtracking", "sp:sliding-window")); 103 | tagMap.put(BruteForce, asList("sp:brute-force")); 104 | tagMap.put(Adhoc, asList("sp:collatz", "sp:logic", "sp:quick-sort", "sp:subset-sum")); 105 | tagMap.put(Adhoc, asList("sp:set-theory", "sp:set-cover", "sp:partial-order", "sp:linear-order", "sp:group-theory", "sp:ring-theory")); 106 | tagMap.put(Adhoc, asList("sp:extreme-principle")); 107 | tagMap.put(Hashing, asList("sp:hash-table")); 108 | 109 | invMap = new HashMap<>(); 110 | tagMap.entries() 111 | .stream() 112 | .forEach(e -> e.getValue().stream() 113 | .forEach(k -> invMap.put(k, e.getKey()))); 114 | } 115 | 116 | public static Tag mapTag(Site site, String tag) { 117 | String siteTag = String.format("%s:%s", site.getShortName(), tag); 118 | return invMap.getOrDefault(siteTag, UNTAGGED); 119 | } 120 | 121 | public static List tagsOfSite(Site site) { 122 | String sname = site.getShortName(); 123 | return invMap.keySet() 124 | .stream() 125 | .filter(t -> t.startsWith(sname + ":")) 126 | .map(t -> t.substring(sname.length() + 1)) 127 | .collect(Collectors.toList()); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/com/codedrills/util/VerdictHelper.java: -------------------------------------------------------------------------------- 1 | package com.codedrills.util; 2 | 3 | import com.codedrills.model.Site; 4 | import com.codedrills.model.Verdict; 5 | 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | import static com.codedrills.model.Verdict.*; 12 | import static java.util.Arrays.asList; 13 | 14 | public class VerdictHelper { 15 | private static Map> verdictMap; 16 | private static Map invMap; 17 | 18 | static { 19 | verdictMap = new HashMap<>(); 20 | verdictMap.put(AC, asList("cf:OK", "cc:solutions_partially_accepted", "cc:solutions_accepted")); 21 | verdictMap.put(WA, asList("cf:WRONG_ANSWER", "cc:wrong_answers")); 22 | verdictMap.put(TLE, asList("cf:TIME_LIMIT_EXCEEDED", "cc:time_limit_exceeded")); 23 | verdictMap.put(RTE, asList("cf:RUNTIME_ERROR", "cc:runtime_error")); 24 | verdictMap.put(CE, asList("cf:COMPILE_ERROR", "cc:compile_error")); 25 | verdictMap.put(MLE, asList("cf:MEMORY_LIMIT_EXCEEDED")); 26 | 27 | invMap = new HashMap<>(); 28 | verdictMap.entrySet() 29 | .stream() 30 | .forEach(e -> e.getValue().stream() 31 | .forEach(k -> invMap.put(k, e.getKey()))); 32 | } 33 | 34 | public static Verdict mapVerdict(Site site, String verdict) { 35 | return invMap.getOrDefault(String.format("%s:%s", site.getShortName(), verdict), OTHER); 36 | } 37 | 38 | public static List verdictsFor(Site site) { 39 | return invMap.keySet() 40 | .stream() 41 | .filter(s -> s.startsWith(site.getShortName())) 42 | .map(s -> s.split(":")[1]) 43 | .collect(Collectors.toList()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/resources/application-dev.properties: -------------------------------------------------------------------------------- 1 | is_prod=false 2 | 3 | # =============================== 4 | # = internal settings 5 | # =============================== 6 | tags_limit=5 7 | 8 | # =============================== 9 | # = compression settings 10 | # =============================== 11 | server.compression.enabled=false 12 | 13 | # =============================== 14 | # = DATA SOURCE 15 | # =============================== 16 | 17 | # Connection url for the database "code-drills" 18 | spring.datasource.url = jdbc:mysql://localhost:3310/code_drills?useSSL=false 19 | 20 | # Username and password 21 | spring.datasource.username = cd_local_user 22 | spring.datasource.password = {cd_use_secure} 23 | 24 | spring.jpa.show-sql = true 25 | 26 | spring.datasource.tomcat.max-active=100 27 | spring.datasource.tomcat.max-idle=8 28 | spring.datasource.tomcat.min-idle=8 29 | 30 | -------------------------------------------------------------------------------- /src/main/resources/application-prod.properties: -------------------------------------------------------------------------------- 1 | is_prod=true 2 | 3 | # =============================== 4 | # = internal settings 5 | # =============================== 6 | tags_limit=100000 7 | 8 | # =============================== 9 | # = compression settings 10 | # =============================== 11 | server.compression.enabled=true 12 | server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,text/css,text/js 13 | 14 | # =============================== 15 | # = DATA SOURCE 16 | # =============================== 17 | 18 | # Connection url for the database "code-drills" 19 | spring.datasource.url = jdbc:mysql://localhost:3306/codedrills?useSSL=false 20 | 21 | # Username and password 22 | spring.datasource.username = root 23 | spring.datasource.password = root123 # is replaced via env variable 24 | 25 | spring.jpa.show-sql = false 26 | 27 | spring.datasource.tomcat.max-active=100 28 | spring.datasource.tomcat.max-idle=8 29 | spring.datasource.tomcat.min-idle=8 30 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # =============================== 2 | # = DATA SOURCE 3 | # =============================== 4 | 5 | # Keep the connection alive if idle for a long time (needed in production) 6 | spring.datasource.testWhileIdle = true 7 | spring.datasource.validationQuery = SELECT 1 8 | 9 | # =============================== 10 | # = JPA / HIBERNATE 11 | # =============================== 12 | 13 | # Hibernate ddl auto (create, create-drop, update): with "update" the database 14 | # schema will be automatically updated accordingly to java entities found in 15 | # the project 16 | spring.jpa.hibernate.ddl-auto = update 17 | 18 | # Naming strategy 19 | spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy 20 | 21 | # Allows Hibernate to generate SQL optimized for a particular DBMS 22 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect 23 | -------------------------------------------------------------------------------- /src/main/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/favicon.ico -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %d{dd MMM yyyy HH:mm:ss.SSS, UTC} [%thread] %-5level %logger{36} - %msg%n 10 | 11 | 12 | 13 | 14 | 15 | ${LOGS}/application.log 16 | 17 | %d{dd MMM yyyy HH:mm:ss.SSS, UTC} [%thread] %-5level %logger{36} - %msg%n 18 | 19 | 20 | 21 | 22 | 23 | ${LOGS}/archived/application.%d{yyyy-MM-dd, UTC}.log 24 | 25 | 26 | 30 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/main/resources/static/css/common.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #e7e7e7 !important; 3 | } 4 | 5 | .container { 6 | background: #fff !important; 7 | padding-bottom: 25px !important; 8 | } 9 | 10 | .footer { 11 | margin-top: 20px; 12 | } 13 | 14 | .faqHeader { 15 | font-size: 27px; 16 | margin: 20px; 17 | } 18 | 19 | .fb-like { 20 | margin-top: 5px; 21 | } 22 | 23 | .panel-heading [data-toggle="collapse"]:after { 24 | font-family: 'Glyphicons Halflings'; 25 | content: "\e072"; /* "play" icon */ 26 | float: right; 27 | color: #F58723; 28 | font-size: 18px; 29 | line-height: 22px; 30 | /* rotate "play" icon from > (right arrow) to down arrow */ 31 | -webkit-transform: rotate(-90deg); 32 | -moz-transform: rotate(-90deg); 33 | -ms-transform: rotate(-90deg); 34 | -o-transform: rotate(-90deg); 35 | transform: rotate(-90deg); 36 | } 37 | 38 | .panel-heading [data-toggle="collapse"].collapsed:after { 39 | /* rotate "play" icon from > (right arrow) to ^ (up arrow) */ 40 | -webkit-transform: rotate(90deg); 41 | -moz-transform: rotate(90deg); 42 | -ms-transform: rotate(90deg); 43 | -o-transform: rotate(90deg); 44 | transform: rotate(90deg); 45 | color: #454444; 46 | } 47 | 48 | .thumbnail img { 49 | min-height: 90px; 50 | height: 100px; 51 | max-height: 120px; 52 | } 53 | 54 | .table-borderless tbody tr td, 55 | .table-borderless tbody tr th, 56 | .table-borderless thead tr th, 57 | .table-borderless thead tr td, 58 | .table-borderless tfoot tr th, 59 | .table-borderless tfoot tr td { 60 | border: none; 61 | } 62 | 63 | .banner { 64 | max-height: 150px; 65 | min-height: 150px; 66 | overflow-y: none; 67 | } 68 | 69 | .site-img { 70 | border: none; 71 | } 72 | 73 | .problem-attributes img { 74 | border: none; 75 | width: 20px; 76 | height: 20px; 77 | margin-left: 5px; 78 | } 79 | 80 | img.input-site-icon { 81 | border: none; 82 | width: 15px; 83 | height: 15px; 84 | margin-left: 5px; 85 | margin-right: 5px; 86 | } 87 | 88 | .site-icon { 89 | border: none; 90 | width: 20px; 91 | height: 20px; 92 | } 93 | 94 | #input-format { 95 | line-height: 175%; 96 | } 97 | 98 | .navbar-right { 99 | margin-top: 5px; 100 | } 101 | 102 | .problem-attributes { 103 | text-align: right; 104 | vertical-align: middle !important; 105 | } 106 | 107 | .practice-solved { 108 | color: green; 109 | text-decoration: line-through; 110 | } 111 | 112 | .code { 113 | margin-top: 20px; 114 | margin-bottom: 20px; 115 | } 116 | 117 | .refs { 118 | margin-top: 10px; 119 | } 120 | 121 | .alert { 122 | margin-top: 10px; 123 | } 124 | 125 | .level > .panel-heading { 126 | background-color: lightgrey; 127 | } 128 | 129 | .level1 > .panel-heading > a { 130 | color: darkgrey; 131 | 132 | } 133 | 134 | .level1-title { 135 | color: darkgrey; 136 | } 137 | 138 | .level2 > .panel-heading > a { 139 | color: darkgreen; 140 | } 141 | 142 | .level2-title { 143 | color: darkgreen; 144 | } 145 | 146 | .level3 > .panel-heading > a { 147 | color: darkblue; 148 | } 149 | 150 | .level3-title { 151 | color: darkblue; 152 | } 153 | 154 | .level4 > .panel-heading > a { 155 | color: #aaaa00; 156 | } 157 | 158 | .level4-title { 159 | color: #aaaa00; 160 | } 161 | 162 | .level5 > .panel-heading > a { 163 | color: red; 164 | } 165 | 166 | .level5-title { 167 | color: red; 168 | } 169 | 170 | .bfscp_title { 171 | font-family: "Courier New", Courier, monospace !important; 172 | } 173 | 174 | .progress-img { 175 | width: 20px; 176 | height: 20px; 177 | } 178 | 179 | -------------------------------------------------------------------------------- /src/main/resources/static/images/banners/codechef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/banners/codechef.png -------------------------------------------------------------------------------- /src/main/resources/static/images/banners/codeforces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/banners/codeforces.png -------------------------------------------------------------------------------- /src/main/resources/static/images/banners/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/banners/facebook.png -------------------------------------------------------------------------------- /src/main/resources/static/images/banners/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/banners/google.png -------------------------------------------------------------------------------- /src/main/resources/static/images/banners/hackerearth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/banners/hackerearth.png -------------------------------------------------------------------------------- /src/main/resources/static/images/banners/hackerrank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/banners/hackerrank.png -------------------------------------------------------------------------------- /src/main/resources/static/images/banners/ipsc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/banners/ipsc.png -------------------------------------------------------------------------------- /src/main/resources/static/images/banners/topcoder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/banners/topcoder.png -------------------------------------------------------------------------------- /src/main/resources/static/images/code-drills.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/code-drills.png -------------------------------------------------------------------------------- /src/main/resources/static/images/difficulty/easy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/difficulty/easy.png -------------------------------------------------------------------------------- /src/main/resources/static/images/difficulty/hard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/difficulty/hard.png -------------------------------------------------------------------------------- /src/main/resources/static/images/difficulty/medium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/difficulty/medium.png -------------------------------------------------------------------------------- /src/main/resources/static/images/fb.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/fb.gif -------------------------------------------------------------------------------- /src/main/resources/static/images/github-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/github-logo.png -------------------------------------------------------------------------------- /src/main/resources/static/images/icons/codechef.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/icons/codechef.png -------------------------------------------------------------------------------- /src/main/resources/static/images/icons/codeforces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/icons/codeforces.png -------------------------------------------------------------------------------- /src/main/resources/static/images/icons/spoj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/icons/spoj.png -------------------------------------------------------------------------------- /src/main/resources/static/images/inprogress.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 16 | 20 | 24 | 28 | 29 | 37 | 45 | 48 | 51 | 52 | 61 | 62 | 65 | 69 | 73 | 78 | 82 | 85 | 89 | 99 | 108 | 118 | 128 | 138 | 146 | 156 | 166 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/main/resources/static/images/ln.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/ln.gif -------------------------------------------------------------------------------- /src/main/resources/static/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 17 | 20 | 24 | 28 | 33 | 38 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/resources/static/images/sorry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeDrills-CD/codedrills-recommender/9000228ae2ac7a2f55ca9526e7d15359e632dd89/src/main/resources/static/images/sorry.jpg -------------------------------------------------------------------------------- /src/main/resources/templates/common/head.ftl: -------------------------------------------------------------------------------- 1 | <#ftl output_format="HTML" auto_esc=true> 2 | <#setting url_escaping_charset="UTF-8"> 3 | <#assign title="code-drills - Competitive programming resources and problem recommender"> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ${title} 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 39 | 40 | <#include "/common/navbar.ftl"> 41 | 42 |
43 | -------------------------------------------------------------------------------- /src/main/resources/templates/common/macros.ftl: -------------------------------------------------------------------------------- 1 | <#macro displayRating handle rating> 2 | 3 | 4 | ${handle.handle} 5 | 6 | ${rating.current!"NA"} 7 | (Highest ${rating.highest!"NA"}) 8 | 9 | 10 | <#macro refs name="References"> 11 | 12 | 13 | 17 | 18 | 19 | <#nested> 20 | 21 |
14 | 15 | ${name} 16 |
22 | 23 | 24 | <#macro ref name link> 25 | ${name} 26 | 27 | 28 | <#macro code gist file> 29 |
30 | 31 |
32 | 33 | 34 | <#macro info> 35 |
36 | 37 | <#nested> 38 |
39 | 40 | 41 | <#macro warning> 42 |
43 | 44 | <#nested> 45 |
46 | 47 | 48 | <#macro remind text> 49 |
50 | ${text} 51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /src/main/resources/templates/common/navbar.ftl: -------------------------------------------------------------------------------- 1 | <#macro isactive type> 2 | <#if type == pageType> 3 | <#assign active="active"/> 4 | <#else> 5 | <#assign active=""/> 6 | 7 | 8 | 67 | -------------------------------------------------------------------------------- /src/main/resources/templates/common/recommendation.ftl: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | <#list recommendation.practiceProblems as pr> 6 | 7 | 10 | 14 | 15 | 16 | 17 |
8 |

${pr.problem.name}

9 |
11 | 12 | 13 |
18 |
19 |
20 | 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/common/tail.ftl: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
5 |
6 |

7 | FAQ 8 |

9 |

10 | Supported by CodeDrills 11 |

12 |
13 |
14 | 15 | 16 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/main/resources/templates/common/tip_macros.ftl: -------------------------------------------------------------------------------- 1 | <#include "/common/macros.ftl"> 2 | 3 | <#macro tip_title> 4 |

<#nested>

5 | 6 | 7 | <#macro tip_content> 8 | <#nested> 9 |
10 | 11 | -------------------------------------------------------------------------------- /src/main/resources/templates/comparator.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="tools"> 2 | <#include "/common/head.ftl"> 3 | 4 |
5 |
6 |

Competitive programming comparator

7 |
8 |
9 | 10 |
11 |
12 | Compare competitive programming profiles between 2 different sets of handles 13 |
14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 |
22 | 26 | 27 |
28 |
29 |
30 | 31 |
32 |
33 |
vs

34 |
35 |
36 | 37 |
38 |
39 |
40 | 44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 | 55 |
56 |
57 |
58 | 59 |
60 |
61 |

62 | 63 | Input format 64 |

65 |
66 |
67 |
68 | <#include "/partial/input.ftl"> 69 |
70 | 71 | 83 | 84 | 85 | 86 | <#include "/common/tail.ftl"> 87 | -------------------------------------------------------------------------------- /src/main/resources/templates/compare.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="tools"> 2 | <#include "/common/head.ftl"> 3 | <#include "/common/macros.ftl"> 4 | <#assign ha="${result.handlesA?html}"> 5 | <#assign hb="${result.handlesB?html}"> 6 | 7 |
8 |
9 |

Comparision Result

10 |
11 |
12 | 13 |
14 |
15 |

A = ${ha}

16 | Vs 17 |

B = ${hb}

18 |
19 |
20 | 21 | <#if result.areSame()> 22 |
23 |
24 |

Both handles are same

25 |
26 |
27 |
28 |
29 | The only person you should try to be better than is who you were yesterday 30 |
31 |
32 | <#else> 33 | 34 |
35 |
36 |

37 | 38 | A's ratings 39 |

40 | 41 | <#list result.ratingsA as handle, rating> 42 | 45 | 46 |
43 | <@displayRating handle=handle rating=rating> 44 |
47 |
48 | 49 |
50 |

51 | 52 | B's ratings 53 |

54 | 55 | <#list result.ratingsB as handle, rating> 56 | 59 | 60 |
57 | <@displayRating handle=handle rating=rating> 58 |
61 |
62 | 63 |
64 | 65 |
66 | 67 |
68 |
69 |

70 | 71 | A is stronger than B in 72 |

73 | <#list result.strongerA as t> 74 | 75 | 76 | ${t} 77 | 78 | <#sep> | 79 | <#else> 80 | None 81 | 82 |
83 |
84 |

85 | 86 | B is stronger than A in 87 |

88 | <#list result.strongerB as t> 89 | 90 | 91 | ${t} 92 | 93 | <#sep> | 94 | <#else> 95 | None 96 | 97 |
98 |
99 | 100 |
101 | 102 |
103 |
104 |

105 | 106 | Problems only solved by A 107 |

108 | <#list result.onlySolvedByA as p> 109 | ${p.name} 110 | <#sep> | 111 | <#else> 112 | None 113 | 114 |
115 |
116 |

117 | 118 | Problems only solved by B 119 |

120 | <#list result.onlySolvedByB as p> 121 | ${p.name} 122 | <#sep> | 123 | <#else> 124 | None 125 | 126 |
127 |
128 | 129 | 130 | <#include "/common/tail.ftl"> 131 | -------------------------------------------------------------------------------- /src/main/resources/templates/content/tips.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="resources"> 2 | <#include "/common/head.ftl"> 3 | <#include "/common/macros.ftl"> 4 | 5 |
6 |
7 |

Tips and tricks

8 |
9 |
10 | 11 |
12 |
13 |

These articles explain the small tricks and tips that are useful for competitive programmers

14 |
15 |
16 | 17 | <@refs name="Tips and tricks"> 18 | <@ref link="/resources/tips/include_all_libraries_cpp" name="Including all libraries in C++"> 19 | <@ref link="/resources/tips/fast_cin_cout_cpp" name="Fast I/O optimization for cin/cout in C++"> 20 | 21 | 22 | <#include "/common/tail.ftl"> 23 | -------------------------------------------------------------------------------- /src/main/resources/templates/content/tips/fast_cin_cout_cpp.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="fast_cin_cout_cpp"> 2 | <#include "/common/head.ftl"> 3 | <#include "/common/tip_macros.ftl"> 4 | 5 | <@tip_title> 6 | Fast I/O optimization for cin/cout in C++ 7 | 8 | 9 | <@tip_content> 10 | Some problems have a huge amount of input and output data. Here are some tips for optimizing I/O through cin/cout. Without these optimizations cin and cout maybe too slow. 11 | 12 | <@info> 13 | All code snippets provided assume that you have included the required headers and are using namespace std; 14 | 15 | 16 |

set sync with stdio to false

17 | C++ allows both C and C++ styled I/O in order to remain backward compatible. So each I/O operation performed on standard C++ streams (such as cin/cout/cerr) are also performed on the standard C streams. We can save a lot of time by not requiring this synchronization. We can do that by: 18 | 19 | <@code gist="https://gist.github.com/code-drills/9e8af843266e58a2d935b6499471d287.js" file="sync_with_stdio.cpp"> 20 | 21 | <@warning> 22 | When you set synchronization to false you cannot intermix C++ styled I/O and C styled I/O (scanf/printf etc.) 23 | 24 | 25 |

untie cin and cout

26 | Usually when you output someting to cout it is not output immediately to stdandard output but stored in a buffer. It has to be flushed for the content to be actually output. Flushing usually happens when the buffer is full or the program is being terminated. To facilitate interaction with the user via cin/cout whenever cin is used cout is flushed. This is to help with user prompt. Consider the following snippet: 27 | 28 | <@code gist="https://gist.github.com/code-drills/9e8af843266e58a2d935b6499471d287.js" file="tied.cpp"> 29 | 30 | Since cin and cout are tied, the cout's buffer is flushed before cin executes allowing the user to see the prompt. However in coding contests we don't need this. So we can untie cout from cin by doing this: 31 | 32 | <@code gist="https://gist.github.com/code-drills/9e8af843266e58a2d935b6499471d287.js" file="untied.cpp"> 33 | 34 |

Avoid using endl

35 | We usually use endl for ending a line of output in cout. But it also has the side effect of flushing cout. So avoid using endl and use '\n' instead to take full advantage of bufferring. This is especially useful if you have to output a large number of lines. 36 | 37 |

Caveats

38 | Before using these optimizations do try to understand the what and why of each optimization. Otherwise they may cause strange, hard-to-debug bugs. Note that with above optimization you will have to manually flush cout for interactive problems. You can do that using: 39 | 40 | <@code gist="https://gist.github.com/code-drills/9e8af843266e58a2d935b6499471d287.js" file="flush.cpp"> 41 | 42 |

Where to put those statements

43 | Those statements should be at start of the program. You can put them in main as the first few lines or if you don't like cluterring main you can use this trick: 44 | 45 | <@code gist="https://gist.github.com/code-drills/9e8af843266e58a2d935b6499471d287.js" file="fastio_class.cpp"> 46 | 47 |

More optimizations

48 | There are ofcourse more I/O optimizations that can be performed. But these should be enough for the vast majority of problems. See the references for links to articles on more advanced optimizations. 49 | 50 | <@refs> 51 | <@ref name="sync_with_stdio" link="http://en.cppreference.com/w/cpp/io/ios_base/sync_with_stdio"> 52 | <@ref name="endl" link="http://www.cplusplus.com/reference/ostream/endl/"> 53 | <@ref name="tie" link="http://www.cplusplus.com/reference/ios/ios/tie/"> 54 | <@ref name="C++ I/O benchmark" link="http://codeforces.com/blog/entry/5217"> 55 | <@ref name="Fast I/O using getchar" link="http://abhisharlives.blogspot.in/2012/06/really-fast-io-methods-for-programming.html"> 56 | <@ref name="Discussion on advanced fast I/O" link="http://discuss.spoj.com/t/fast-io-in-c-c/3902/8"> 57 | <@ref name="Fast I/O using getc" link="https://discuss.codechef.com/questions/11364/fast-io-discussion"> 58 | 59 | 60 | 61 | 62 | 63 | 64 | <#include "/common/tail.ftl"> 65 | -------------------------------------------------------------------------------- /src/main/resources/templates/content/tips/include_all_libraries_cpp.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="include_all_libraries_cpp"> 2 | <#include "/common/head.ftl"> 3 | <#include "/common/tip_macros.ftl"> 4 | 5 | <@tip_title> 6 | Including all libraries in C++ 7 | 8 | 9 | <@warning> 10 | This tip only applies for gcc and mingw compiler 11 | 12 | 13 | 14 | <@tip_content> 15 | Suppose you have a lot of includes in your code: 16 | 17 | <@code gist="https://gist.github.com/code-drills/02aa2637040d84081ba4478172cd3282.js" file="lots_of_includes.cpp"> 18 | 19 | If you use gcc compiler (or minGW) you can replace all of above with a single line: 20 | 21 | <@code gist="https://gist.github.com/code-drills/02aa2637040d84081ba4478172cd3282.js" file="single_include.cpp"> 22 | 23 | This header has almost all of the usual libraries. This is especially useful if you don't have template with you or you want to shortern your template. Keep in mind that this is not a standard header file and hence not available in all C++ compilers. 24 | 25 | <@refs> 26 | <@ref name="GCC's stdc++.h header file" link="https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/include/precompiled/stdc++.h"> 27 | 28 | 29 | 30 | 31 | <#include "/common/tail.ftl"> 32 | -------------------------------------------------------------------------------- /src/main/resources/templates/contests.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="contests"> 2 | <#include "/common/head.ftl"> 3 | 4 | <#macro timeUrl time str msg> 5 | 6 | ${str} 7 | 8 | 9 | 10 | <#if timezone?has_content> 11 |
12 |
13 |
14 | All times are in ${timezone} time zone. Switch to UTC . 15 |
16 |
17 | 18 | 19 | <#list contests as type,clists> 20 |
21 |
22 |

${type?cap_first}

23 |
24 |
25 | <#if type == "live"> 26 | <#assign startText="Started"> 27 | <#assign panelType="panel-success"> 28 | <#else> 29 | <#assign startText="Starts"> 30 | <#assign panelType="panel-info"> 31 | 32 | <#list clists?chunk(3) as crow> 33 |
34 | <#list crow as contest> 35 |
36 |
37 | 42 | 49 | 71 |
72 |
73 | 74 |
75 |
76 | 77 |
78 | 79 | <#include "/common/tail.ftl"> 80 | -------------------------------------------------------------------------------- /src/main/resources/templates/error.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="error"> 2 | <#include "/common/head.ftl"> 3 |
4 |
5 |

${errorText!"Oops! Something went wrong while processing your request."}

6 |
7 |
8 |
9 |
10 |
11 | Sorry! 12 |
13 |
14 |
15 |
16 |
17 | Mark this as a RTE against us :( 18 |
19 |
20 | <#include "/common/tail.ftl"> 21 | -------------------------------------------------------------------------------- /src/main/resources/templates/partial/analysis.ftl: -------------------------------------------------------------------------------- 1 | <#include "/common/macros.ftl"> 2 |
3 |

Analysis

4 |
5 | 6 | <#assign userStats=result.getUserStats()/> 7 | <#assign total_submitted=userStats.totalSubmissions()/> 8 | <#assign subs=userStats.getVerdictCount()/> 9 | 10 | <#assign total_solved=userStats.totalSolved()/> 11 | <#assign tags=userStats.getTagCount()/> 12 | 13 | <#assign strong=userStats.getStrong()/> 14 | <#assign weak=userStats.getWeak()/> 15 | 16 | 17 | 38 | 39 | <#if total_submitted != 0> 40 | 97 | 144 | 145 | 146 |
147 |

148 | 149 | Ratings 150 |

151 |
152 | 153 |
154 | 155 |
156 | <#list userStats.ratings as handle, rating> 157 |
158 | <@displayRating handle=handle rating=rating> 159 |
160 | 161 |
162 | 163 |
164 |
165 |

166 | 167 | Submissions 168 |

169 |
170 | 171 |
172 |
173 |
174 | <#if total_submitted == 0> 175 | No submissions found 176 | 177 |
178 |
179 |
180 |
181 | <#if total_submitted == 0> 182 | No submissions found 183 | 184 |
185 |
186 |
187 | 188 |
189 |
190 |

191 | 192 | 193 | Strong Areas 194 |

195 |
196 |
    197 | <#list strong as s> 198 | <#if s?index < 5> 199 |
  • 200 | 201 | ${s.tag} 202 |
  • 203 | 204 | <#else> 205 |
  • Need more submissions for this analysis
  • 206 | 207 |
208 |
209 |
210 |

211 | 212 | 213 | Weak Areas 214 |

215 |
216 |
    217 | <#list weak as w> 218 | <#if w?index < 5> 219 |
  • 220 | 221 | ${w.tag} 222 |
  • 223 | 224 | <#else> 225 |
  • Need more submissions for this analysis
  • 226 | 227 |
228 |
229 | -------------------------------------------------------------------------------- /src/main/resources/templates/partial/input.ftl: -------------------------------------------------------------------------------- 1 | <#macro example> 2 | <#nested> 3 | 4 |
5 |
6 |
    7 |
  • Each handle should be prefixed with the site prefix (in lowercase) of that handle
  • 8 |
  • Sites supported
    9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 27 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | 41 | 45 | 46 | 47 | 48 | 49 |
    SitePrefixExample
    20 | 21 | Codeforces 22 | 24 | cf/
    25 | no prefix 26 |
    28 | <@example>cf/tourist
    29 | <@example>petr 30 |
    34 | 35 | Codechef 36 | cc/ <@example>cc/mugurelionut
    42 | 43 | Spoj 44 | sp/ <@example>sp/xilinx
    50 |
  • 51 |
  • 52 | You can apply multiple prefixes to same handle. This is useful if the same handle is used across multiple sites
    53 | e.g. <@example>cf/sp/cc/acrush 54 |
  • 55 |
  • 56 | Different handle groups should be space separated
    57 | e.g. <@example>cc/sp/balajiganapath cf/balajiganapathi 58 |
  • 59 |
60 |
61 |
62 | -------------------------------------------------------------------------------- /src/main/resources/templates/partial/recommendations.ftl: -------------------------------------------------------------------------------- 1 | <#assign practice=result.getPracticeRecommendations().getRecommendations()/> 2 | <#assign defaultRec="Warmup"> 3 |
4 |
5 |

6 | 7 | Practice Recommendations 8 |

9 |
10 |
11 | 12 |
13 |
14 |
15 | 22 |
23 |
24 |
25 | <#list practice as recommendation> 26 |
27 |
28 |
29 |
30 | 31 | Track 32 | 33 |
34 |
${recommendation.description}
35 |
36 | <#include "/common/recommendation.ftl"> 37 |
38 | 39 |
40 |
41 | -------------------------------------------------------------------------------- /src/main/resources/templates/profile.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="tools"> 2 | <#include "/common/head.ftl"> 3 | 4 |
5 |
6 |

7 | ${result.handles?html} 8 |

9 |
10 |
11 | <#include "/partial/analysis.ftl"> 12 | <#include "/partial/recommendations.ftl"> 13 | 14 | <#include "/common/tail.ftl"> 15 | -------------------------------------------------------------------------------- /src/main/resources/templates/recommendation_list.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="tools"> 2 | <#include "/common/head.ftl"> 3 | 4 |
5 |
6 |

${recommendation.name}

7 |
8 |
9 | 10 |
11 |
12 |

${recommendation.description}

13 |
14 |
15 | 16 | 17 |
18 |
19 |

Tracking for ${recommendation.handles}

20 |
21 |
22 | 23 | 24 |
25 |
26 |
Use this url to keep track of your recommendation list. You can also share it with your team for team practice. It may take upto 10 minutes for a solved problem to be updated here.
27 |
28 |
29 | 30 | 31 |
32 | 33 | <#include "/common/recommendation.ftl"> 34 | 35 | <#include "/common/tail.ftl"> 36 | -------------------------------------------------------------------------------- /src/main/resources/templates/recommender.ftl: -------------------------------------------------------------------------------- 1 | <#assign pageType="tools"> 2 | <#include "/common/head.ftl"> 3 | 4 |
5 |
6 | 10 |
11 |
12 | 13 |
14 |
15 |

Practice problems recommender

16 |
17 |
18 | 19 |
20 |
21 | Analyze your profile on various competitive programming sites and get practice problem recommendations based on your past submissions 22 |
23 |
24 | 25 |
26 | 27 |
28 |
29 |
30 |
31 | 35 |
36 | 37 | 38 | 42 | 43 |
44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |

52 | 53 | Input format 54 |

55 |
56 |
57 |
58 | <#include "/partial/input.ftl"> 59 |
60 | 61 | 74 | 75 | <#include "/common/tail.ftl"> 76 | --------------------------------------------------------------------------------