LINUX.ORG.RU

Выясняем с помощью Emacs, какой ЯП лучше

 ,


0

2

Написал небольшое приложение для Emacs, которое позволяет ответить на subj, а так же многие подобные вопросы.

Как это работает: вы последовательно выбираете предпочтительный вариант из двух предложенных. elosort пересчитывает и сохраняет Elo-рейтинг для элементов списка и формирует таблицу с результатами. Результаты становятся более-менее устойчивыми после примерно N*N*2 или N*N*3 сравнений (каждый элемент с каждым 2 или 3 раза).

Как пользоваться: подготовьте список языков программирования (оптимальный размер списка 10-15) для сравнения в файлу в формате org-mode:

#+elosort_question: Какой язык программирования лучше?
* Common Lisp
* C++
* Python
* Scheme
* Pascal

(Рейтинг и результаты сравнений сохраняются в этот же файл.)

Загрузите elosort в Emacs. Например, скопируйте код elosort.el в *scratch* и выполните eval-buffer. Вызовите команду elosort-fight {M-x elosort-fight RET} и укажите путь к файлу из предыдущего пункта. Выбирайте предпочтительный вариант с помощью мышки или клавишами , и .. Посмотреть таблицу результатов - t. Вернуться к сравнениями из этой таблицы - f.

Приветствуются комментарии по улучшению кода и фич-реквесты!

;;; elosort.el --- Sort a list of incomparable things  -*- lexical-binding: t; -*-

;; Copyright (C) 2024 Sergey Lilo
;; Author: Sergey Lilo <s3rg31110@gmail.com>
;; Created: 14 Apr 2024
;; Keywords: games
;; URL: https://github.com/lilo/elosort.el

;; This file is NOT part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; 1. Prepare file with list of things in org-mode format:
;; #+elosort_question: What is tastier?
;; * apples
;; * oranges
;; * pears
;; * ...
;;
;; Run elosort with {M-x elosort-fight RET} and select the file.
;;
;; You can learn more about Elo here:
;; https://en.wikipedia.org/wiki/Elo_rating_system
;;

;;; Code:

