Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Адресная книга [Python etc]

87
07 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
С целью агитации к созданию новых разделов предлагают проводить всякие турниры по решению задачек на экзотических языках. Я же решил для начала имитировать простые студенческие вопросы, но с требованием решения на упомянутых языках.

Итак, задачка. Создать программу "Адресная книга", которая будет запоминать и выводить адреса и телефоны. Информация об объекте будет представлена в виде: ник, email, телефон. В программе должен быть поиск по нику, адресу, телефону, и по части ника, телефона и адреса. В последнем случае, должен быть выведен список из найденных абонентов.

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

Задачу решать на Python или других языках, о которых говорится тут. Сам попытаюсь решать на Python.

Условия для определения качества работы: краткость, работоспособность. Ну и читаемость. Кто создаст более краткий работающий код - тот и крут :)
29K
09 сентября 2009 года
Ander Skirnir
109 / / 08.06.2009
Надеюсь, Вы не будете против что я сделал её на Лиспе, хотя его и нет по вышеприведённой ссылке; - всё-таки Лисп - первый язык как ФП, так и ДП и тп :)

Код:
;; обобщенная ф-ция чтения-записи в файл
(defun file-io-op (filename mode &optional data)
    (with-open-file
        (stream filename :direction mode :if-exists :supersede)
            (if (eq mode :output)
                (mapc #'(lambda (dp) (print dp stream)) data)
                (loop when (read stream nil) collect it
                    else do (loop-finish)))))

;; ф-ция загрузки книги из файла
(defun book-load (filename)
    (file-io-op filename :input))

;; ф-ция сохранения книги в файл
(defun book-save (book filename)
    (file-io-op filename :output book))

;; обобщенная ф-ция поиска данных
(defun book-find (subseq storage &key storage-type)
    (if (eq storage-type :file)
        (setf storage (file-io-op storage :input)))
    (mapcan
        #'(lambda (sl)
            (if (search (coerce subseq 'list)
                    (apply 'append
                        (mapcar #'(lambda (el) (coerce el 'list)) sl)))
                (list sl) nil))
        storage))


Конечно, очень даже не коротко, но это стартовый вариант, немного намеренно прямолинейный - с целью пошагового морфизма в кое-что более интересное. Зато работает - и не только для ника, адреса, телефона, но для любых данных в любом количестве.

[COLOR="SeaGreen"]Использование:[/COLOR]

[COLOR="RoyalBlue"]- входной файл состоит из записей вида ("ник" "мыло" "телефон" ...), например:[/COLOR]
[COLOR="Teal"]("Ander Skirnir" "some-email@mail.ru" "991-19-09")
("Evil Invader" "abigor@hellfire.pwnz" "1010011010")
[/COLOR]

[COLOR="RoyalBlue"]- загрузка данных осуществляется функцией book-load:[/COLOR]
[COLOR="Teal"](defvar *adress-book* (book-load "input"))[/COLOR] [COLOR="Gray"];; вычитывает данные из файла input и записывает их в переменную *adress-book*[/COLOR]

[COLOR="RoyalBlue"]- запись данных в файл реализуется функцией book-save:[/COLOR]
[COLOR="Teal"](book-save *adress-book* "input")[/COLOR] [COLOR="Gray"];; записывает данные из переменной *adress-book* в файл input[/COLOR]

[COLOR="RoyalBlue"]- за поиск отвечает функция book-find, ищущая данные содержащие поданую на вход строчную последовательность:[/COLOR]
[COLOR="Teal"](book-find "rnir" *adress-book*)[/COLOR] [COLOR="Gray"]>> (("Ander Skirnir" "some-email@mail.ru" "991-19-09"))[/COLOR]
[COLOR="Teal"](book-find "@" *adress-book*)[/COLOR] [COLOR="Gray"]>> (("Ander Skirnir" "some-email@mail.ru" "991-19-09") ("Evil Invader" "abigor@hellfire.pwnz" "1010011010"))[/COLOR]
[COLOR="RoyalBlue"], также ей на вход можно кинуть не переменную, а имя файла, но тогда нужно указать дополнительный параметр: [/COLOR][COLOR="Teal"](book-find "@" "input" :storage-type :file)[/COLOR]


[COLOR="RoyalBlue"]- ну и да - добавление данных осуществляется стандартными лисповскими функциями:[/COLOR]
[COLOR="Teal"](push '("Yoda" "h@x.glctx" "Lli98-22131" "^.!.^") *adress-book*)[/COLOR][COLOR="Gray"] ;; и другими, например nconc и тп...[/COLOR]
2
09 сентября 2009 года
squirL
5.6K / / 13.08.2003
а я бы взял LDAP каталог...
87
09 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ander Skirnir
Надеюсь, Вы не будете против что я сделал её на Лиспе



Конечно, не против.

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

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

В общем, я и сам немного застрял из-за того, что условия сформулированы слишком расплывчато. Постараюсь внести уточнения.

361
09 сентября 2009 года
Odissey_
661 / / 19.09.2006
Два момента по программе на Лиспе

Цитата:
Зато работает - и не только для ника, адреса, телефона, но для любых данных в любом количестве.


То что программа ищет по списку не разделяя на типы полей, это не плюс - это не верно. Понимаю что так в принципе сделать проще, но все же думаю стоит усложнить решения и придерживаться условий задачи -

Цитата:
В программе должен быть поиск по нику, адресу, телефону, и по части ника, телефона и адреса.



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

И второй момент.
Наверника ты знаком с книгой Practical Common Lisp, а именно глава 3. Практикум: Простая база данных (). Вообщем, как бы ты придумал и , возможно, реализовал интерфейс для доступа к адресной книге - просмотр, поиск и удаление, учитывая что пользователю совсем не обязательно знать Лисп что бы использовать твою программу.

87
09 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
Предлагаю следующие уточнения:

Пользователь взаимодействует с программой при помощи определенного языка, который состоит из примерно следующего набора команд: help, find, edit, push, pop, quit.

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

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

Функции undo и redo не требуются на данном этапе. Хотя и не запрещается их реализовывать.

Описание функций:

help – выводит (основной) список команд. Если используется с параметром-командой, то выводится описание команды. Например

help find

выводит справку по команде find
Команду help реализовывать не обязательно – это факультативная фича.

find – имеет 2 параметра: поле, в котором надо искать и строку для поиска. Поле указывается с помощью слов nick, phone, email.

Примеры:

find nick andrew
find email www
find phone 3245533

При использовании этой команды программа должна выводить абонента, если требуемое поле полностью совпало, либо список из не более 10 абонентов, требуемое поле которых начинается с этой строки. Именно начинается – не надо усложнять пока. После можно какие-нибудь дополнительные правила ввести для поиска.

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

push, pop – работают подобно edit. Используются для внесения или удаления абонента. Возможно объединение команд edit и push.

Надеюсь, не очень сложное задание.
Корректировки приветствуются.

Добавлено позже. Как справедливо мне заметили, это я писал для себя. Команды и логика работы может быть другая. Я только пример привел. Главное, чтобы пользователь не обязан был знать язык программирования, на котором написана программа и чтобы поиск был конкретным: по никам, по телефонам, по почте.
361
09 сентября 2009 года
Odissey_
661 / / 19.09.2006
Почему вдруг поиск по нескольким полям у тебя редуцировался к поиску только одному? Не вижу причины. Сложность задачи это не увеличивает.

Команда edit которую ты предлагаешь без параметров для набора данных из 3-х полей в данной задаче не имеет смысла и зачем вводиться не ясно.

Почему упрощен поиск до совпадения с началом? Функцию поиска подстроки реализовать не сложно, да и тем более она почти везде есть.

Поэтому. Предлагаю.
Оставьте возможность придумать свою систему команд решающему. Это часть задачи, и причем одна из самых интересных.
Должен быть поиск (причем по нескольким полям), удаление (как 1 записи так и нескольких удовлетворяющих условию), добавление.
87
09 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: Odissey_
Почему вдруг поиск по нескольким полям у тебя редуцировался к поиску только одному? Не вижу причины. Сложность задачи это не увеличивает.


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

Цитата: Odissey_
Команда edit которую ты предлагаешь без параметров для набора данных из 3-х полей в данной задаче не имеет смысла и зачем вводиться не ясно.


Возможно.

Цитата: Odissey_
Почему упрощен поиск до совпадения с началом? Функцию поиска подстроки реализовать не сложно, да и тем более она почти везде есть.


Опять таки здесь я продумывал смысл для пользователя. Например, если в телефоне я набираю "к", то мне выдаются абоненты начинающиеся на букву "к", но не первые попавшиеся, в которых эта буква есть. Кто хочет может добавить хитрый синтаксис со звездочками и знаками вопроса, с регулярными выражениями и т.д.

Цитата: Odissey_
Поэтому. Предлагаю.
Оставьте возможность придумать свою систему команд решающему. Это часть задачи, и причем одна из самых интересных.
Должен быть поиск (причем по нескольким полям), удаление (как 1 записи так и нескольких удовлетворяющих условию), добавление.


я только за. Я привел конкретный пример, чтобы было ясно о чем речь, как я вижу. Чтобы было о чем спорить :)

87
15 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
Как всегда, внезапно у меня появилась куча неотложных дел, потому с адресной книгой я застрял. Пока сделал эскиз-основу. Пока получилось не очень лаконично. Главная задумка, которая у меня была - сделать так, чтобы из консольной версии большую часть можно было перенести в версию с GUI. Не знаю, получилось или нет - буду проверять, создавая гуишную версию параллельно.

Код:
#!/usr/bin/env python
#-*-coding:cp1251-*-
import csv
import os

class View:
    def OutAddress(self, nick, fields):
        print nick, fields
   
    def OutNames(self, names):
        for name in names: print name
   
    def OutMessage(self, mess):
        print mess
           
    def GetAddress(self):
        phone = raw_input("Input a Phone: ")
        email = raw_input("Input an Email: ")
        return [phone, email]
   
    def GetCommand(self):
        return raw_input("Input command: ")
       
class Contr:
   
    def __init__(self, view, book):
        self.book = book
        self.view = view
        self.commands = {"quit": self.OnQuit, "q": self.OnQuit,
            "names": self.OnNames, "find": self.OnFind, "f": self.OnFind,
            "add": self.OnAdd, "del": self.OnDelete, "edit": self.OnEdit,
            "help": self.OnHelp}
           
    def Analysis(self, command):
        if len(command):
            parts = command.split(" ")
            command = parts[0].strip()
            if self.commands.has_key(command):
                return self.commands[command](parts[1:])          
            else:
                self.view.OutMessage("Use help for get info about commands")
   
    def OnHelp(self, parts):
        """ Help for help """
        if len(parts):
            if self.commands.has_key(parts[0]):
                self.view.OutMessage(self.commands[parts[0]].__doc__)
            else:
                self.view.OutMessage("Not found: " + parts[0])
        else: self.view.OutNames(sorted(self.commands.keys()))
   
    def OnFind(self, parts):
        """ Help for find """
        if not len(parts): return
   
        if self.book.has_key(parts[0]):
            self.view.OutAddress(parts[0], self.book[parts[0]])
        else:
            nicks = self.book.keys()
            notfound = True
            for s in nicks:
                name = str(s)
                if name.find(parts[0]) == 0:
                    self.view.OutAddress(name, self.book[name])
                    notfound = False
            if notfound: self.view.OutMessage(parts[0] + " not found")
       
    def OnAdd(self, parts):
        """ Help for add """
        nick = parts[0]
        self.book[nick] = self.view.GetAddress()
       
    def OnDelete(self, parts):
        """ Help for del """
        nick = parts[0]
        if self.book.has_key(nick):
            self.book.pop(nick)
            self.view.OutMessage(nick + " deleted")
        else: self.view.OutMessage("nick not found!")
       
    def OnEdit(self, parts):
        """ Help for edit """
        nick = parts[0]
        if self.book.has_key(nick):
            addr = self.view.GetAddress()        
            if(len(addr[0])):
                self.book[nick][0] = addr[0]
            if(len(addr[1])):
                self.book[nick][1] = addr[1]
        else: self.view.OutMessage("nick not found!")
       
    def OnNames(self, parts):
        """ Help for ... """
        self.view.OutNames(self.book.keys())
   
    def OnQuit(self, parts):
        """ Help for quit """
        return True
   
class Base:
    nick, phone, email = 0, 1, 2

    def __init__(self, fileName):
        self.baseName = fileName
        self.book = {}
       
    def Load(self):
        if os.path.exists(os.getcwd() + '\\' + self.baseName):
            csvReader = csv.reader(open( self.baseName, "r" ), delimiter = ';')        
            for row in csvReader:
                try: self.book[row[self.nick]] = [row[self.phone], row[self.email]]
                except:  pass        
   
    def Save(self):
        csvWriter = csv.writer(open(self.baseName, "w" ), delimiter = ';')
        for i in self.book:
            csvWriter.writerow( + self.book)
       
class Dispatcher:
   
    def __init__(self):
        self.view = View()
        self.base = Base("base.csv")
        self.contr = Contr(self.view, self.base.book)
       
    def Start(self):
        self.view.OutMessage("Hi!")
        self.base.Load()
   
    def Cycle(self):
        while True:
            if self.contr.Analysis(self.view.GetCommand()): break
        self.base.Save()
        self.view.OutMessage("Bye!")

if __name__ == "__main__":
    disp = Dispatcher()
   
    disp.Start()
    disp.Cycle()
87
16 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
Небольшой комментарий.

Хочу сравнить реализацию некоторых приемов с реализацией их в C++.

1 Прием. Использование словаря (map, dict) с функциями. Данный прием позволяет ассоциировать команды пользователя с определенными функциями. В C++ пришлось бы заполнять std::map указателями на функцию, используя хитрый синтаксис. При том, все функции должны были быть одного типа. Для большей гибкости (то есть, чтобы еще приблизиться к тому, что есть в Python) нужно было бы создать иерархию классов (функторов) и заполнять map указателями на объекты данных классов.

2 Прием. Использование помощи. Я его не полностью осуществил, только основу сделал. Здесь для каждой функции можно предусмотреть комментарий, который будет использоваться как помощь по этой функции. Удобство заключается в том, что строка помощи располагается в самой функции. То есть, во-первых, ее можно использовать как комментарий, во вторых, всегда видно, есть ли у функции данная строка. В C++ опять пришлось бы создавать хитрую иерархию классов.

3 Прием. Не все функции в словаре одинаковы: одна функция возвращает True, остальные ничего не возвращают (честнее говоря, они возвращают None). Этот признак я использовал для определения того, когда требуется выйти из цикла требования команд от пользователя. В общем, это не явное преимущество, а скорее фича. В C++ наверно нужно было бы определить какое-то поле в иерархии функторов. Или в каждой функции возвращать False.

Что мне недоставало в Python (возможно, из-за плохого ознакомления с ним):

1. Не знаю, как сделать переменную (ссылку) константной, чтобы нельзя было ее случайно переопределить.

2. Нету нормальных enum. Такая удобная и очевидная штука как enum вроде бы должна быть в нормальном языке, но ее нет, например, и в Java (там есть некий странный одноименный класс, близкий по смыслу к словарю в Python).

Я не нашел пока подходящей альтернативы, поэтому кое-где у меня остались не вычищенные магические цифры и дублирование кода. То есть, в TODO остается избавление от дублирования и магических цифр, а также усовершенствование поиска.
87
18 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
В общем, сделал "бета-версию" (правда, с пустым хелпом). Угробил почти весь ООП, заменив классы на подобия модулей.

Код:
#!/usr/bin/env python
import csv
import os

class Address: # module equivalent
    phone = 0
    email = 1
    fields = ("a Phone", "an Email")
    names = {"phone": phone, "email": email}

class View: # module equivalent
    def OutAddress(nick, fields):
        print nick, " => ", '; '.join([n + ": " + fields[Address.names[n]] for n in Address.names])
    OutAddress = staticmethod(OutAddress)
   
    def OutNames(names):
        for name in names: print name
    OutNames = staticmethod(OutNames)
   
    def OutMessage(mess):
        print mess
    OutMessage = staticmethod(OutMessage)
           
    def GetAddress():
        return [raw_input("Input " + p + ": ") for p in Address.fields]
    GetAddress = staticmethod(GetAddress)
   
    def GetCommand():
        return raw_input("Input command: ")
    GetCommand = staticmethod(GetCommand)

class Base:  # module equivalent

    baseName = "base.csv"
   
    def Load(book):
        nick = 0
        firstField = nick + 1
        lastField = firstField + len(Address.fields)
       
        if os.path.exists(os.getcwd() + '\\' + Base.baseName):
            csvReader = csv.reader(open( Base.baseName, "r" ), delimiter = ';')        
            for row in csvReader:
                try: book[row[nick]] = row[firstField : lastField]
                except:  pass        
    Load = staticmethod(Load)
   
    def Save(book):
        csvWriter = csv.writer(open(Base.baseName, "w" ), delimiter = ';')
        for i in book:
            csvWriter.writerow( + book)
    Save = staticmethod(Save)
       
class Contr:
   
    def __init__(self):
        self.book = {}
        Base.Load(self.book)
        self.commands = {"quit": self.OnQuit, "q": self.OnQuit,
            "names": self.OnNames, "find": self.OnFind, "f": self.OnFind,
            "add": self.OnAdd, "del": self.OnDelete, "edit": self.OnEdit,
            "help": self.OnHelp}
           
    def Analysis(self, command):
        if len(command):
            parts = command.split(" ")
            command = parts[0].strip()
            if self.commands.has_key(command):
                return self.commands[command](parts[1:])          
            else:
                View.OutMessage("Use help for get info about commands")
   
    def OnHelp(self, parts):
        """ Help for help """
        if len(parts):            
            if self.commands.has_key(parts[0]):
                View.OutMessage(self.commands[parts[0]].__doc__)
            else:
                View.OutMessage("Not found: " + parts[0])
        else: View.OutNames(sorted(self.commands.keys()))
   
    def OnFind(self, parts):
        """ Help for find """
        if not len(parts): return
   
        def FindNick(nick):
            if self.book.has_key(nick):
                View.OutAddress(nick, self.book[nick])
            else:
                nicks = self.book.keys()
                notfound = True
                for s in nicks:
                    name = str(s)
                    if name.find(nick) == 0:
                        View.OutAddress(name, self.book[name])
                        notfound = False
                if notfound: View.OutMessage(nick + " not found")
               
        def FindField(field, numb):
            notfound = True
            for addr in self.book:
                fieldStr = str(self.book[addr][numb])
                if fieldStr.find(field) == 0:
                    View.OutAddress(addr, self.book[addr])
                    notfound = False
            if notfound: View.OutMessage(field + " not found")
       
        if len(parts) == 1: FindNick(nick = parts[0])
        elif len(parts) == 2:
            name = parts[0]
            if Address.names.has_key(name):  FindField(parts[1], Address.names[name])
            else: View.OutMessage("You have used invalid key: ", name)
       
    def OnAdd(self, parts):
        """ Help for add """
        nick = parts[0]
        self.book[nick] = View.GetAddress()
        Base.Save(self.book)
       
    def OnDelete(self, parts):
        """ Help for del """
        nick = parts[0]
        if self.book.has_key(nick):
            self.book.pop(nick)
            Base.Save(self.book)            
            View.OutMessage(nick + " deleted")
        else: View.OutMessage("nick not found!")
       
    def OnEdit(self, parts):
        """ Help for edit """
        nick = parts[0]
        if self.book.has_key(nick):
            addr = View.GetAddress()        
            for i in xrange(len(addr)):
                if(len(addr)): self.book[nick] = addr
            Base.Save(self.book)            
        else: View.OutMessage("nick not found!")
       
    def OnNames(self, parts):
        """ Help for ... """
        View.OutNames(self.book.keys())
   
    def OnQuit(self, parts):
        """ Help for quit """
        return True
   
def Cycle():
    View.OutMessage("Hi!")
    contr = Contr()
    while True:
        if contr.Analysis(View.GetCommand()): break
    View.OutMessage("Bye!")

if __name__ == "__main__":
    Cycle()

Ну что же, на курсовую третьекурсника тянет :)
29K
18 сентября 2009 года
Ander Skirnir
109 / / 08.06.2009
Вот выкладываю свой вопиющий ужас (:

Код:
(defpackage :address-book
  (:use :common-lisp :iterate))

(in-package :address-book)

;; обобщенная ф-ция чтения-записи в файл
(defun file-io-op (filename mode &optional data)
  (with-open-file
    (stream filename :direction mode :if-exists :supersede)
    (if (eq mode :output)
      (print data stream)
      (read stream nil))))

;; загрузка базы из файла
(defvar *address-book* (file-io-op "input" :input))
(defvar *basis* (cadr (assoc 'basis *address-book*)))

;; функция помощи нуждающимся
(defun userfunc-help (&optional command)
  (format t
    (or (case command
          ((help) ">> prints command's description~%>> e.g: help find~%~%")
          ((find) ">> find records by fields and its values~%~%")
          ((nil) ">> command list:~%>- help~%>- find~%~%>? for extra info about command~%>? type help %cmdname%, for e.g:~%>? help find~%~%"))
        ">> invalid argument!~%~%")))

;; макрос высшего порядка
(defmacro adbook-high-order (names conditions code)
  (let ((target-list (cadr (assoc 'data *address-book*))))
    `(iter (for lst* :on ',target-list)
        ,@(mapcar #'(lambda (name)
                     `(for ,name := (nth ,(position name *basis*) (car lst*))))
                  (eval names))
        (if ,(eval conditions) ,(eval code)))))

(defun userfunc-dump ()
  (print *address-book*))

;; поиск
(defun userfunc-find (&rest request)
  (let* ((lst (divide-by-tokens '(where) (if (equal (car request) 'record) (append *basis* (cdr request)) request)))
         (names (delete 'and (car lst)))
         (conditions (reduce 'infix-prefix `((and or) (equal ~) ,(nsubst 'equal '= (cadr lst))) :from-end t))
         (code `(collect (list ,@names)))
         (result (eval `(adbook-high-order ',names ',conditions ',code))))
    (iter (for r :in result)
          (iter (for i :in names)
                (for c :in r)
                (format t ">> ~a: ~a~%" i c))
          (finally (format t "~%")))))

;; добавление
(defun userfunc-add-record (&rest request)
  (let* ((lst (reduce 'delete `(and with = ,request) :from-end t))
        (result (iter (for field :in *basis*)
                      (collect (car (iter (for value :on lst :by 'cddr)
                                          (if (equal field (car value))
                                              (collect (cadr value)))))))))
    (if (notevery #'null result) (push result (cadadr *address-book*)))))

;; удаление
(defun userfunc-delete-record (&rest request)
  (let* ((lst (delete 'with request))
         (names (intersection lst *basis*))
         (conditions (reduce 'infix-prefix `((and or) (equal ~) ,(nsubst 'equal '= lst)) :from-end t))
         (code '(progn (format t ">> deleting record ~s~%" (car lst*)) (rplaca lst* (cadr lst*)) (rplacd lst* (cddr lst*)))))
    (eval `(adbook-high-order ',names ',conditions ',code))))

;; функция сопоставления со строчным образцом
(defun ~ (var-a var-b)
  (if (not (stringp var-b))
      (setf var-b (format nil "~a" var-b)))
  (if (equal (elt var-b (1- (length var-b))) #\*)
      (if (= (or (search (string-right-trim "*" var-b) var-a) -1) 0) t nil)
      (if (equal (elt var-b 0) #\*)
          (if (> (or (search (string-left-trim "*" var-b) var-a) -1) 0) t nil)
          (if (search var-b var-a) t nil))))

;; ф-ция перевода инфиксной записи в лисповаримую префиксную скобочную
(defun infix-prefix (operator-list target-list)
  (let* ((lst* (copy-list target-list))
         (result
           (iter (for i := lst*) (until (null lst*))
                  (if (member (cadr i) operator-list)
                     (progn (collect (cons (cadr i)
                                             (mapcar #'(lambda (k)
                                                        (if (listp k) (infix-prefix operator-list k) k))
                                                      (list (car i) (caddr i)))))
                             (setf lst* (cdddr lst*)))
                     (progn (collect (car i)) (setf lst* (cdr lst*)))))))
    (if (listp (car result)) (car result) result)))

;; ф-ция разбиения списка на подсписки токенами
(defun divide-by-tokens (token-list target-list)
  (iter (for i :in target-list)
        (if (member i token-list)
            (progn (collect temp :into result)
                    (setf temp '()))
            (collect i :into temp))
        (finally (return (nconc result (list temp))))))

;; парсит строку в лисповскую форму,
;; голова цела, хвост - заквочен
(defun adbook-parse-form (text-form)
  (let ((form (read (make-string-input-stream text-form))))
    (let ((tild* (if (member '~ form) (nthcdr (1+ (position '~ form)) form))))
      (if tild* (rplaca tild* (format nil "~a" (car tild*)))))
    (cons (car form) (mapcar #'(lambda (s) `',s) (cdr form)))))

;; приветствие и приглашение
(format t "| Address Book Operating Terminal |~% ...(type help to get cmdlist)... ~%~%address-book > ")

;; mainloop
(iter (for query := (string-right-trim '(#\Return) (read-line)))
      (until (string= "exit" query))
       (let ((form (adbook-parse-form (concatenate 'string "(userfunc-" query ")"))))
         (if (fboundp (car form))
            (eval form)
             (format t ">> invalid command!~%~%")))
      (princ "address-book > ")
      (force-output t))


Допустим имеются записи:
nick/email/phone :
("Ander Skirnir" "some-email@mail.ru" 991-19-09)
("Evil Invader" "abigor@hellfire.pwnz" 1010011010)

Комманды поддерживаются такого рода:
[COLOR="SeaGreen"]find nick where phone = 991-19-09[/COLOR]
>> Nick: "Ander Skirnir"

[COLOR="SeaGreen"]find nick and email where (nick ~ "der" and email ~ "@") or 1 = 1[/COLOR]
>> Nick: "Ander Skirnir"
>> Email: "some-email@mail.ru"
>> Nick: "Evil Invader"
>> Email: "abigor@hellfire.pwnz"

[COLOR="SeaGreen"]find record where nick ~ "A*"[/COLOR]
>> Nick: "Ander Skirnir"
>> Email: "some-email@mail.ru"
>> Phone: 991-19-09

ну и
[COLOR="SeaGreen"]add-record with nick = ...[/COLOR]
[COLOR="SeaGreen"]delete-record with phone ~ ...[/COLOR] и тп

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

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

В следующем варианте я попробую хоть немного приблизиться к lisp-way и написать dsl, решающую целый класс задач типа "адресная книга" - а это и есть бд.
9
21 сентября 2009 года
Lerkin
3.0K / / 25.03.2003
Маленькое рацпредложение в целях, так сказать, увеличения интереса анонимусов к экзотическим и малоизвестным языкам. Думаю, что следовало бы, исключительно для примера, реализовать данную программку на парочке-троечке общеизвестных языков, ибо все познаётся в сравнении. ;)

P.S. Как только личная запарка кончится, кину вариант на Erlang'е. Думаю, смогу через пару дней.
87
21 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: Lerkin
Думаю, что следовало бы, исключительно для примера, реализовать данную программку на парочке-троечке общеизвестных языков, ибо все познаётся в сравнении.



Так у меня практически на C++ и написано. На "правильном" Питоне кодить еще не научился... А так переводится один в один в C++. Может и переведу как-нибудь. Но я думал, что вполне хватит сравнительного комментария (с C++), который я уже сделал.

Кстати, я сделал ООП-шную ошибку в своем последнем варианте. Все-таки надо было пользоваться объектом класса View - для перевода в GUI-шный вариант пригодилось бы.

Посмотрел на вариант от Ander Skirnir и понял, что теме надо сменить название на "Вивилонская башня" ))

5
22 сентября 2009 года
hardcase
4.5K / / 09.08.2005
Адресная книга на Nemerle:

Код:
using System;
using System.Collections.Generic;
using System.Console; // можно раскрывать статические члены класса :)
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Serialization;

using Nemerle;
using Nemerle.Assertions;
using Nemerle.Utility;

[Record] // макрос создаст конструктор, инициализирующий поля
[XmlType("Address")]
[Serializable]
public sealed class AddressEntry {

    public this() {  // требуется для работы сериализатора
    }
   
    [Accessor(flags = WantSetter)] // создает свойство с геттером и сэтером
    private mutable nick : string = string.Empty;
   
    [Accessor(flags = WantSetter)]
    private mutable email : string = string.Empty;
   
    [Accessor(flags = WantSetter)]
    private mutable phone : string = string.Empty;
   
    public override ToString() : string {
        $"$nick, $email, $phone"
    }
}

[XmlRoot] // корневой элемент XML файла
[XmlType("AddressBook")]
[Serializable]
public sealed class AddressEntryCollection : List[AddressEntry] {
}

// аналог перечисления enum
public enum SearchCriteria {
    | Nick
    | Email
    | Phone
}

public sealed class AddressBook : IDisposable {

    private static serializer : XmlSerializer = XmlSerializer(typeof(AddressEntryCollection));

    public this(file_name : string = "AddressBook.xml")  // можно использовать значения параметров по-умолчанию
        requires !string.IsNullOrEmpty(file_name)      // проверка параметра на корректность
            otherwise throw ArgumentException("Parameter 'file_name' must have non-emtpy value.")
    {
        this.file_name = file_name;
        this.data = if(File.Exists(file_name)) {
                using(stream = FileStream(file_name, FileMode.Open, FileAccess.Read))
                    serializer.Deserialize(stream) :> AddressEntryCollection
            } else
                AddressEntryCollection()
    }
   
    [Accessor]
    private file_name : string;
   
    private data : AddressEntryCollection;
   
    public Find(str : string, criteria : SearchCriteria) : list[AddressEntry] {
        def condition = match(criteria) {
            | Nick => fun(rec) { rec.Nick.IndexOf(str) >= 0 }
            | Email => fun(rec) { rec.Email.IndexOf(str) >= 0 }
            | Phone => fun(rec) { rec.Phone.IndexOf(str) >= 0 }
        }
       
        def searcher(seq) {
            if (seq.MoveNext())
                if (condition(seq.Current))
                    seq.Current :: searcher(seq)
                else searcher(seq)
            else []
        }
       
        using(seq = data.GetEnumerator()) {
            searcher(seq)
        }
    }
   
    public Add(rec : AddressEntry) : void {
        unless(data.Contains(rec))
            data.Add(rec)
    }
   
    public Remove(rec : AddressEntry) : void {
        _ = data.Remove(rec)
    }
   
    protected override Finalize() : void { // "деструктор" в терминах C#
        Dispose(false);
    }
   
    private Dispose(disposing : bool) : void {
        ignore(disposing); // чтобы компилятор не ругался на неиспользуемый параметр
       
        using(stream = FileStream(file_name, FileMode.Create, FileAccess.Write))
            serializer.Serialize(stream, data)
    }
   
    private mutable disposed : bool = false;
   
    public Dispose() : void {
        unless(disposed) {
            Dispose(true);
            GC.SuppressFinalize(this);  // отключаем "деструктор"
            disposed = true;
        }
    }
}

module Program {

    Main() : void {
        WriteLine("Welcome to address book keeper!");
       
        using(ab = AddressBook()) {
            WriteLine("Address book now loaded.");    
           
            mutable quit = false;
            while(!quit) {
                def input = ReadLine();
                when(input != string.Empty)
                    match(ParseInput(input)) {
                        | "q" :: [] | "quit" :: [] | "exit" :: [] => quit = true;
                        | "f" :: tail | "find" :: tail => DoFind(ab,  tail);
                        | "a" :: tail | "add" :: tail => DoAdd(ab, tail);
                        | "e" :: tail | "edit" :: tail => DoEdit(ab, tail);
                        | "d" :: tail | "delete" :: tail => DoDelete(ab, tail);
                        | _ => WriteLine("Input not recognized :(");
                    }
            }
           
            WriteLine("Saving changes...");
        }
       
        WriteLine("Bye!");
    }
   
    private pattern : Regex = Regex(<#(\".*?\")|(=)|((\w|\.|@|_|-)+)#>, RegexOptions.Compiled);
   
    ParseInput(str : string) : list[string] {
        def list_builder(seq) {
            if(seq.MoveNext()) {
                def m = seq.Current :> Match;
                if(m.Success)
                    m.Value :: list_builder(seq)
                else list_builder(seq)
             } else []
        }
        list_builder(pattern.Matches(str).GetEnumerator())
    }
   
   
    DoFind(ab : AddressBook, input : list[string]) : void {
        def result = match(input) {
            | "n" :: nick :: []
            | "nick" :: nick :: [] =>
                ab.Find(nick, SearchCriteria.Nick)
            | "e" :: email :: []
            | "email" :: email :: [] =>
                ab.Find(email, SearchCriteria.Email)
            | "p" :: phone :: []    
            | "phone" :: phone :: [] => ab.Find(phone, SearchCriteria.Phone)
            | _ => WriteLine("Input for searching not recognized :(");
                   []
        }
        match(result) {
            | [] => WriteLine("No records found.");
            | _ =>    WriteLine($"$(result.Length) record(s) found:");
                result.Iter(WriteLine);
        }
    }
   
    SetEntryField(entry : AddressEntry, field : string, value : string) : bool {
        match(field) {
            | "n" | "nick" =>  entry.Nick = value; true
            | "e" | "mail" | "email" => entry.Email = value; true
            | "p" | "phone" | "mobile" => entry.Phone = value; true
            | _ => false
        }
    }
   
    ParseEntryFields(entry : AddressEntry, input : list[string]) : bool {
        match(input) {  
            | [] => true
            | field :: "=" :: value :: tail
            | field :: value :: tail =>
                ParseEntryFields(entry, tail) && SetEntryField(entry, field, value)
            | _ => false
        }
    }
   
    DoAdd(ab : AddressBook, input : list[string]) : void {
        def entry = AddressEntry();
       
        if(ParseEntryFields(entry, input)) {
            ab.Add(entry);
            WriteLine($"New record added: $entry");
        } else
            WriteLine("Invalid input :(");
    }
   
    DoEdit(ab : AddressBook, input : list[string]) : void {
        match(input) {
            | nick :: tail =>
                match(ab.Find(nick, SearchCriteria.Nick)) {
                    | entry :: _ =>
                        if(ParseEntryFields(entry, tail))
                            WriteLine($"Record changed to: $entry.")
                        else
                            WriteLine($"Some input not recognized, new record: $entry.")
                    | _ => WriteLine($"No records for '$nick' found.");
                }
            | _ => WriteLine("Invalid input :(");
        }
    }
   
    DoDelete(ab : AddressBook, input : list[string]) : void {
        match(input) {
            | nick :: [] =>
                match(ab.Find(nick, SearchCriteria.Nick)) {
                    | entry :: _ => {
                            WriteLine($"Deleting first found entry $entry");
                            ab.Remove(entry);
                        }                        
                    | _ => WriteLine($"No records for '$nick' found.");
                }
            | _ => WriteLine("Invalid input :(");
        }
    }
   

}
Синтаксис работы с программой в принципе можно понять из match-конструкций (эквивалентны ML-подобным языкам).


Добавление:
a n=hardcase e=hc@net.du p=9031112234
или вот так:
add nick hardcase email [EMAIL="hc@net.du"]hc@net.du[/EMAIL] phone 9031112234
или даже вот так:
add nick=hardcase email=hc@net.du phone=9031112234

Поиск:
find nick ha

Редактирование:
edit ha n=hardcaseminator e=hc@bla.bla.net
(изменит первую попавшуюся)

Удалить тоже можно
delete ha
(удалит первую запись)
87
22 сентября 2009 года
Kogrom
2.7K / / 02.02.2008
Цитата: Ander Skirnir

Допустим имеются записи:
nick/email/phone :
("Ander Skirnir" "some-email@mail.ru" 991-19-09)
("Evil Invader" "abigor@hellfire.pwnz" 1010011010)

Комманды поддерживаются такого рода:
[COLOR="SeaGreen"]find nick where phone = 991-19-09[/COLOR]
>> Nick: "Ander Skirnir"



я Lisp не знаю - пытаюсь интуитивно понять. Вижу, что в коде нигде нет слов типа phone, email и т.п. Возможно, они хранятся в файле базы или в каком-то дополнительном файле настроек.

Если в файле базы, то программа не будет адекватно работать без такого файла, что предусмотрено в моей и Хардкейсовской версиях. С другой стороны, так добавляется некая гибкость для пользователя.

29K
22 сентября 2009 года
Ander Skirnir
109 / / 08.06.2009
Да, забыл упомянуть. У меня хранилище данных описывается базисом и, собственно, данными. И всё это на списках ):

 
Код:
((basis (nick email phone))
(data
  (("Ander Skirnir" "some-email@mail.ru" 991-19-09)
  ("Evil Invader" "abigor@hellfire.pwnz" 1010011010))))


Т.е. в первом подсписке хранится базис, из которого находится координата нужной компоненты по её имени при работе с записями со второго подсписка.
5
22 сентября 2009 года
hardcase
4.5K / / 09.08.2005
Цитата: Ander Skirnir
Да, забыл упомянуть. У меня хранилище данных описывается базисом и, собственно, данными.


Именно это мы с Когромом и предположили на #codenet. :)

9
22 сентября 2009 года
Lerkin
3.0K / / 25.03.2003
Цитата: Ander Skirnir
Да, забыл упомянуть. У меня хранилище данных описывается базисом и, собственно, данными. И всё это на списках ):


Ну так. Нормальный lisp-way. На Эрланге будет похоже.

P.S. Передумал выкладывать простой вариант. mnesia прикручу ;)

12K
30 сентября 2009 года
Ghox
297 / / 26.07.2009
Итак, созданы новые разделы:
Python / Ruby / Groovy и пр.
Haskell / Erlang / OCaml и т.д
Может быть, стоит перенести эту тему в какой-нибудь из них, а то потеряется со временем в "Студентах"...
Правда, непонятно куда переносить - здесь есть решения, как я понял, на языках из обоих разделов...
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог