Список постов в категории "Программирование"

Тестовые задания для программиста

Так случилось, что в марте 2011 года я потерял работу в связи с закрытием фирмы. Взгрустнулось. Недолго думая, накатал резюме и выложил его на разные сайты: hh.ru, habrahabr.ru, job.ru и dtf.ru. В течение месяца ответило довольно много работодателей. Некоторые высылали тестовые задания для проверки. Делал я их бесплатно, в свое личное время, так что имею полное право выложить решение.

Итак, погнали: решение тестового задания от фирмы «Saber Interactive».

Задача

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

Решение

#include <stdio.h>
#include <limits.h>

void BinaryPrint( long n )
{
    // Частный случай - n равно нулю
    if (n == 0)
    {
        printf("0\n");
        return;
    }

    unsigned long mask = 1 << (sizeof(n) * 8 - 1);

    // Пропускаем бесполезные нули слева
    int skipZero = 1;

    while (mask)
    {
        // Определим текущий бит
        unsigned char c = (n & mask) ? 1 : 0;

        // Отключим пропуск нулей в дальнейшем
        if (skipZero && c)
            skipZero = 0;

        if (!skipZero)
            printf("%d", c);

        // Сдвинем маску на один бит вправо
        mask >>= 1;
    }
    printf("\n");
}

int main()
{
    BinaryPrint(0);
    BinaryPrint(1);
    BinaryPrint(2);
    BinaryPrint(-2);
    BinaryPrint(-1);
    BinaryPrint(LONG_MAX);
    BinaryPrint(LONG_MIN);
    return 0;
}

Задача

Напишите функцию, удаляющую последовательно дублирующиеся символы в строке: («AAA BBB ААА» -> «A B А»)
void RemoveDups (char *pStr);

Решение

#include <stdio.h>

void RemoveDups( char * pStr )
{
    const char * src = pStr;
    char * dst = pStr;

    // Пока не дойдем до конца строки
    while (*src)
    {
        // Записываем в pStr текущий символ, если указатели разные
        if (dst != src)
            *dst = *src;

        // Сдвигаем dst вправо
        *dst++;

        // Пока не конец строки и следующий символ такой же
        while ((*src + 1) && *(src + 1) == *src)
        {
            // Сдвигаем указатель src вправо
            *src++;
        }
        *src++;
    }
    // Запишем ноль в конец строки
    *dst = 0;
}

int main()
{
    char s[] = "AAA BBB AAA";
    RemoveDups(s);
    printf("%s\n", s);
    return 0;
}

Задача

Предположим, Вам предстоит написать простенькое программное обеспечение для учета книг в библиотеке. По некоторым, не зависящим от Вас причинам, нужно, чтобы программа была написана на С++ без использования каких-либо баз данных. Вы прикинули предварительный
список требований, которым должна удовлетворять ваша программа, и у Вас получилось:

Вам нужно хранить информацию обо всех книгах из библиотеки — название, автор, год, количество экземпляров
— Вам нужно хранить базовую информацию о читателях — ФИО, адрес, телефон
— Вам нужно уметь быстро находить читателя по ФИО или только по части ФИО
— Вам нужно хранить информацию о том, какие книги взяты какими читателями и на какой срок
— Вам нужно уметь быстро находить информацию о книгах по автору и/или названию, а также выдавать информацию о том, есть ли свободные экземпляры, и у каких читателей находятся выданные книги, и когда они будут возвращены

Разработайте структуры хранения данных, наилучшим образом подходящие для реализации этой функциональности. Постарайтесь учесть непредвиденные ситуации, которые могут возникнуть, а также организовать доступ к данным наиболее быстрым и эффективным образом. Представьте ответ в виде .h файла. Вы можете использовать классы С++, а также контейнеры STL или их аналоги.

Решение

Исходники программы на C/C++
#ifndef _LIBRARY_H_
#define _LIBRARY_H_

/*
 Каждой книге и каждому читателю при регистрации присваивается уникальный 
 числовой идентификатор. Доступ к книгам и читателям происходит только 
 с помощью идентификаторов.
 
 База книг/читателей преставляет собой деревья, где ключи - это идентификатор,
 а значения - книга/читатель. 
 
 Для каждого поля, по которым мы можем осуществить поиск, создается
 индексная таблица. По образу и подобию индексных таблиц в mysql.
 
 Класс Index представляет собой обертку над std::multimap. 
 std::multimap выбран потому, что значения ключей могут повторяться.
 один автор -> много книг, одно название -> много книг и т.д.
 
 Все методы поиска возвращают список идентификаторов, поскольку ключевые поля не уникальны.
*/

