NonTourist's blog

By NonTourist, 5 days ago, In English

AlgoRadar — an explainable recommender for CP practice

Hello Codeforces!

I built AlgoRadar, an open-source recommendation/analytics system for competitive programming practice.

Repository: Link

The goal is simple:

Given my public submission history, what should I practice next?

The system uses Codeforces as the strongest source because it provides public submissions, verdicts, problem ratings, tags, rating history, and problemset metadata. It also has adapters for LeetCode and CodeChef, but Codeforces is the main difficulty anchor.


1. Main idea

AlgoRadar converts public CP activity into explainable features and ranks unsolved problems by learning value, not just rating or popularity.

Rough pipeline:

handle
-> fetch public submissions/problem metadata
-> build profile/tag/difficulty features
-> classify focus areas
-> estimate solve probability
-> rank candidate problems
-> bucket into confidence/growth/stretch/avoid-for-now
-> diversify final recommendations

I intentionally used a baseline-first approach instead of a black-box model.

Why? Public solved history is useful but noisy. An accepted submission does not always prove independent ability, and a failed submission does not fully prove inability. So instead of pretending to have perfect labels, the system uses feature engineering, scorecards, caps, and behavioral tests.


2. Feature engineering

For every user/tag/problem, the system computes features like:

rating_gap = problem_rating - user_rating
tag_accuracy
attempts_on_tag
tag_solved_count
tag_avg_rating_solved
tag_max_rating_solved
recent_failures
recent_accuracy
popularity_log
rating_confidence

Examples:

tag_accuracy = accepted_submissions_on_tag / attempts_on_tag

tag_avg_rating_solved = average rating of accepted problems in that tag

tag_max_rating_solved = hardest accepted problem in that tag

The system does not trust accuracy alone.

For example:

2/2 solves  !=  40/60 solves

Both may look good, but the second gives much stronger evidence.

Also, solving many easy dp problems should not imply that a 2200-rated dp problem is easy. That is why average solved rating and hardest solved rating are used.


3. Focus / weakness classification

Tags are classified into:

Strong
Stable
Weak
Untouched
Over-attempted

The important distinction is:

Weak != Untouched

If a user has never practiced geometry, the system should not say "you are weak in geometry". It should say there is not enough evidence.

The classifier is rule-based and uses attempts, accuracy, recent failures, and solved depth. This is easier to inspect and debug than a fake supervised model trained on weak labels.


4. Solve probability scorecard

The solve estimate is mainly driven by rating gap:

rating_gap = problem_rating - user_rating

Then tag evidence adjusts it.

Conceptually:

logit =
    base
  - rating_gap_penalty
  + tag_volume_bonus
  + hardest_solved_bonus
  + average_solved_rating_bonus
  + overall_experience_bonus
  + popularity_bonus
  - recent_failure_penalty
  - uncertainty_penalty

Then the logit is converted into probability:

p = 1 / (1 + exp(-logit))

But the raw probability is capped.

Reason: a problem near the user's rating should not become 95% just because the user solved many problems before. Same-level problems are usually growth attempts, not guaranteed solves.

The final probability is bucketed as:

> 75%    -> confidence
45-75%  -> growth
25-45%  -> stretch
< 25%   -> avoid-for-now

This makes the output actionable instead of giving only one sorted list.


5. Recommendation ranking

The recommender first removes already solved problems:

candidates = all_problems - solved_problem_ids

Then candidates are scored using:

solve probability
growth fit
weak/focus tag overlap
tag evidence
topic familiarity
rating fit
log popularity
diversity penalties

The main idea:

The best recommendation is not necessarily the easiest problem or the most similar problem. It should be useful for improvement.


6. Topic familiarity with cosine similarity

I added a solved-tag vector signal.

For a user, build a tag-count vector:

dp: 10
graphs: 6
math: 4

Then L2-normalize it:

u[j] = count[j] / sqrt(sum(count[k]^2 over all tags k))

For each candidate problem, build a binary tag vector t.

Then:

sim(problem, user) = dot(t, u) / norm(t)

This measures how familiar the candidate's topics are to the user.

But this is only a small signal. If it dominates, the system will keep recommending comfort-zone problems forever. So AlgoRadar uses it as topic familiarity, not as the whole recommender.


7. Gaussian rating fit

I also added a smooth rating-fit score:

rating_fit = exp(-((problem_rating - user_rating)^2) / (2 * sigma^2))

where:

sigma = 0.3 * user_rating

This gives a smooth peak near the user's current level instead of a hard cutoff.

For a 1200-rated user:

1200 problem -> high fit
1400 problem -> reasonable fit
2000 problem -> low fit
600 problem  -> also lower fit

This is used as a ranking feature, not as the entire difficulty model.


8. Final learning-value score

A simplified version of the ranking score is:

learning_value =
    1.15 * growth_fit
  + 0.52 * tag_similarity
  + 0.32 * ceiling_score
  + 0.22 * evidence_score
  + 0.18 * quality_score
  + 0.12 * tag_cosine_similarity
  + 0.14 * rating_fit
  - 0.00028 * rating_distance

Then:

rank_score = learning_value + 0.16 * solve_probability

where:

growth_fit = 1 - abs(solve_probability - 0.6) / 0.6

So the system prefers problems around a realistic growth zone, not extremely easy problems and not nearly impossible problems.

The weights are heuristic and open to tuning.


9. Similar-problem search

There is also optional similar-problem search.

Default fallback: TF-IDF over problem name, rating, and tags.

If available: MiniLM embeddings for semantic similarity.

This is used to find related or slightly harder problems around a recommendation. It is optional because the recommender should not depend on transformer downloads or heavy runtime dependencies.


10. Testing

The tests focus on behavior, not only imports.

Examples:

harder problem should generally not get higher solve probability
same-rating problem should not get unrealistic 90%+ probability
strong tag depth should improve estimate
cold tag should reduce confidence
solved problems should not be recommended again
calibration should affect cross-platform estimates

This matters because a recommender can run without crashing and still give misleading advice.


11. Limitations

Important limitations:

public solved history is noisy
tags are broad
weights are heuristic
CodeChef/LeetCode mappings are approximate
no real feedback loop yet
recommendation quality needs real user outcome data

Future improvements:

viewed / attempted / solved / skipped feedback
time-to-solve tracking
calibration curves and Brier score
better mashup generation
better tag hierarchy
learned model after enough real outcome labels

12. Conclusion

AlgoRadar is my attempt at an explainable CP recommendation system.

It combines:

rating gap
tag depth
weakness classification
solve probability
topic familiarity
Gaussian rating fit
log popularity
bucketed practice modes
diversity

It is not meant to be an oracle. It is meant to be a practical tool for deciding what to practice next.

I would appreciate suggestions, especially on:

better scoring functions
better rating-fit curves
tag similarity improvements
evaluation metrics
mashup/workout generation

Repository: Link

Thanks for reading!

Full text and comments »

  • Vote: I like it
  • +7
  • Vote: I do not like it