CCCPaste

Eliminate Clubs

# eliminateclubs_fixed.R
# Εξάρτηση: install.packages(c("dplyr", "readr", "tidyr"))

library(dplyr)
library(readr)
library(tidyr)

# --- Ρυθμίσεις ---
min_threshold <- 10L
responses_file <- "responses.csv"
clubs_file <- "clubs.csv"
output_prefix <- "responses_round"

# --- Διαβάζουμε δεδομένα ---
responses <- read_csv(responses_file, show_col_types = FALSE)
clubs <- read_csv(clubs_file, show_col_types = FALSE)

# --- Καθαρισμός και εναρμόνιση (όπως πριν) ---
colnames(clubs) <- tolower(trimws(colnames(clubs)))
clubs$club_id <- trimws(tolower(clubs$club_id))
if("club_name" %in% colnames(clubs)) {
  clubs$club_name <- trimws(clubs$club_name)
} else if("clubname" %in% colnames(clubs)) {
  clubs$club_name <- trimws(clubs$clubname)
} else {
  clubs$club_name <- clubs$club_id
}

student_col <- colnames(responses)[1]
initial_club_cols <- trimws(tolower(colnames(responses)[-1]))
colnames(responses) <- c(student_col, initial_club_cols)

# Μετατροπή σε 'long' μορφή για ευκολότερη διαχείριση προτιμήσεων
responses_long_master <- responses |>
  pivot_longer(cols = -all_of(student_col),
               names_to = "ClubID", values_to = "Rank") |>
  mutate(
    ClubID = trimws(tolower(ClubID)),
    # Εξασφάλιση ότι το Rank είναι ακέραιος
    Rank = as.integer(Rank)
  ) |>
  # Φιλτράρουμε μόνο τις καταγεγραμμένες προτιμήσεις
  filter(!is.na(Rank))


round <- 1L
elimination_log <- data.frame(Round=integer(), ClubID=character(), ClubName=character(), FirstPrefCount=integer(), stringsAsFactors = FALSE)
active_clubs <- initial_club_cols

repeat {
  # --- Βήμα 1: Υπολογισμός Πρώτων Προτιμήσεων (Τρέχουσα κατάσταση) ---
  # Βρίσκουμε την τρέχουσα υψηλότερη προτίμηση (το χαμηλότερο Rank) για κάθε μαθητή
  current_prefs <- responses_long_master |>
    # Φιλτράρουμε μόνο τους ενεργούς Ομίλους
    filter(ClubID %in% active_clubs) |>
    # Βρίσκουμε το ελάχιστο Rank (την πρώτη προτίμηση)
    group_by(across(all_of(student_col))) |>
    filter(Rank == min(Rank, na.rm = TRUE)) |>
    ungroup()
  
  # Μετράμε τις πρώτες προτιμήσεις
  counts <- current_prefs |>
    count(ClubID, name = "FirstPrefCount") |>
    right_join(data.frame(ClubID = active_clubs), by = "ClubID") |>
    replace_na(list(FirstPrefCount = 0)) |>
    # Μετατροπή σε named vector για εύκολη χρήση
    tibble::deframe()
  
  cat(sprintf("\n--- Γύρος %d ---\n", round))
  cat("Όμιλοι και πρώτες προτιμήσεις:\n")
  print(data.frame(ClubID=names(counts), FirstPref=as.integer(counts)), row.names = FALSE)
  
  # --- Βήμα 2: Έλεγχος Τερματισμού ---
  if(length(active_clubs) == 0) {
    cat("\nΤερματισμός: Δεν έμεινε κανένας Όμιλος.\n")
    break
  }
  if(all(counts >= min_threshold)) {
    cat("\nΤερματισμός: όλοι οι ενεργοί Όμιλοι έχουν τουλάχιστον ", min_threshold, " πρώτες προτιμήσεις.\n", sep = "")
    # Αναδημιουργία του τελικού responses_wide για αποθήκευση
    responses_final_wide <- responses_long_master |>
      filter(ClubID %in% active_clubs) |>
      pivot_wider(id_cols = all_of(student_col),
                  names_from = ClubID, values_from = Rank)
    
    write_csv(responses_final_wide, "responses_final.csv")
    break
  }
  
  # --- Βήμα 3: Εξάλειψη ---
  counts_df <- data.frame(ClubID=names(counts), FirstPrefCount=as.integer(counts)) |>
    filter(FirstPrefCount < min_threshold)
  
  if(nrow(counts_df) == 0) {
    # Αυτό δεν θα έπρεπε να συμβεί λόγω του προηγούμενου ελέγχου,
    # αλλά εξασφαλίζει τη συνέχεια αν το all(counts >= min_threshold) δεν πιάσει την περίπτωση.
    cat("\nΤερματισμός: Όλοι οι Όμιλοι άνω του ορίου.\n")
    next 
  }
  
  min_count <- min(counts_df$FirstPrefCount)
  elim_candidates <- counts_df |> filter(FirstPrefCount == min_count)
  
  # Λογική Ισοπαλίας: επιλέγουμε αλφαβητικά
  club_to_elim <- sort(elim_candidates$ClubID)[1]
  
  match_idx <- match(club_to_elim, clubs$club_id)
  club_name <- if(!is.na(match_idx)) clubs$club_name[match_idx] else club_to_elim
  cat(sprintf("Εξαλείφεται ο Όμιλος: %s (%s) με %d πρώτες προτιμήσεις.\n",
              club_to_elim, club_name, min_count))
  
  elimination_log <- rbind(elimination_log,
                           data.frame(Round=round,
                                      ClubID=club_to_elim,
                                      ClubName=club_name,
                                      FirstPrefCount=min_count,
                                      stringsAsFactors = FALSE))
  
  # --- Βήμα 4: Μεταφορά Ψήφων (Αναβάθμιση Προτιμήσεων) ---
  
  # 1. Απενεργοποίηση του εξαλειφθέντος Ομίλου
  active_clubs <- setdiff(active_clubs, club_to_elim)
  
  # 2. Ακύρωση του club_to_elim (θέτουμε το Rank του σε NA)
  responses_long_master <- responses_long_master |>
    mutate(Rank = ifelse(ClubID == club_to_elim, NA_integer_, Rank)) |>
    # Αφαιρούμε πλέον τα NA rank, καθώς ο Όμιλος δεν μετράει
    filter(!is.na(Rank))
  
  # 3. Αναβαθμίζουμε τα Ranks για κάθε μαθητή
  responses_long_master <- responses_long_master |>
    group_by(across(all_of(student_col))) |>
    # Αναβαθμίζουμε τα Ranks (1, 3, 4, 5... γίνεται 1, 2, 3, 4...)
    # Επανα-αριθμούμε τις προτιμήσεις με βάση την τρέχουσα σειρά τους
    mutate(Rank = rank(Rank, ties.method = "min")) |> 
    ungroup()
  
  # --- Αποθήκευση Ενδιάμεσου Αρχείου (responses_wide) ---
  round_file <- sprintf("%s%d.csv", output_prefix, round)
  
  responses_round_wide <- responses_long_master |>
    pivot_wider(id_cols = all_of(student_col),
                names_from = ClubID, values_from = Rank) |>
    # Συμπληρώνουμε με NA τους Όμιλους που έχουν ήδη εξαλειφθεί (αν υπάρχουν)
    select(all_of(student_col), all_of(active_clubs))
  
  write_csv(responses_round_wide, round_file)
  cat("Αποθηκεύτηκε ενδιάμεσο αρχείο: ", round_file, "\n")
  
  round <- round + 1L
}

if(nrow(elimination_log)>0) write_csv(elimination_log, "elimination_log.csv")
cat("\nΟλοκληρώθηκε η διαδικασία εξάλειψης. Δες το elimination_log.csv.\n")