2017-01-18 3 views
1

По существу у меня есть метод оценки, который принимает что-то вродеRuby Lisp Interpreter преобразует арифметический символ?

['+','2','3'] 

, который будет оценивать до 5.

Это в настоящее время создана как так,

def evaluate(exp) 
    f = exp[0] 
    if f == '+' then 
     return exp[1].to_i + exp[2].to_i 
    elsif f == '-' then 
     return exp[1].to_i - exp[2].to_i 
    elsif f == '*' then 
     return exp[1].to_i * exp[2].to_i 
    elsif f == '/' then 
     return exp[1].to_i/exp[2].to_i 
    end 
end 

Это прекрасно работает, но имеет быть лучшим способом сделать это без гигантского if. Есть ли способ преобразовать символ и использовать его? Как обычно интерпретируют Lisp-интерпретаторы?

ответ

4

Ruby все о динамическом программировании. На самом деле это делает ваш код до смешного прост:

def evaluate(exp) 
    exp[1].to_i.send(exp[0], exp[2].to_i) 
end 

Было бы еще проще, если эти маркеры были преобразованы на пути в:

exp.map! do |token| 
    case (token) 
    when /\A\-?\d+\z/ 
    token.to_i 
    else 
    token 
    end 
end 

Тогда вы получите это:

def evaluate(exp) 
    exp[1].send(exp[0], exp[2]) 
end 

Теперь это предполагает, что вы только поставляете действительные операции, что вы не делаете ничего абсурдного, но для тривиальных случаев это работает очень хорошо.

Если вы конвертируются все, и вы хотите, чтобы сделать это более расширяемым:

def evaluate(exp) 
    op, *args = exp 

    args.reduce(&op.to_sym) 
end 

Тогда вы можете сделать это на произвольных списков:

evaluate([ '+', 2, 1, 3, 4 ]) 
+0

Вам не нужно отделять первый аргумент и все остальное. Вы можете просто сделать «[2, 3, 4, 5, 6] .inject (: +) # => 20'. – sawa

+0

@sawa Да, я добавил немного с 'reduce', который функционально то же самое. – tadman

+0

Вообще говоря, 'сокращение 'здесь размывает идею быть LISP-подобным. АСТ добавления трех элементов не будет '{: +, 1, 2, 3}', это '{: +, 1, {: +, 2, 3}}'. Во всяком случае. – mudasobwa

3
def evaluate(*args) 
    operation, *numbers = args 
    numbers.map!(&:to_i) 
    numbers.first.public_send(operation, numbers.last) 
end 

Если вы ожидаете более, чем два числа:

def evaluate(*args) 
    operation, *numbers = args 
    numbers.map!(&:to_i).inject(&operation.to_sym) 
end 

evaluate('+','2','3', 4, 5, 6) 
#=> 20 

Вы можете добавить начальное значение, если вам нужно, чтобы убедиться, чтобы всегда возвращать Numeric из метода (убедитесь, чтобы выбрать любое ненулевое число, если операция деление или умножение):

def evaluate(*args) 
    operation, *numbers = args 
    initial_value = %w(/ *).include?(operation) ? 1 : 0 
    numbers.map!(&:to_i).inject(initial_value, &operation.to_sym) #<==== note 0 
end 

evaluate '+' 
#=> 0 
+0

NB, вы должны выбрать начальное значение '1' для умножения и деления. – akuhn

+0

@akuhn thx! отредактировано –

+0

Вам не нужно отделять первый аргумент, а остальное. Вы можете просто сделать «[2, 3, 4, 5, 6] .inject (: +) # => 20'. – sawa

1

Поскольку вы спросите как это обычно делается.

Вы создаете таблицу поиска от операций до их реализации. И затем используйте первый элемент для поиска реализации и передайте оставшиеся элементы этой функции.

По сути все, что вам нужно реализовать Лисп

  • evaluate
  • apply
  • табличного

Это ядро ​​может даже поместиться в 30-50 строк. Я не думаю, что вы хотите, чтобы мы дали вам полную реализацию. Это займет все самое интересное от написания вашего собственного письма.

я, таким образом, просто обрисовать основную структуру ...

