initial commit
This commit is contained in:
commit
30fe2ec951
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
sortvote
|
||||||
|
sortvote.exe
|
||||||
|
|
9
README.md
Normal file
9
README.md
Normal file
@ -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.
|
39
entries_esc2025.txt
Normal file
39
entries_esc2025.txt
Normal file
@ -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
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module sortvote
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require github.com/mappu/miqt v0.10.0
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -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=
|
100
main.go
Normal file
100
main.go
Normal file
@ -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()
|
||||||
|
}
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 114 KiB |
59
window.go
Normal file
59
window.go
Normal file
@ -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() {
|
||||||
|
|
||||||
|
}
|
53
window.ui
Normal file
53
window.ui
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>MainWindow</class>
|
||||||
|
<widget class="QMainWindow" name="MainWindow">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>320</width>
|
||||||
|
<height>415</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>SortVote</string>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="centralwidget">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QListWidget" name="infoArea">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="b1">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="b2">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
Loading…
x
Reference in New Issue
Block a user