LINUX.ORG.RU

[Lisp] Hello-world

 


0

2

Осваиваю Lisp. В качестве первого упражнения решаю следующую задачу. Имеется текстовый файл с часами разработчика в формате

<дата> <модификатор деятельности> <количество часов> <описание>

(Файл может содержать комментарии, начинающие с знака «#».) Необходимо подсчитать общее количество часов работы.

Например, для файла

20.05.2011 RD 2 Чтение глав «Функции» и «Параметры» из PCL
20.05.2011 OT 1 Установка SBCL
20.05.2011 CO 2 Кодирование и отладка функции count-hours

Результат должен быть 5.

Вот мое решение:

;;;; Program reads file with working hours of developer                                                                                        
;;;; and outputs sum of hours.

;; Counts working hours from specified filename.                                                                                               
(defun count-hours (filename)                                                                                                                
  (let ((in (open filename :if-does-not-exist nil)) (hours 0))                                                                                 
       (when in                                                                                                                                
             (loop for line = (read-line in NIL) while line do          
                   ; Doesn't process line with comments and empty line                                                                       
                   (if (not (or (= (length line) 0) (char= (elt line 0) #\#)))             
                       (setf hours (+ hours (parse-integer (get-word line 3))))))                                                            
             (close in)) hours))                                                                                                               
                                                                                                                                               
;; Returns nth word in string. Words are separated by Space and Tab                                                        
(defun get-word (str num)                                                                                                                    
  (let* ((white-spaces (list #\Space #\Tab)) (pos (get-white-space-min-pos white-spaces str)))                                                 
       (if (not pos) "0"                                                                                                                       
           (if (= (1- num) 0) (subseq str 0 pos)                                                                                               
               (get-word (string-trim white-spaces (subseq str pos)) (1- num))))))
                                                                                                                                               
                                                                                                                                               
;; Returns minimum position in str of character from list of characters (white-spaces).
;; If str doesn't have characters from list then nil is returned.                                                                              
(defun get-white-space-min-pos (white-spaces str)                                                                                              
  (let ((min-pos NIL))                                                                                                                         
       (dolist (white-space white-spaces)                                                                                                      
               (let ((pos (position white-space str)))                                                                                         
                    (if (not min-pos) (setf min-pos pos))                                                                                      
                    (if (and pos min-pos) (setf min-pos (min min-pos pos)))))                                                                  
       min-pos))                                                                                                                               

Запускать можно так:

(сount-hours «wh-dimv.txt»)

Собственно вопросы:
1. Не кажется ли вам, что здесь все написано в императивном стиле, просто с использованием скобочек? Если да, то направьте на путь истинный.
2. Есть ли в коде места, которые лучше было бы реализовать с помощью макросов? Я таких мест сейчас вижу, скорее всего, потому что слишком мало знаком с lisp. Или задача слишком маленькая, чтобы понадобились макросы?


Ответ на: комментарий от anonymous

> Чтобы реализовать полноценный DSL внутри лиспа, тебе потребуется ничуть не меньше телодвижений, чем при использовании ANTLR, Flex, Bison и подобных инструментов.

чушь

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

> Ты анонимус-гигиенофил или другой анонимус?

Я всегда пишу общелисп/cl, а не лисп, когда говорю о говенности макр. Так что нет, не я.

anonymous
()
Ответ на: комментарий от dizza

Ну я тоже вставлю 50 копеек, ок?

def count_hours(file_name):
  return sum(int(line.split()[2]) for line in open(file_name) if len(line) > 1 and line[0] != "#")

Троллим тоньше!=)

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

Угу, так вообще эпик вин. Ну я как бе не питон-программист, про LH забыл :). Хотя я бы все-таки не писал такой код в одну строку, хреновый однострочник получается. Как-то так имхо по приятнее для глаз:

def count_hours(file_name):
    return sum (
        int(line.split()[2])
        for line in open(file_name)
        if len(line) > 1 and line[0] != "#"
    )
dizza ★★★★★
()
Ответ на: комментарий от arsi
perl -MList::Util=sum -E 'say sum map /^\S+\s+\S+\s+(\d+)/, grep !/^#/, <>' test.txt

а что так длинно? можно например так:

perl -Wne '$s+=$1 if /^[^#\s]\S*\s+\S+\s+(\d+)/; END{print $s}' test.txt

или так (подлиннее, но и попонятнее):

perl -Wne 'next if /^#/; $s+=$1 if /^\S+\s+\S+\s+(\d+)/; END{print $s}' test.txt
www_linux_org_ru ★★★★★
()
Ответ на: комментарий от Divius

> Троллим тоньше!=)

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

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

ещё один выполз… :(

хочешь краткости? держи:

perl -anE'END{say$x}$x+=!/^#/&&$F[2]' test.txt

а теперь потрудись перечитать пост, на который я давал тот пример. если повезёт, может поймёшь, почему…

arsi ★★★★★
()
Ответ на: комментарий от dizza
import fileinput
from itertools import imap
from itertools import ifilter

def count_hours(file_name):
    return sum(imap(
                    lambda line: int(line.split()[2]),
                    ifilter(
                        lambda line: not (len(line) == 0 and line[0] != '#'),
                        fileinput.input(file_name))))

Шутка. Я не против лиспа. Верю, что это хорошая платформа. Но просто как язык он не такой уж и волшебный. Почти все хорошое из лиспа доступно и в питоне.

Вы привели пример из девяти строк и делаете такие выводы?

EVP
()
Ответ на: комментарий от arsi
perl -anE'END{say$x}$x+=!/^#/&&$F[2]' test.txt

тут уже пошла краткость за счет читаемости (и отсутствия W), и это не годится; как минимум надо так:

perl -WanE 'END{say $x} $x+=!/^#/&&$F[2]' test.txt

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

perl -WanE 'END{say $x} !/^#/ and $x+=$F[2]' test.txt

а теперь потрудись перечитать пост, на который я давал тот пример. если повезёт, может поймёшь, почему…

не вижу никакого смысла пихать сюда функциональщину

явный цикл вместо применения +редукции чем-то похож на QBE; и хороший транслятор должен оптимизировать его точно так же, как и явный вызов sum

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

Какая разница сколько строк я привел? Выводы я делают из другого (точно не «из строк»).

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

а вот так мне еще больше нравится (оно чуть подокументированней — имеется слово sum)

perl -WanE '$sum+=$F[2] if !/^#/ ; END{say $sum}' test.txt

и еще немного наеду на функциональщину — что если в grep придется использовать условие, для которого придется использовать код внутри регулярных выражений? а там имеется WARNING: This extended regular expression feature is considered experimental, and may be changed without notice

так что явный цикл удобнее

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

вкусовщина и неосиляторство…

> если вместо сложения будет умножение

…и классическая детская болезнь «а давайте при решении этой задачи решим ещё стопицот других задач, о которых нас не просили!».

> не вижу никакого смысла пихать сюда функциональщину

в однострочнике на перле? м/б и нет. а как насчёт сабжа, т.е. лиспа? если даже на перле ФП-реализация этой задачи выглядит стройно и логично, то какая же это «сугубо императивная задача», тем более для лиспа?

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

> а вот так мне еще больше нравится

см. выше.

> что если в grep придется использовать

см. выше.

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

> вкусовщина

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

и неосиляторство…

неосиляторство чего? неосиляторство убранных пробелов что ли, гы-гы?

не вижу никакого смысла пихать сюда функциональщину

в однострочнике на перле? м/б и нет. а как насчёт сабжа, т.е. лиспа? если даже на перле ФП-реализация этой задачи выглядит стройно и логично, то какая же это «сугубо императивная задача», тем более для лиспа?

нет задач императивных и функциональных;

есть задачи, на которых функциональный подход может привести к профиту в виде

* простоты модификации кода (да-да, я про те самые «а давайте при решении этой задачи решим ещё стопицот других задач, о которых нас не просили!» — щас нас не просили, но вот скоро попросят в рамках поддержки/развития проекта)

* скорости выполнения — функциональщину потенциально можно более широко оптимизировать, чем императивщину

ну так ничего такого здесь нет

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

> однозначно положительно качество

выдавать свои вкусы за вкусы большинства — однозначно отрицательное качество.

> неосиляторство чего?

синтаксиса же. 26 символов показались сложными, переделывать взялся, позор…

> щас нас не просили, но вот скоро попросят в рамках поддержки/развития проекта

однострочник на перле в 26 символов — проект? о_О при изменении условий его проще переписать с нуля. или ты с этого однострочника фреймворк для решения всего класса подобных задач замутить решил? не забуть на CPAN выложить потом ;)

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

>> щас нас не просили, но вот скоро попросят в рамках поддержки/развития проекта

однострочник на перле в 26 символов — проект?

К.О. спешит сообщить, что он не проект, а часть проекта, и как часть проекта тоже может быть подвержен модификации

при изменении условий его проще переписать с нуля.

так я и поверил

если изменение условий состоит например в том, что символ комментария заменили на ";", то каждый вменяемый программист его будет переписывать, а не писать заново ( за исключением, возможно, тех случаев, когда обнаружит в нем костыльную идиому $че_то += (условие) && $значение )

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

> К.О. спешит сообщить, что он не проект, а часть проекта, и как часть проекта тоже может быть подвержен модификации

ты фальшивый к.о.; настоящий к.о. сообщает, что это пример решения задачи, опубликованный на ЛОРе, а не проект или часть проекта. у тебя слишком разыгралось воображение, иди поспи.

> так я и поверил

ну это твои личные половые проблемы.

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

Да, в рабочем коде я обычно разбиваю на строки.

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

Чё, писькомерки уже начались, да? В _твоём_ питоне тоже мало красивого. Плюс, кое-где некошерно. Хотя, лениво, да.

sum(int(line.split(' ', 3)[2]) for line in open('wh-dimv.txt') if len(line) > 2 and not line.startswith('#'))

len(line) > 2 это чтобы ни пустые строки, ни платформозависимые '\r\n', '\r', '\n' не учитывались. Грязновато, но на скорую руку сойдет. Как более понятный вариант

sum(int(line.split(' ', 3)[2]) for line in open('wh-dimv.txt').read().splitlines() if not line.startswith('#'))

line.split(' ', 3) - нефиг дробить зазря строку до победного конца (её), вытянули нужные данные и остановились. Вот это лениво и функционально по-настоящему ;-).

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

>> явный цикл вместо применения +редукции

Это признак быдлокода.

это зависит от языка — некоторые вообще не приспособлены для использования +редукции

ну допустим даже и с виду приспособлены;

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

в цикле это делается в полпинка: c[i]>0 ? (pos+=c[i]) : (neg+=c[i]), хотя лучше было бы ( c[i]>0 ? pos : neg )+=c[i];

и как это будет выглядеть на функциональном языке? будет ли гарантия, что c[] будет пройдено ровно 1 раз (проход 2 раза это уменьшение быстродействия при больших c[], не помещающихся в кэш)

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

и как это будет выглядеть на функциональном языке?

Как-то так:

f1 = foldl' (\(p, n) e -> if e > 0 then (p + e, n) else (p, n + e)) (0, 0)
-- ^ Т.е. начинаем с (0, 0) и сворачиваем (fold) эту лямбду на списке.
--     e -- очередной элемент списка,
--     p -- аккумулятор для положительных значений,
--     n -- для отрицательных.

или (эдакий псевдо-питон) так:

-- * Опять, начиная с `(0, 0)'
f2 list = roll (0, 0) list $
  -- * Для каждого элемента `e' списка `list' с аккумуляторами `(p, n)'
  \e (p, n) ->
    -- * обновляем аккумуляторы
    if e > 0
    then (p + e, n)
    else (p, n + e)

-- Отличие от foldl - порядок аргументов у f, и сама эта f последним элементом
-- (можно делать roll ... $ {newline/layout} \ ... -> ...).
roll :: r -> [t] -> (t -> r -> r) -> r
roll i xs f = foldl' (flip f) i xs

будет ли гарантия, что c[] будет пройдено ровно 1 раз

Если использовать непосредственно fold*, то и так будет один проход по умолчанию. А если делать в два прохода на вид:

f3 = map2 sum . partition (> 0)
-- ^ map2 :: (Functor2 f) => (a -> b) -> f a a -> f b b

то есть соответсвующие оптимизации (deforestation, stream (ex. warm) fusion, list fusion — из той же серии что super-compilation и partial evaluation). Например, в Data.Vector так сделано — http://haskell.org/haskellwiki/Numeric_Haskell:_A_Vector_Tutorial#A_note_on_fusion

quasimoto ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.