$table = { 
    '+' => lambda { |a, b| a + b }, 
    '-' => lambda { |a, b| a - b }, 
    '*' => lambda { |a, b| a * b }, 
    '/' => lambda { |a, b| a.fdiv(b) }, 
} 

def evaluate(args, context = $table) 
    # A complete implementation of evaluate would make use of apply ... 
    head, *tail = args 
    context[head][*tail] 
end 

def apply 
    # ... 
end 

puts evaluate(['/', 4, 3]) 
# => 1.3333333333333333 

Просто так получилось, что ваши Лиспе имена функций и соответствующие имена функций Рубин одинаковы. Но это не всегда будет дано, поэтому использование справочной таблицы является более типичным решением. Я отобразил lisp / на Ruby's fdiv, чтобы продемонстрировать это.

Удовлетворительный факт, эти первые элементы называются символами, и на самом деле Ruby также использует очень похожий механизм и, следовательно, также имеет символы. Внутренне Ruby также использует вложенные таблицы поиска для организации классов и констант и методов, а также локальных переменных и т. Д.

+1

Таким образом, можно построить собственный АСТ, используя '# curry' вместо' # [] 'в' eval'. Это лучший ответ здесь до сих пор. – mudasobwa

+0

Ха-ха, я хотел оставить для них полную реализацию: по существу, все, что им нужно, это таблица поиска с некоторыми базовыми функциями lisp и реализациями 'eval' и' apply' – akuhn

+0

Nice. Однако вы не должны называть это eval. –

0

с LISP точки зрения (общий сюсюкать, чтобы быть точным), вы можете определить evaluate как:

(defun evaluate (func &rest args) 
    (apply func args)) 

в первой строке определить evaluate функцию, чтобы взять первый аргумент называется func, а остальные (если таковые имеются), чтобы положить в список под названием args.

, то вы применяете список аргументов, хранящихся в args, к функции, хранящейся в func.

Примеры использования:

(evaluate #'print "some text, printed by function evaluated by my own code!") 
=> "some text, printed by function evaluated by my own code!" 

(evaluate #'+ 2 3) 
=> 5 

этот странный #' кавычки имя функции, в противном случае «язык» будет пытаться оценить его, как это делает со всеми аргументами перед передачей функционировать.

Я также настоятельно рекомендую «структуру и интерпретацию компьютерных программ», по крайней мере видео! в середине есть эта замечательная лекция об оценке, в основном сердце (почти) любого компьютерного языка на 5 досках! (Ну ... зеленый;)

0

Немного уборщик версия одного и того же:

def evaluate(operation, *numbers) 
    numbers.map(&:to_i).reduce(operation) 
end 

evaluate('+', '2', '3')  # => 5 
evaluate('/', '6', '2', '3') # => 1 
1

Как правило, Lisp переводчиков с этим справиться?

Lisp использует символы для глобальных имен функций. A символ - это именованная вещь с несколькими связанными данными. Одна из них может быть функцией .

CL-USER 42 > (symbol-function '+) 
#<Function + 4100044D34> 

CL-USER 43 > (funcall (symbol-function '+) 1 2 3 4) 
10 

Так для простого вычисления выражения:

CL-USER 50 > (defun eval-expression (expression) 
       (destructuring-bind (function &rest arguments) 
        expression 
       (apply function arguments))) 
EVAL-EXPRESSION 

CL-USER 51 > (eval-expression '(+ 1 2 3 4)) 
10 

себя символы организованы в пакеты, которые вы можете себе представить, как какой-то специализированной таблицы. Пакеты сопоставление имен с символами:

CL-USER 52 > (find-symbol "+") 
+ 
:INHERITED 

Если вы хотите оценить строку, вы должны сначала прочитать строку, чтобы преобразовать строку в LISP данных.читателя будет искать символы, здесь +, из текущего пакета:

CL-USER 53 > (eval-expression (read-from-string "(+ 1 2 3 4)")) 
10 
0

Есть много различных решений, и вы можете выбрать тот, который более милый для вас, есть несколько от меня:

exp = ['+','2','3'] 

def evaluate(exp) 
    exp.map(&:to_i).inject(exp.shift) 
end 

# > evaluate(exp) 
# => 5 

def evaluate(operation, *nums) 
    nums.map(&:to_i).inject(operation) 
end 

# > evaluate(*exp) 
# => 5 
Смежные вопросы