(defcustom elosort-k 20
  "The K."
  :group 'elo)

(defcustom elosort-starting-elo 1200
  "Starting elo."
  :group 'elo)

(defvar elosort-file nil
  "The DB file.")

(defun elosort-get-question ()
  "Return property `elosort_question' from the file."
  (when-let
      ((elosort-question
        (alist-get
         "ELOSORT_QUESTION"
         (org-collect-keywords '("elosort_question"))
         (not 'default)
         (not 'remove)
         #'equal)))
    (car elosort-question)))

(defun elosort-get-by-title (title)
  "Return POM of the heading with matching TITLE."
  (cl-dolist (pt (org-map-entries #'point))
    (when
        (string-equal
         title
         (alist-get
          "ITEM"
          (org-entry-properties pt)
          (not 'default)
          (not 'remove)
          #'string-equal))
      (cl-return pt))))

(defun elosort-compute-elo (winner loser)
  "Compute new elo for WINNER and LOSER using K."
  (let* ((k elosort-k)
         (p1 (/ 1.0 (+ 1.0 (expt 10 (/ (- loser winner) 400.0)))))
         (w (+ winner (* k (- 1 p1))))
         (p2 (/ 1.0 (+ 1.0 (expt 10 (/ (- winner loser) 400.0)))))
         (l (+ loser (* k (- 0 p2)))))
    (cons (round w) (round l))))

(defun elosort-get-alist ()
  "Current entry as alist."
  (interactive)
  (let* ((pom (point))
         (elo (org-entry-get pom "ELO"))
         (title (org-entry-get pom "ITEM"))
         (plist-fights
          (org-entry-get-multivalued-property pom "ELO_FIGHTS"))
         (plist-numfights ;; sum values in plist
          (cl-loop
           for n from 1 to (length plist-fights) by 2
           sum (string-to-number (nth n plist-fights)))))
    `((elo . ,(if (stringp elo) (string-to-number elo) elosort-starting-elo))
      (title . ,title)
      (fights . ,plist-fights)
      (num-fights . ,plist-numfights))))


(defun elosort-compare-tabulated (item1 item2)
  "Compare tabulated items by rating"
  (let ((elo1
         (pcase
             item1
           (`(,tabulated-id ,_)
            (let-alist tabulated-id .elo))))
        (elo2
         (pcase
             item2
           (`(,tabulated-id ,_)
            (let-alist tabulated-id .elo)))))
    (< elo1 elo2)))

(defvar elosort-list-top-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "f") #'elosort-fight-from-top)
    map)
  "elosort scoreboard mode keymap.")

(define-derived-mode elosort-list-top-mode tabulated-list-mode "elosort-list-top-mode"
  "Elo Top"
  (setq truncate-lines t)
  (setq buffer-read-only t)
  (setq tabulated-list-format [("rating" 8 elosort-compare-tabulated)
			       ("title" 50 t)
                               ("num_games" 16 t)])
  (setq tabulated-list-sort-key
        (cons "rating" :inverted))
  (add-hook 'tabulated-list-revert-hook #'elosort-list-top-refresh (not 'depth) :local))

(put 'elosort-list-top-mode 'mode-class 'special)

(defun elosort-list-top (filename)
  "Display the score board."
  (interactive "fSelect file: ")
  (let ((elo-buf
         (get-buffer-create
           "*Org Elo Top*")))
    (switch-to-buffer elo-buf)
    (elosort-list-top-mode)
    (setq-local elo-source-buffer elo-buf)
    (setq elosort-file filename)
    (elosort-list-top-refresh)))

(defun elosort-list-top-refresh ()
  "Refresh elo top."
  (interactive)
  (let* ((entries
          (save-excursion
            (with-current-buffer (find-file-noselect elosort-file)
              (org-map-entries
               (lambda () (elosort-tabulate (elosort-get-alist))))))))
    (setq tabulated-list-entries entries)
    (tabulated-list-init-header)
    (tabulated-list-print)))

(defun elosort-tabulate (alist)
  "Item alist as tabulated-list entry."
  (let-alist alist
    (list
     alist
     (vector
      (list (number-to-string .elo))
      (list .title)
      (list (number-to-string .num-fights))))))

(defun elosort-list-top-from-fight ()
  "Call from fight mode providing `elosort-file' as argument"
  (interactive)
  (elosort-list-top elosort-file))


(defvar elosort-fight-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd ",") #'elosort-fight-win1)
    (define-key map (kbd ".") #'elosort-fight-win2)
    (define-key map (kbd "g") #'elosort-fight-revert)
    (define-key map (kbd "t") #'elosort-list-top-from-fight)
    map)
  "Fight mode keymap.")

(defun elosort-fight-win1 ()
  "Set p1 as winner and update"
  (interactive)
  (elosort-fight-update :p1-win))

(defun elosort-fight-win2 ()
  "Set p2 as winner and update"
  (interactive)
  (elosort-fight-update (not :p1-win)))

(defun elosort-fight-update (p1-winner-p)
  "Update ratings DB for current pair."
  (let* ((p1 (copy-alist elosort-p1))
         (p2 (copy-alist elosort-p2))
         (p1-title (let-alist p1 .title))
         (p2-title (let-alist p2 .title))
         (p1-fights (let-alist p1 .fights))
         (p2-fights (let-alist p2 .fights))
         (p1-vs-p2-num
          (1+ (string-to-number
           (or
            (plist-get p2-fights p1-title #'equal)
            "0"))))
         (p2-vs-p1-num
          (1+ (string-to-number
           (or
            (plist-get p1-fights p2-title #'equal)
            "0"))))
         (p1-elo (or (let-alist p1 .elo) elosort-starting-elo))
         (p2-elo (or (let-alist p2 .elo) elosort-starting-elo))
         (new-elos
          (if
              p1-winner-p
              (elosort-compute-elo p1-elo p2-elo)
            (elosort-compute-elo p2-elo p1-elo)))
         (p1-new-elo (if p1-winner-p (car new-elos) (cdr new-elos)))
         (p2-new-elo (if p1-winner-p (cdr new-elos) (car new-elos)))
         (p1-new-fights (plist-put p1-fights p2-title
                               (number-to-string p2-vs-p1-num) #'equal))
         (p2-new-fights (plist-put p2-fights p1-title
                               (number-to-string p1-vs-p2-num) #'equal)))
    (save-excursion
      (with-current-buffer (find-file-noselect elosort-file)
        (org-entry-put (elosort-get-by-title p1-title) "ELO" (number-to-string p1-new-elo))
        (org-entry-put (elosort-get-by-title p2-title) "ELO" (number-to-string p2-new-elo))
        (org-entry-delete (elosort-get-by-title p1-title) "ELO_FIGHTS")
        (org-entry-delete (elosort-get-by-title p2-title) "ELO_FIGHTS")
        (apply `(org-entry-put-multivalued-property
                 ,(elosort-get-by-title p1-title)
                 "ELO_FIGHTS"
                 ,@p1-new-fights))
        (apply `(org-entry-put-multivalued-property
                 ,(elosort-get-by-title p2-title)
                 "ELO_FIGHTS"
                 ,@p2-new-fights))
        (save-buffer)))
  (elosort-fight-revert)))

(define-derived-mode elosort-fight-mode special-mode "elosort-fight-mode"
  "Fight!"
  (setq buffer-read-only t)
  (defvar-local elosort-buf nil)
  (defvar-local elosort-p1 nil "Candidate #1")
  (defvar-local elosort-p2 nil "Candidate #2"))

(put 'elosort-fight-mode 'mode-class 'special)

;;;
(defun swap (LIST el1 el2)
  "in LIST swap indices EL1 and EL2 in place"
  (let ((tmp (elt LIST el1)))
    (setf (elt LIST el1) (elt LIST el2))
    (setf (elt LIST el2) tmp)))


(defun shuffle (LIST)
  "Shuffle the elements in LIST.
shuffling is done in place."
  (cl-loop for i in (reverse (number-sequence 1 (1- (length LIST))))
        do (let ((j (random (+ i 1))))
             (swap LIST i j)))
  LIST)

(defun elosort-next-pair (items)
  "Return (cons item1 item2) from items list."
  (let* ((entries (copy-sequence items))
         (sorted
          (sort entries
                (lambda (a b)
                  (< (let-alist a .num-fights)
                     (let-alist b .num-fights)))))
         (p1 (car sorted))
         (p1-title (let-alist p1 .title))
         (but-p1 (cdr sorted))
         (p2 (car
              (sort but-p1
                    (lambda (a b)
                      (let* ((afs
                              (string-to-number
                               (or (plist-get (let-alist a .fights) p1-title #'equal) "0")))
                             (bfs
                              (string-to-number
                               (or (plist-get (let-alist b .fights) p1-title #'equal) "0"))))
                        (< afs bfs)))))))
    (cons p1 p2)))

(defun elosort-fight-revert ()
  "Refresh buffer, get next pair."
  (interactive)
  (save-excursion
    (with-current-buffer (find-file-noselect elosort-file)
      (setq elosort-question (elosort-get-question))
      (setq entries (shuffle (org-map-entries #'elosort-get-alist)))))
  (let* ((inhibit-read-only t)
         (pair (elosort-next-pair entries))
         (p1 (car pair))
         (p1-elo (let-alist p1 .elo))
         (p1-title (let-alist p1 .title))
         (p2 (cdr pair))
         (p2-elo (let-alist p2 .elo))
         (p2-title (let-alist p2 .title)))
    (setq-local elosort-p1 p1)
    (setq-local elosort-p2 p2)
    (delete-region (point-min) (point-max))
    (when elosort-question
      (insert (format "%s\n" elosort-question)))
    (insert-button p1-title 'action (lambda (_) (elosort-fight-win1)))
    (insert " vs ")
    (insert-button p2-title 'action (lambda (_) (elosort-fight-win2)))
    (insert " ")))


(defun elosort-fight (filename)
  "Start competition."
  (interactive "fSelect file: ")
  (let ((fight-buf
         (get-buffer-create
           "*Org Elo fight")))
    (pop-to-buffer-same-window fight-buf)
    (with-current-buffer fight-buf
      (setq inhibit-read-only t)
      (elosort-fight-mode)
      (setq-local elosort-buf fight-buf)
      (setq elosort-file filename)
      (elosort-fight-revert))))

(defun elosort-fight-from-top ()
  "Call from fight mode providing filename"
  (interactive)
  (elosort-fight elosort-file))

(provide 'elosort)
;;; elosort.el ends here


Последнее исправление: s1110 (всего исправлений: 1)

Написал небольшое приложение для Emacs, которое позволяет ответить на subj, а так же многие подобные вопросы.

Не позволит - ты не указал цель, для которой один из яп должен быть лучше другого.

u5er ★★
()
  1. Если результат всегда одинаковый, то почему бы последующие сравнения (после первого) не выполнять автоматически?
  2. Предпочтения могут сильно меняться в зависимости от задачи. Сферические языки в вакууме сравнивать не очень интересно.
Gentooshnik ★★★★★
()

Elosort is an Emacs extension that helps you to sort a set which elements aren’t sortable with “usual” commutative comparison.

The extension associates an Elo-rating for each element and lets you set a “competition” between elements. You pick a “winner” from the given pair and Elosort updates the ratings accordingly.

Finally you can compare apples and oranges!

Уважаемый коллега, рекомендую вам поскорее открыть для себя мощное и выразительное средство программирования, называемое в народе питоном

fruits = ["apple", "orange", "banana"]
fruits.sort(key = get_sugar) 

не благодарите

FishHook
()
Ответ на: комментарий от s1110
(let* 
  (p1 (/ 1.0 (+ 1.0 (expt 10 (/ (- loser winner) 400.0))))))
p1=1/(1+expt(10, (lower-winner)/400))

Язык для машинисток)))) Прочесть пытался, но сдался, напиши в чем задумка программы...

MOPKOBKA ★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от FishHook

Elosort is an Emacs extension that helps you to sort a set which elements aren’t sortable with “usual” commutative comparison.

aren’t sortable with “usual” commutative comparison.

В качестве рекламы “самого мощного и выразительно средства программирования“ показывает сортировку с обычным коммутативным сравнением.

Gentooshnik ★★★★★
()
Ответ на: комментарий от s1110

При сравнении одной и той же пары языков одним и тем же человеком примерно в одно и то же время (т.е. чтобы не проходили годы на пересмотр приоритетов) результат одинаковый.

Gentooshnik ★★★★★
()

вы последовательно выбираете предпочтительный вариант из двух предложенных

Т.е. выбирает пользователь? Это как-то субъективно.

dmitry237 ★★★★
()
Ответ на: комментарий от MKuznetsov

там кобол нужнее лиспа на 6 позиций :-)

лисп не вошёл в 20-ку, но прочно держится между прологом и адой

Лучше быть между Прологом и Агдой, чем между ПХП и Коболом!

s1110
() автор топика
Ответ на: комментарий от Artsi0m

tiobe мусорный индекс построенный просто по количеству запросов в поисковиках.

Значит на первом месте должен быть самы всратый и слабо документированный язык.

ЗЫ: Глянул, а там питон. Значит поправка - на первом месте язык для самых тупых, а вот на втором - то, что я подразумевал, должно было быть на первом (Спойлер: С++)

her_s_gory
()

Elo-рейтинг

The Elo rating system is a method for calculating the relative skill levels of players in zero-sum games

in zero-sum games

Ну ты понял.

То, что вокруг нас одни только игры с нулевой суммой – серьёзное когнитивное искажение, на самом деле таких игр исчезающе малое число, и почти все они искусственно придуманы.

buddhist ★★★★★
()
Ответ на: комментарий от buddhist

Не надо сюда натужно приплетать теорию игр.

В данном случае zero-sum означает последовательный выбор одного варианта из 2х, не более. В результате у всех элементов сохраняется ненулевой рейтинг.

s1110
() автор топика

Как пользоваться: подготовьте список языков программирования (оптимальный размер списка 10-15) для сравнения в файлу в формате org-mode:

пардон, но ведь можно тогда их сразу в правильном порядке и записать.

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Я тестировал на различных списках и результат иногда получался довольно неожиднным. Узнал (о себе) много нового. Например, с теми же фруктами. Черешня победила арбуз с очень незначительным преимуществом. А яблоки неожиданно оказались в самом низу списка.

Предлагаю вам самому попробовать. Когда действительно задумываешься над выбором, можешь придти к неожиданным выводам. К тому же, подкрепленными неоспоримыми цифрами. И вообще это прикольно, и еще один повод запустить Emacs.

Но если у вас уже действительно настолько сильно сформировано мнение по какому-либо вопросу, то это приложение вам ничего не даст.

s1110
() автор топика
Ответ на: комментарий от alysnix

ленивым

Именно что ленивым. Это индекс тупизны программистов на определённых языках. Т.е. в каких языках больше программистов, которые гуглят ответы.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)