#include <string>
#include <vector>
#include <map>
#include <algorithm>
#include <assert.h>

// Уникальный идентификтор объекта
typedef unsigned long Ident;
// Список идентификаторов
typedef std::vector<Ident> IdentVector;

namespace detail
{
    // Шаблонный класс для удобства работы с multimap
    template <class T>
    class Index
    {
    public:
        void append( const T & value, Ident ident )
        {
            m_values.insert(std::make_pair(value, ident));
        }
        
        void remove( Ident ident )
        {
            for (Values::const_iterator i = m_values.begin(); i != m_values.end();)
            {
                if (i->second == ident)
                {
                    i = m_values.erase(i);
                }
                else
                {
                    ++i;
                }
            }
        }
        
        IdentVector get( const T & value ) const
        {
            IdentVector result;
            
            Values::const_iterator i = m_values.find(value);
            while (i != m_values.end() && i->first == value)
            {
                result.push_back(i->second);
                ++i;
            }
            
            return result;
        }
        
    private:
        typedef std::multimap<T, Ident> Values;
        Values m_values;
    };
    
} // namespace detail

// Описание книги
struct Book
{
    // Автор
    std::string author;
    // Название
    std::string title;
    // Год выпуска
    size_t year;
    // Количество экземпляров
    size_t count;
    
    Book( const std::string & author, const std::string & title, size_t year, size_t count )
        : author(author), title(title), year(year), count(count)
    {
    }        
};

// База данных книг
class BookDatabase
{
public:
    BookDatabase()
        : m_nextIdent(0)
    {
    }        
    
    virtual ~BookDatabase() {}

    // Добавление книги в библиотеку (возвращает идентификатор)
    Ident registerBook( const Book & book )
    {
        Ident ident = getNextIdent();
        m_books.insert(std::make_pair(ident, book));
        m_authors.append(book.author, ident);
        m_titles.append(book.title, ident);
        return ident;
    }
    
    // Удаление книги из библиотеки 
    void unregisterBook( Ident ident )
    {
        Books::const_iterator i = m_books.find(ident);
        if (i != m_books.end())
        {
            i = m_books.erase(i);
        }
        m_authors.remove(ident);
        m_titles.remove(ident);
    }
    
    // Получение всех идентификаторов книг с заданным авторов
    IdentVector getBooksByAuthor( const std::string & author ) const
    {
        assert(!author.empty());
        return m_authors.get(author);
    }
    
    // Получение всех идентификаторов книг с заданным названием
    IdentVector getBooksByTitle( const std::string & title ) const
    {
        assert(!title.empty());
        return m_titles.get(title);
    }
    
    // Получение всех идентикаторов книг с заданным автором и названием
    IdentVector getBooksByAuthorAndTitle( const std::string & author, const std::string & title ) const
    {
        assert(!author.empty() && !title.empty());
        
        IdentVector authors = getBooksByAuthor(author);
        IdentVector titles = getBooksByTitle(title);
        
        IdentVector result;        
        std::set_intersection(authors.begin(), authors.end(), titles.begin(), titles.end(), result.begin());
        return result;
    }

    // Получение данных книги по идентификатору (возвращает false, если книга не найдена)
    bool getBook( size_t ident, Book & book ) const
    {
        Books::const_iterator i = m_books.find(ident);
        if (i != m_books.end())
        {
            book = i->second;
            return true;
        }
        return false;
    }
    
    // Увеличение кол-ва книг
    void increaseBooksCount( Ident ident )
    {
        Books::iterator i = m_books.find(ident);
        if (i != m_books.end())
        {
            i->second.count++;
        }
    }
    
    // Уменьшение кол-ва книг (возвращает false, если кол-во равно нулю
    bool decreaseBooksCount( Ident ident )
    {
        Books::iterator i = m_books.find(ident);
        if (i != m_books.end())
        {
            if (i->second.count > 0)
            {
                i->second.count--;
                return true;
            }                
        }
        return false;
    }
    
    // Вернуть список всех книг, которые можно взять в библиотеке
    IdentVector getFreeBooks() const
    {
        IdentVector result;
        
        for (Books::const_iterator i = m_books.begin(); i != m_books.end(); ++i)
        {
            if (i->second.count > 0)
            {
                result.push_back(i->first);
            }
        }
        
        return result;
    }

private:
    Ident getNextIdent()
    {
        return m_nextIdent++;
    }

