Лисп случай с другим предикатом равенства - PullRequest
0 голосов
/ 02 декабря 2018

Как часть бота, играющего в крестики-нолики, мне нужна функция, которая оценивает комбинации плиток и очков.Код будет выглядеть примерно так:

(case combination
    ("EEEEE" 0)
    ("EEEEP" 1)
    ("EEEPE" 1)
    ("EEEPP" 2)
    ("EEPEE" 1)
    ("EEPEP" 2)
    ("EEPPE" 2)
    ("EEPPP" 3)
    ("EPEEE" 1)
    ("EPEEP" 2)
    ("EPEPE" 2)
    ("EPEPP" 3)
    ("EPPEE" 2)
    ("EPPEP" 3)
    ("EPPPE" 3)
    ("EPPPP" 4)
    ("PEEEE" 1)
    ("PEEEP" 2)
    ("PEEPE" 2)
    ("PEEPP" 3)
    ("PEPEE" 2)
    ("PEPEP" 3)
    ("PEPPE" 3)
    ("PEPPP" 4)
    ("PPEEE" 2)
    ("PPEEP" 3)
    ("PPEPE" 3)
    ("PPEPP" 4)
    ("PPPEE" 3)
    ("PPPEP" 4)
    ("PPPPE" 4)
    ("PPPPP" 5))

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

Проблема в том, что в этом случае используется предикат, который не возвращает истину для идентичных строк, которые не являются одним и тем же объектом (трудно найти, если это eq или eql).Как вы можете это изменить?

РЕДАКТИРОВАТЬ: я решил исходную проблему путем преобразования строки в соответствующее двоичное число, которое можно сравнить с помощью eql или использовать в качестве индекса в списке.

Ответы [ 5 ]

0 голосов
/ 07 декабря 2018

Другим решением может быть определение правил в виде списка и поиск в списке соответствующей строки.

(defun match-combination (combination)
  (let ((rules '(("EEEEE" 0)
                 ("EEEEP" 1)
                 ("EEEPE" 1)
                         ...)))
    (cadr (find combination rules :key #'car :test #'string=))))
0 голосов
/ 03 декабря 2018

Другой подход заключается в преобразовании combination в символ.Полученный код будет выглядеть следующим образом:

(case (intern combination)
  (EEEEE 0)
  (EEEEP 1)
  (EEEPE 1)
  ...)

Но вы должны иметь в виду, что intern работает во время выполнения в контексте текущего пакета (*package*), что означает, что если это частьфункции, которая вызывается вне пакета, в котором она определена, она не будет работать.Есть два способа обойти это (в основном, два варианта одного): либо intern в пакете ((intern combination <your-package>)), либо intern в качестве ключевого слова.В последнем случае вся форма будет выглядеть следующим образом:

(case (intern combination :keyword)
  (:EEEEE 0)
  (:EEEEP 1)
  (:EEEPE 1)
  ...)

Также стоит отметить соображения производительности.Они не должны быть проблемой, поскольку, хотя intern ing является довольно сложной операцией, первоначально, когда она вызывается повторно для уже интернированного символа, это, в основном, просто поиск по словарю.

0 голосов
/ 02 декабря 2018

Ваш код просто вычисляет (count #\P combination).

Обычно я конвертирую строку в число и вычисляю его.Использование LOGCOUNT, чтобы получить бит или что-то.Даже если бы я использовал большой CASE -подобный переключатель, я бы преобразовал строку один раз в число, вместо того, чтобы выполнять множество сравнений строк.

0 голосов
/ 02 декабря 2018

Вы можете написать макрос:

(defmacro string-case (key &rest forms)
  (let ((k (gensym "KEY")))
    `(let ((,k ,key))
       (cond
         ,@(loop for (str . body) in forms
                 collect `((string= ,k ,str) ,@body))))))

И затем использовать его как case.Обратите внимание, что этот макрос будет проверять каждую подстроку по одной (до 32 веток в вашем случае), что менее эффективно, чем, например, смотреть на первый символ и решать, что делать, затем смотреть на следующий символ и так далее (5-10 веток в вашем случае), что менее эффективно, чем делать то, что вы на самом деле намереваетесь (например, считая #\P) (это можно сделать с 5-6 легко предсказуемыми ветвями, или, может быть, 10 в зависимости от реализации).Из этих вариантов второй генерирует больше всего кода, затем первый, а затем третий.

0 голосов
/ 02 декабря 2018

Используйте alexandria:switch из библиотеки alexandria, доступной из quicklisp.

(switch (combination :test #'string=)
  ("FOO" …)
  …)
...