lisp/extrema.lisp @ 502c03ce389d

More
author Steve Losh <steve@stevelosh.com>
date Thu, 04 Dec 2025 10:15:30 -0500
parents (none)
children (none)
(eval-when (:compile-toplevel :load-toplevel :execute)
  (ql:quickload '(:adopt :iterate :losh :alexandria :conserve)
                :silent t))

(defpackage :extrema
  (:use :cl :iterate :losh)
  (:export :toplevel :*ui*))

(in-package :extrema)


;;;; Functionality ------------------------------------------------------------
(defun run (stream field n &key reverse numeric)
  (iterate
    (with results = (list))
    (with predicate = (if numeric
                        (if reverse
                          #'>
                          #'<)
                        (if reverse
                          #'string>
                          #'string<)))
    (for line :in-stream stream :using #'read-line)
    (for fields = (conserve:read-row line))
    (for i :from 0)
    (when (null fields)
      (next-iteration))
    (for val = (parse-integer (nth (1- field) fields)))
    (for initial = (< i n))
    (when (or initial
              (if numeric
                (if reverse
                  (funcall predicate val (car (first results)))
                  (< val (car (first results))))
                (if reverse
                  (string> val (car (first results)))
                  (string< val (car (first results))))))
      (setf results (merge 'list
                           (list (cons val line))
                           (if initial results (rest results))
                           #'<
                           :key #'car)))
    (finally (map nil (lambda (r) (write-line (cdr r)))
                  (nreverse results)))))


;;;; User Interface -----------------------------------------------------------
(defparameter *examples*
  '(("Do the equivalent of `sort -nr -k 4 | head -n 100`:" .
     "extrema -nr -k 4 -l 100")))


(defparameter *option/help*
  (adopt:make-option 'help
    :help "Display help and exit."
    :long "help"
    :short #\h
    :reduce (constantly t)))

(defparameter *o/field*
  (adopt:make-option 'field
    :result-key 'field
    :long "key"
    :short #\k
    :parameter "FIELD"
    :help "Key (1-indexed) to compare (default 1)."
    :initial-value 1
    :key #'parse-integer
    :reduce #'adopt:last))

(defparameter *o/lines*
  (adopt:make-option 'n
    :result-key 'n
    :short #\l
    :long "lines"
    :parameter "N"
    :help "Collect the top N results (default 10)."
    :initial-value 10
    :key #'parse-integer
    :reduce #'adopt:last))

(adopt:defparameters (*o/numeric* *o/no-numeric*)
  (adopt:make-boolean-options 'numeric
    :result-key 'numeric
    :short #\n
    :long "numeric"
    :help "Parse keys as integers for comparison."
    :help-no "Compare keys lexicographically (the default)."))

(adopt:defparameters (*o/reverse* *o/no-reverse*)
  (adopt:make-boolean-options 'reverse
    :result-key 'reverse
    :short #\r
    :long "reverse"
    :help "Collect smallest elements instead of largest."
    :help-no "Collect largest elements (the default)."))



(adopt:define-string *help*
  "extrema processes lines of standard input and prints the top N")


(defparameter *ui*
  (adopt:make-interface
    :name "extrema"
    :usage "[OPTIONS]"
    :summary "a more efficient `sort | head`"
    :help *help*
    :examples *examples*
    :contents (list *option/help*
                    *o/field*
                    *o/lines*
                    *o/reverse*
                    *o/no-reverse*
                    *o/numeric*
                    *o/no-numeric*
                    )))

(defun toplevel ()
  (adopt::quit-on-ctrl-c ()
    (multiple-value-bind (arguments options) (adopt:parse-options-or-exit *ui*)
      (when (gethash 'help options)
        (adopt:print-help-and-exit *ui*))
      (handler-case
          (progn (assert (null arguments) () "No arguments accepted.")
                 (run *standard-input*
                      (gethash 'field options)
                      (gethash 'n options)
                      :reverse (gethash 'reverse options)))
        (error (e) (adopt:print-error-and-exit e))))))