    // Следующий идентификатор
    Ident m_nextIdent;
    
    // Список книг
    typedef std::map<Ident, Book> Books;
    Books m_books;
    
    // Индексная таблица по авторам
    detail::Index<std::string> m_authors;
    // Индексная таблица по названиям книг
    detail::Index<std::string> m_titles;
};

// Читатель
struct Reader
{
    // ФИО
    std::string fullName;
    // Адрес
    std::string address;
    // Телефон
    std::string phone;
    
    Reader( const std::string & fullName, const std::string & address, const std::string & phone )
        : fullName(fullName), address(address), phone(phone)
    {
    }        
};

// База читателей
class ReaderDatabase
{
public:
    ReaderDatabase()
        : m_nextIdent(0)
    {
    }
    
    virtual ~ReaderDatabase() {}
    
    Ident registerReader( const Reader & reader )
    {
        Ident ident = getNextIdent();
        m_readers.insert(std::make_pair(ident, reader));
        m_fullNames.append(reader.fullName, ident);
        return ident;
    }
    
    void unregisterReader( Ident ident )
    {
        Readers::const_iterator i = m_readers.find(ident);
        if (i != m_readers.end())
        {
            m_readers.erase(i);
        }
        m_fullNames.remove(ident);
    }
    
    IdentVector getReadersByFullName( const std::string & fullName ) const
    {
        return m_fullNames.get(fullName);
    }
    
    IdentVector getReadersByPartName( const std::string & partName ) const
    {
        IdentVector result;
        for (Readers::const_iterator i = m_readers.begin(); i != m_readers.end(); ++i)
        {
            const Reader & reader = i->second;
            if (reader.fullName.find(partName) != std::string::npos)
            {
                result.push_back(i->first);
            }
        }
        return result;
    }
    
    // Информация о читателе
    bool getReader( Ident ident, Reader & reader ) const
    {
        Readers::const_iterator i = m_readers.find(ident);
        if (i != m_readers.end())
        {
            reader = i->second;
            return true;
        }
        return false;
    }
    
private:
    Ident m_nextIdent;
    
    Ident getNextIdent()
    {
        return m_nextIdent++;
    }    
    
    typedef std::map<Ident, Reader> Readers;
    Readers m_readers;
    
    detail::Index<std::string> m_fullNames;
};

// Библиотека
class Library : public ReaderDatabase, public BookDatabase
{
    struct Rent
    {
        Ident book;
        Ident reader;
        size_t days;
    };

public:
    // Взять книгу bookIdent читателем readerIdent на days дней (возвращает false в случае неудачи)
    bool popBook( Ident bookIdent, Ident readerIdent, size_t days )
    {
        if (decreaseBooksCount(bookIdent))
        {
            Rent rent;
            rent.book = bookIdent;
            rent.reader = readerIdent;
            rent.days = days;
            
            m_rents.push_back(rent);
            
            return true;
        }
        return false;
    }
    
    // Вернуть книгу bookIdent читателем readerIdent
    void pushBook( Ident bookIdent, Ident readerIdent )
    {
        for (Rents::const_iterator i = m_rents.begin(); i != m_rents.end(); ++i)
        {
            if (i->book == bookIdent && i->reader == readerIdent)
            {
                m_rents.erase(i);
                increaseBooksCount(bookIdent);
                break;
            }
        }
    }
    
    // Через сколько дней будет возвращена книга bookIdent читателем readerIdent
    size_t getRentaDays( Ident bookIdent, Ident readerIdent ) const
    {
        for (Rents::const_iterator i = m_rents.begin(); i != m_rents.end(); ++i)
        {
            if (i->book == bookIdent && i->reader == readerIdent)
            {
                return i->days;
            }
        }
    }
    
    // Возвращает список читателей, взявший данную книгу
    IdentVector getReadersByBook( Ident bookIdent ) const
    {
        IdentVector result;
        for (Rents::const_iterator i = m_rents.begin(); i != m_rents.end(); ++i)
        {
            if (i->book == bookIdent)
            {
                result.push_back(i->reader);
            }
        }
        return result;
    }
    
private:
    typedef std::vector<Rent> Rents;
    Rents m_rents;

};

#endif // _LIBRARY_H_


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

