commit 30fe2ec95196b0c58349e177c0f029fb784828c2 Author: mappu Date: Sat May 3 12:39:16 2025 +1200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d222e93 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +sortvote +sortvote.exe + diff --git a/README.md b/README.md new file mode 100644 index 0000000..921107d --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# sortvote + +Interactively sort a textfile with pairwise ranking. + +Mostly useful for ranking your favourite Eurovision songs. Made with MIQT. + +Usage: `./sortvote file.txt` + +Any blank lines, or lines with leading `#` characters are skipped from sorting. \ No newline at end of file diff --git a/entries_esc2025.txt b/entries_esc2025.txt new file mode 100644 index 0000000..4f55589 --- /dev/null +++ b/entries_esc2025.txt @@ -0,0 +1,39 @@ +# Eurovision Song Contest 2025 entrants + +๐Ÿ‡ฆ๐Ÿ‡ฑ Albania - Shkodra Elektronike - Zjerm +๐Ÿ‡ฆ๐Ÿ‡ฒ Armenia - Parg - Survivor +๐Ÿ‡ฆ๐Ÿ‡น Austria - JJ - Wasted Love +๐Ÿ‡ฆ๐Ÿ‡บ Australia - Go-Jo - Milkshake Man +๐Ÿ‡ฆ๐Ÿ‡ฟ Azerbaijan - Mamagama - Run with U +๐Ÿ‡ง๐Ÿ‡ช Belgium - Red Sebastian - Strobe Lights +๐Ÿ‡จ๐Ÿ‡ญ Switzerland - Zoรซ Mรซ - Voyage +๐Ÿ‡จ๐Ÿ‡พ Cyprus - Theo Evan - Shh +๐Ÿ‡จ๐Ÿ‡ฟ Czechia - Adonxs - Kiss Kiss Goodbye +๐Ÿ‡ฉ๐Ÿ‡ช Germany - Abor & Tynna - Baller +๐Ÿ‡ฉ๐Ÿ‡ฐ Denmark - Sissal - Hallucination +๐Ÿ‡ช๐Ÿ‡ช Estonia - Tommy Cash - Espresso Macchiato +๐Ÿ‡ช๐Ÿ‡ธ Spain - Melody - Esa diva +๐Ÿ‡ซ๐Ÿ‡ฎ Finland - Erika Vikman - Ich komme +๐Ÿ‡ซ๐Ÿ‡ท France - Louane - Maman +๐Ÿ‡ฌ๐Ÿ‡ง United Kingdom - Remember Monday - What the Hell Just Happened +๐Ÿ‡ฌ๐Ÿ‡ช Georgia - Mariam Shengelia - Freedom +๐Ÿ‡ฌ๐Ÿ‡ท Greece - Klavdia - Asteromata +๐Ÿ‡ญ๐Ÿ‡ท Croatia - Marko Boลกnjak - Poison Cake +๐Ÿ‡ฎ๐Ÿ‡ช Ireland - Emmy - Laika Party +๐Ÿ‡ฎ๐Ÿ‡ฑ Israel - Yuval Raphael - New Day Will Rise +๐Ÿ‡ฎ๐Ÿ‡ธ Iceland - Vรฆb - Rรณa +๐Ÿ‡ฎ๐Ÿ‡น Italy - Lucio Corsi - Volevo essere un duro +๐Ÿ‡ฑ๐Ÿ‡น Lithuania - Katarsis - Tavo akys +๐Ÿ‡ฑ๐Ÿ‡บ Luxembourg - Laura Thorn - La poupรฉe monte le son +๐Ÿ‡ฑ๐Ÿ‡ป Latvia - Tautumeitas - Bur man laimi +๐Ÿ‡ฒ๐Ÿ‡ช Montenegro - Nina ลฝiลพiฤ‡ - Dobrodoลกli +๐Ÿ‡ฒ๐Ÿ‡น Malta - Miriana Conte - Serving +๐Ÿ‡ณ๐Ÿ‡ฑ Netherlands - Claude - C'est la vie +๐Ÿ‡ณ๐Ÿ‡ด Norway - Kyle Alessandro - Lighter +๐Ÿ‡ต๐Ÿ‡ฑ Poland - Justyna Steczkowska - Gaja +๐Ÿ‡ต๐Ÿ‡น Portugal - Napa - Deslocado +๐Ÿ‡ท๐Ÿ‡ธ Serbia - Princ - Mila +๐Ÿ‡ธ๐Ÿ‡ช Sweden - KAJ - Bara bada bastu +๐Ÿ‡ธ๐Ÿ‡ฎ Slovenia - Klemen - How Much Time - Do We Have Left +๐Ÿ‡ธ๐Ÿ‡ฒ San Marino - Gabry Ponte - Tutta l'Italia +๐Ÿ‡บ๐Ÿ‡ฆ Ukraine - Ziferblat - Bird of Pray \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..53b2abb --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module sortvote + +go 1.19 + +require github.com/mappu/miqt v0.10.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..44cec08 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/mappu/miqt v0.10.0 h1:w+ucRwdoIO7xS32us34lL2Mh0+aarywNpQz6c76ZSDY= +github.com/mappu/miqt v0.10.0/go.mod h1:xFg7ADaO1QSkmXPsPODoKe/bydJpRG9fgCYyIDl/h1U= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9cb778b --- /dev/null +++ b/main.go @@ -0,0 +1,100 @@ +package main + +import ( + "fmt" + "math/rand" + "os" + "sort" + "strings" + + qt "github.com/mappu/miqt/qt6" + "github.com/mappu/miqt/qt6/mainthread" +) + +func main() { + + qt.NewQApplication(os.Args) + appName := "SortVote" + qt.QCoreApplication_SetApplicationName(appName) + + if len(os.Args) < 2 { + qt.QMessageBox_Critical(nil, appName, "Usage: ./sortvote filename.txt") + os.Exit(1) + } + + b, err := os.ReadFile(os.Args[1]) + if err != nil { + qt.QMessageBox_Critical(nil, appName, fmt.Sprintf("Error reading file %q: %s", os.Args[1], err.Error())) + os.Exit(1) + } + + file_lines := strings.Split(string(b), "\n") + entries := make([]string, 0, len(file_lines)) + for _, line := range file_lines { + if len(line) > 0 && line[0] != '#' { + entries = append(entries, line) + } + } + + // Start with a randomized order for fairness + + for i := range entries { + j := rand.Intn(i + 1) + entries[i], entries[j] = entries[j], entries[i] + } + + // + + ui := NewMainWindowUi() + ui.MainWindow.SetWindowTitle(os.Args[1] + " - " + appName) + + ui.infoArea.AddItem(fmt.Sprintf("Sorting %d entries", len(entries))) + + ret := make(chan bool, 0) + ui.b1.OnClicked(func() { // Set up click handlers only once + ret <- true + }) + ui.b2.OnClicked(func() { + ret <- false + }) + + ui.MainWindow.Show() + + comparisons := 0 + + go func() { + sort.SliceStable(entries, func(i, j int) bool { + + mainthread.Wait(func() { + comparisons++ + + ui.infoArea.AddItem(fmt.Sprintf("Comparison #%d", comparisons)) + ui.infoArea.ScrollToBottom() + + // Escape & used as an alt-accelerator + ui.b1.SetText(strings.ReplaceAll(entries[i], `&`, `&&`)) + ui.b2.SetText(strings.ReplaceAll(entries[j], `&`, `&&`)) + }) + + return <-ret + }) + + mainthread.Start(func() { + ui.b1.SetEnabled(false) + ui.b2.SetEnabled(false) + + // Sorting 39 entries took 77 comparisons + // Finished sorting 37 entries in 219 comparisons + // 37 x log_2(37) = 192 so we are 15% over(?) + + ui.infoArea.AddItem(fmt.Sprintf("Finished sorting %d entries in %d comparisons", len(entries), comparisons)) + + for i, ent := range entries { + ui.infoArea.AddItem(fmt.Sprintf("%02d. %s", i+1, ent)) + ui.infoArea.ScrollToBottom() + } + }) + }() + + qt.QApplication_Exec() +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000..361af75 Binary files /dev/null and b/screenshot.png differ diff --git a/window.go b/window.go new file mode 100644 index 0000000..de99b4e --- /dev/null +++ b/window.go @@ -0,0 +1,59 @@ +// Generated by miqt-uic. To update this file, edit the .ui file in +// Qt Designer, and then run 'go generate'. +// +//go:generate miqt-uic -InFile window.ui -OutFile window.go -Qt6 + +package main + +import ( + qt "github.com/mappu/miqt/qt6" +) + +type MainWindowUi struct { + MainWindow *qt.QMainWindow + centralwidget *qt.QWidget + verticalLayout *qt.QVBoxLayout + infoArea *qt.QListWidget + b1 *qt.QPushButton + b2 *qt.QPushButton +} + +// NewMainWindowUi creates all Qt widget classes for MainWindow. +func NewMainWindowUi() *MainWindowUi { + ui := &MainWindowUi{} + + ui.MainWindow = qt.NewQMainWindow(nil) + ui.MainWindow.Resize(320, 415) + ui.MainWindow.SetWindowTitle("SortVote") + + ui.centralwidget = qt.NewQWidget(ui.MainWindow.QWidget) + + ui.verticalLayout = qt.NewQVBoxLayout(ui.centralwidget) + ui.verticalLayout.SetContentsMargins(11, 11, 11, 11) + ui.verticalLayout.SetSpacing(6) + + ui.infoArea = qt.NewQListWidget(ui.centralwidget) + /* miqt-uic: no handler for infoArea property 'sizePolicy' */ + + ui.verticalLayout.AddWidget(ui.infoArea.QWidget) + + ui.b1 = qt.NewQPushButton(ui.centralwidget) + /* miqt-uic: no handler for b1 property 'sizePolicy' */ + + ui.verticalLayout.AddWidget(ui.b1.QWidget) + + ui.b2 = qt.NewQPushButton(ui.centralwidget) + /* miqt-uic: no handler for b2 property 'sizePolicy' */ + + ui.verticalLayout.AddWidget(ui.b2.QWidget) + ui.MainWindow.SetCentralWidget(ui.centralwidget) // Set central widget + + ui.Retranslate() + + return ui +} + +// Retranslate reapplies all text translations. +func (ui *MainWindowUi) Retranslate() { + +} diff --git a/window.ui b/window.ui new file mode 100644 index 0000000..9568a18 --- /dev/null +++ b/window.ui @@ -0,0 +1,53 @@ + + + MainWindow + + + + 0 + 0 + 320 + 415 + + + + SortVote + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + + + + + +