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