Читайте также: «Вопросы программисту на собеседовании с ответами»

Что такое матрица 4×4 в трехмерных играх?

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

Ну согласитесь — нормального человека нельзя заставить умножать и складывать 32 числа непонятно для какой цели.. Тупо делать это только ради оценки в журнале. Я так не умею, а Вы?

Итак, что же такое матрица 4×4? Какую роль она играет в трехмерных играх? Зачем эти 16 чисел складывать и умножать? СКАНДАЛЫ, ИНТРИГИ, РАССЛЕДОВАНИЯ.. Читайте дальше в моем журнале после короткой рекламы.

Рассмотрим абстрактную матрицу, в которой злой гений зашифровал положение объекта в трехмерном мире:

M=\begin{bmatrix}A&E&I&M\\B&F&J&N\\C&G&K&O\\D&H&L&P\end{bmatrix}

Начнем расшифровку.. В матрице присутствуют три вектора, вот они перед Вами:

\vec{X}=(A, B, C)
\vec{Y}=(E, F, G)
\vec{Z}=(I, J, K)

Как нетрудно догадаться, это вектора осей системы координат. Длина этих векторов — это масштаб объект вдоль каждого вектора. Если совсем дотошно, то это базисные векторы новой системы координат \vec{i}, \vec{j} и \vec{k} умноженные на масштаб вдоль каждой оси.

Рассмотрим такую матрицу:

M=\begin{bmatrix}2&0&0&M\\0&3&0&N\\0&0&0.5&O\\D&H&L&P\end{bmatrix}

Что следует из этой матрицы? Во-первых, по X объект увеличен в два раза, по Y в три раза, по Z уменьшен в два раза, направление системы координат не менялось.

Смотрим дальше. Числа M, N и O — это смещение объекта вдоль этих векторов (по X, Y и Z соответственно).

С остальными числами я пока не разобрался. Число P практически всегда равно единице, остальные — нулю. Есть подозрение, что они имеют отношение к проецированию трехмерного объекта на двухмерный экран.

Ссылка на подробный обзор матриц и операций на ними из комментариев: http://www.opengl-tutorial.org/ru/beginners-tutorials/tutorial-3-matrices/ и видео по теме:

Что такое кватернион — объяснение человеческим языком

Для закончивших ВУЗ данный вопрос не представляет проблемы, а для нас, учащихся ПТУ и техникумов, это тайна за семью замками. Особенно если вы, как и я, постоянно прогуливали высшую математику.

Цитата из википедии: Кватернио́ны (от лат. quaterni, по четыре) — система гиперкомплексных чисел, предложенная Гамильтоном в 1843 году.. Вы что-нибудь поняли? Значит дальше вам читать не надо, до свидания! Заходите еще..

Кватернион — это ось, относительно которой будем вращать объект и угол, на который мы будем вращать объект относительно этой оси. Всего у кватерниона четыре компоненты: X, Y, Z и W. XYZ — нормированная ось поворота, умноженная на синус половины угла, W — косинус половины угла поворота.

Псевдокод получения кватерниона из угла a (в радианах) и оси (x, y, z):

def quatFromAngleAxis(a, x, y, z):
    # Длина вектора xyz
    l = sqrt(x * x + y * y + z * z)

    # Если длина вектора близка к нулю, возвращаем единичный кватернион
    if l <= 1e-06:
        return (0, 0, 0, 1)

    # Нормализуем вектор xyz
    x = x / l
    y = y / l
    z = z / l

    # Синус половины угла (a - угол в радианах)
    hSin = sin(a / 2)

    # Заполняем xyz
    qx = x * hSin
    qy = y * hSin
    qz = z * hSin

    # Косинус половины угла
    hCos = cos(a / 2)

    # Заполняем w
    qw = hCos

    # Возвращаем наш кватернион
    return (qx, qy, qz, qw)

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

Ещё более простым языком: представьте, что у вас есть некий объект, допустим яблоко, вы протыкаете его спицей и вращаете. Так вот спица — это ось вращения, её направление — наш вектор (x, y, z), а угол вращения — это наш угол a, который означает насколько вы повернули яблоко на спице по часовой стрелке или против.

Если вас приводит в недоумение термин «матрица», рекомендую почитать заметку: Что такое матрица 4×4 в трехмерных играх?

Крутая система логгирования

Эта заметка была написана в моем втором по счету блоге на сайте blogspot два года назад. Сейчас я уже пришел к пониманию, что такая система логов мне не нравится. Она довольно навороченная, но все эти шаблоны меня теперь почему-то пугают. Дабы это добро не сгинуло вместе с тем дневником, публикую здесь. Прочитав данную статью, возможно, вы сможете блеснуть умом в узких кругах. :)

Полгода назад мы создавали крутой логгер, использование которого выглядело так:

logger::info() << "Тестовое сообщение номер " << 1;

Самая главная проблема была в том, чтобы воткнуть ‘\n’ в конце сообщения. Для этого в info() был примерно следующий код:

InfoStream info()
{
    static InfoStream stream;
    // std::endl пишет в ostream '\n' и делает flush()
    stream << std::endl;
    return stream;
}

Последняя строка завершалась только при закрытии файла (в деструкторе класса InfoStream). Первый flush игнорировался. Это все кажется простым, если мы пишем лог в обычный текстовый файл.

Когда лог стал в формате html, начались пляски с бубном.

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

Совсем недавно пришлось копаться во внутренностях. Задача стояла — вынести этот логгер в отдельный набор заголовков а-ля boost. Посмотрев на этот код, я плюнул и решил написать логгер заново. :)

Вот как это было реализовано:

class logger
{
public:
    class internal_stream
    {
    public:
        template <class T>
        internal_stream & operator <<(const T & value)
        {
            logger << value;
            return *this;
        }

        ~internal_stream() {}

    private:
        // Может использоваться только классом logger
        internal_stream(logger & stream) 
            : mStream(stream)
        {};

        logger & mStream;
    };

    internal_stream info()
    {
        return internal_stream(*this);
    }
};

Как это все работает:

  • в конструкторе logger — подготавливаем файл для записи, в текстовом ничего не делаем, в html — записываем заголовок
  • в деструкторе logger — закрываем файл, в html формате закрываем теги body, html и т.д.
  • в конструкторе internal_stream — начало строки, в html формате пишем tr, td и все что нравится
  • в деструкторе internal_stream — окончание строки, в текстовом это просто ‘\n’, в html закрываем теги tr и т.д.

Если сделать logger шаблонным классом, то можно реализовать разные буферы вывода. Начиная от текстового, html и xml и заканчивая сервером для приема и клиента для отправки сообщений по сети.

Python модуль для создания древовидной структуры даных на лету

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

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

# -*- coding: utf-8 -*-
import cPickle as pickle
from Dictionary import Dictionary

# Создаем пустой объект "animalsDict"
animalsDict = Dictionary()

# Добавляем ветку "cats" и ноду "wild" со значением "Tiger"
animalsDict.cats.wild = 'Tiger'
# К ветке "cats" добавляем ноду "home" со значение "House"
animalsDict.cats.home = 'House'
# Добавляем ветку "dogs" одной строкой с нодами "wild" и "home"
animalsDict.dogs = Dictionary(wild='wolf', home='House')
# Выводим данные с префиксом "animals"
print "before dump:"
animalsDict.printf('animals')

# Сохраняем объект "animalsDict" в строку
data = pickle.dumps(animalsDict)
# Восстанавливаем данные в объект "animalsRestored"
animalsRestored = pickle.loads(data)

# Выводим данные с префиксом "animals2"
print
print "after dump:"
animalsRestored.printf("animals2")

# Удаляем ветку cats
del animalsRestored.cats

# Повторно выводим данные
print
print "after remove:"
animalsRestored.printf("animals2")

# Создаем объект только для чтения
p = Dictionary(read_only=True, x=2, y=3.5, z=1.0)

# Выводим объект "p" с префиксом "point"
print
print "read only:"
p.printf("point")

# Если это раскомментировать, будет исключение типа KeyError
# del p.x

Вывод

before dump:
animals.cats.home = House
animals.cats.wild = Tiger
animals.dogs.home = House
animals.dogs.wild = wolf

after dump:
animals2.cats.home = House
animals2.cats.wild = Tiger
animals2.dogs.home = House
animals2.dogs.wild = wolf

after remove:
animals2.dogs.home = House
animals2.dogs.wild = wolf

read only:
point.x = 2
point.y = 3.5
point.z = 1.0

Модуль Dictionary.py

Исходники программы на Python

import cPickle as pickle
 
class Dictionary:
    def __init__(self, read_only=False, **params):
        self.__dict__['__children__'] = {}
        self.__dict__['__readonly__'] = read_only
        for key, value in params.iteritems():
            self.__dict__['__children__'][key] = value
 
 
    def clear(self):
        for key, value in self.__dict__['__children__'].iteritems():
            if isinstance(value, Dictionary):
                value.clear()
        self.__dict__['__children__'].clear()
 
 
    def printf(self, prefix=None):
        for key in sorted(self.__dict__['__children__'].iterkeys()):
            value = self.__dict__['__children__'][key]
            if value is None:
                continue
            if isinstance(value, Dictionary):
                if prefix:
                    value.printf('%s.%s' % (prefix, key))
                else:
                    value.printf('%s' % key)
            else:
                if prefix:
                    print '%s.%s = %s' % (prefix, key, str(value))
                else:
                    print '%s = %s' % (key, str(value))
 
 
    def __getattr__(self, name):
        try:
            return self.__dict__['__children__'][name]
        except KeyError:
            if not self.__dict__['__readonly__']:
                value = self.__dict__['__children__'][name] = Dictionary()
                return value
            raise
 
 
    def __delattr__(self, name):
        if self.__dict__['__readonly__']:
            raise KeyError, "Read only structure."
        del self.__dict__['__children__'][name]
 
 
    def __setattr__(self, name, value):
        children = self.__dict__['__children__']
        old_value = children.get(name)
 
        if name not in children:
            if self.__dict__['__readonly__']:
                raise KeyError, "Read only structure."
        elif isinstance(old_value, Dictionary):
            if old_value:
                raise AttributeError, "Attribute %s has children: %s" % (repr(name), old_value)
 
        children[name] = value
 
 
    def __getitem__(self, name):
        return self.__getattr__(name)
 
 
    def __setitem__(self, name, value):
        self.__setattr__(name, value)
 
 
    def __delitem__(self, name):
        self.__delattr__(name)
 
 
    def __eq__(self, value):
        if isinstance(value, Dictionary):
            return repr(self) == repr(value)
        if isinstance(value, dict):
            return self.__dict__['__children__'] == value
        return value is None
 
 
    def __ne__(self, value):
        return not self.__eq__(value)
 
 
    def __nonzero__(self):
        return bool(self.__dict__['__children__'])
 
 
    def __str__(self):
        return str(repr(self))
 
 
    def __repr__(self):
        params = [('read_only=%s' % self.__dict__['__readonly__'])]
        for key in sorted(self.__dict__['__children__'].iterkeys()):
            value = self.__dict__['__children__'][key]
            params.append('%s=%s' % (key, repr(value)))
        return 'Dictionary(%s)' % ', '.join(params)
 
 
    def __len__(self):
        return len(self.__dict__['__children__'])
 
 
    def iterkeys(self):
        return self.__dict__['__children__'].iterkeys()
 
 
    def itervalues(self):
        return self.__dict__['__children__'].itervalues()
 
 
    def iteritems(self):
        return self.__dict__['__children__'].iteritems()
 
 
    def __iter__(self):
        return self.__dict__['__children__'].__iter__()
 
 
    def __setstate__(self, state):
        for key, name, value in state:
            if key == 'param':
                self.__dict__[name] = value
            elif key == 'value':
                self.__dict__['__children__'][name] = pickle.loads(value)
 
 
    def __getstate__(self):
        result = []
        result.append(('param', '__readonly__', self.__dict__['__readonly__']))
 
        for name, value in self.__dict__['__children__'].iteritems():
            result.append(('value', name, pickle.dumps(value)))
        return tuple(result)
 
 
    def __getinitargs__(self):
        return ()
 
 
    @staticmethod
    def parse(kwargs):
        kwargs = dict(kwargs)
        params = {}
 
        while kwargs:
            key, value = kwargs.popitem()
            uri = key.split(".")
            params_ptr = params
 
            if len(uri) == 1:
                params[key] = value
                continue
 
            for i in xrange(len(uri)):
                k = uri[i]
                if i == len(uri) - 1:
                    params_ptr[k] = value
                elif k not in params_ptr:
                    params_ptr[k] = {}
                params_ptr = params_ptr[k]
 
        def create(dictionary):
            result = {}
            for key, value in dictionary.iteritems():
                if isinstance(value, dict):
                    result[key] = create(value)
                else:
                    result[key] = value
            return Dictionary(read_only=True, **result)
 
        return create(params)

Личный блог Евгения Жирнова