Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 15:05
Оценка:
Задание: последовательно обработать объекты, каждый из которых принадлежит определенному юзеру, но своих обработать только первые столько-то (потому что сам юзер мог задействовать undo). В C-подобном синтаксисе получается элементарно:
int cOwnSeen = 0;
for(item : items)
{
    if(item.owner != myself || cOwnSeen++ < limit)
    {
        processItem(item);
    }
}

Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?
До последнего не верил в пирамиду Лебедева.
Re: Красиво избежать побочных эффектов
От: FR  
Дата: 22.06.12 15:16
Оценка: -1
Здравствуйте, Roman Odaisky, Вы писали:


RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?


Псевдокод:

items |> take(limit) |> filter(item.owner != myself) |> processItem
Re[2]: Красиво избежать побочных эффектов
От: rameel https://github.com/rsdn/CodeJam
Дата: 22.06.12 15:24
Оценка: +1
Здравствуйте, FR, Вы писали:

FR>Псевдокод:


Скорее всего имелось это
items |> filter(item.owner != myself) |> processItem
items |> filter(item.owner == myself) |> take(limit) |> processItem


но здесь два прохода по коллекции, чего, возможно, захочется избежать
... << RSDN@Home 1.2.0 alpha 5 rev. 43>>
Re[3]: Красиво избежать побочных эффектов
От: FR  
Дата: 22.06.12 15:53
Оценка:
Здравствуйте, rameel, Вы писали:


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


Да невнимательно условия прочитал, такое на потоки плохо ложится.
Можно попробовать сложный фильтр возвращающий кортеж (element type, bool) где
второй элемент это флаг сигнализирующий что это родной элемент и в конце
take_while с условием. но сложновато получается по сравнению с императивным
циклом да.
Re: Красиво избежать побочных эффектов
От: elmal  
Дата: 22.06.12 17:05
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?

ИМХО наиболее идеологически правильно просто не допускать таких задач и таких условий, в которых есть опасность наделать ошибок. Для данной задачи ИМХО лучше не сделаешь. Можно конечно сделать изврат с созданием однократно используемого предиката с внутренним состоянием, и фильтрацией с использованием этого предиката, но это извратище из извратов.
То есть псевдокод:
Predicate p = new AllUsersButOnlyLimitedMine(limit);
processByFilter(items, p, processFunction);

Может идеологически и более правильно, но тут настолько легко ошибиться, забыв перед вызовом создать новый предикат, отсчитывающий с нуля, что ну их нахрен, такие извраты, лучше по старинке .
Re[3]: Красиво избежать побочных эффектов
От: elmal  
Дата: 22.06.12 17:14
Оценка:
Здравствуйте, rameel, Вы писали:

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

Зачем избегать? Избегать, это уже начинаются оптимизации. А хочется же наиболее чистого кода с минимальной вероятностью ошибки. Соответственно наиболее идеологически правильно дробить сложное навороченное правило на 2 простых правила, и выполнять их по отдельности. Время то будет один черт линейное. А когда появится проблема, что хотелось бы пробегать по коллекции один раз, уже придется простотой и идеологической правильностью жертвовать. На двух стульях сразу не усидеть!
Re: Красиво избежать побочных эффектов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 22.06.12 17:25
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?

Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.
Re[4]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 17:30
Оценка:
Здравствуйте, elmal, Вы писали:

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

E>Зачем избегать? Избегать, это уже начинаются оптимизации. А хочется же наиболее чистого кода с минимальной вероятностью ошибки. Соответственно наиболее идеологически правильно дробить сложное навороченное правило на 2 простых правила, и выполнять их по отдельности. Время то будет один черт линейное. А когда появится проблема, что хотелось бы пробегать по коллекции один раз, уже придется простотой и идеологической правильностью жертвовать. На двух стульях сразу не усидеть!

Задание было — обойти всё последовательно. Там графические объекты один на другой накладываются, надо рисовать в правильном порядке.
До последнего не верил в пирамиду Лебедева.
Re: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 17:35
Оценка:
Если я верно понял задание, то:

for i, item in enumerate(items):
    if(item.owner != myself or i < limit):
        processItem(item)


Аналогичный код можно написать и на C++. Возможно, на Питоне можно как-то ещё ужать, но задание выполнено: внешнего счётчика нет.
Re[2]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 17:37
Оценка:
Здравствуйте, samius, Вы писали:

RO>>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?

S>Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.

processItem — это нечто вроде canvas.drawShape. Но никто не мешает переписать чисто функционально наподобие foldl getCanvasCombinedWithShape originalCanvas itemsSubset. Как только сформировать этот itemsSubset?
До последнего не верил в пирамиду Лебедева.
Re[2]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 17:38
Оценка:
Здравствуйте, Kogrom, Вы писали:

K>Если я верно понял задание, то:


К сожалению, нет: считает все элементы, а не только свои. Если limit = 2, а items = [чужое, чужое, свое, свое], не сработает.

K>for i, item in enumerate(items):

K> if(item.owner != myself or i < limit):
K> processItem(item)
До последнего не верил в пирамиду Лебедева.
Re[3]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 17:50
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:
RO>К сожалению, нет: считает все элементы, а не только свои. Если limit = 2, а items = [чужое, чужое, свое, свое], не сработает.


class Item:
    def __init__(self, owner):
        self.owner = owner

def processItem(item):
    print item.owner

items = [Item(a) for a in [1, 1, 0, 0]]
limit = 2
myself = 0

for i, item in enumerate(items):
    if(item.owner != myself or i < limit):
        processItem(item)


Вывод:
1
1

Что требовалось?

Тем, кому нравится запутывать читателя можно вместо нормального цикла написать:
[processItem(item) for i, item in enumerate(items) if (item.owner != myself or i < limit)]
Re[3]: Красиво избежать побочных эффектов
От: samius Япония http://sams-tricks.blogspot.com
Дата: 22.06.12 17:53
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Здравствуйте, samius, Вы писали:


S>>Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.


RO>processItem — это нечто вроде canvas.drawShape. Но никто не мешает переписать чисто функционально наподобие foldl getCanvasCombinedWithShape originalCanvas itemsSubset. Как только сформировать этот itemsSubset?


я считаю что правильно формировать с помощью свертки, где состоянием пойдет счетчик таких что (owner != myself), и последующей фильтрации.

Если последовательность/язык не ленивы, то накапливать нужные элементы прямо в передаваемом состоянии свертки.
Re[4]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 18:06
Оценка:
Здравствуйте, Kogrom, Вы писали:

K>Вывод:

K>1
K>1

K>Что требовалось?

1
1
0
0


limit должен распространяться только на свои элементы.
До последнего не верил в пирамиду Лебедева.
Re[5]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 18:14
Оценка: -1
Здравствуйте, Roman Odaisky, Вы писали:

RO>limit должен распространяться только на свои элементы.


В общем, я тупо взял Ваш алгоритм и засунул счётчик внутрь него. Ничего в работе алгоритма не изменилось. Если у меня работает неверно, то значит в Вашем исходном алгоритме ошибка

Кстати, вывод и прорисовка кругов — это побочный эффект.
Re[6]: Красиво избежать побочных эффектов
От: Roman Odaisky Украина  
Дата: 22.06.12 19:02
Оценка:
Здравствуйте, Kogrom, Вы писали:

RO>>limit должен распространяться только на свои элементы.

K>В общем, я тупо взял Ваш алгоритм и засунул счётчик внутрь него. Ничего в работе алгоритма не изменилось.

Ленивость оператора «||» приводит к увеличению счетчика только при обработке своих (owner == myself) элементов, в отличие от enumerate, который возвращает увеличенное значение для всех объектов.

K>Кстати, вывод и прорисовка кругов — это побочный эффект.


Если рассматривать обсуждаемый кусок как функцию (canvas, items) → canvas, то можно считать, что побочных эффектов нет, см. сообщение выше
Автор: Roman Odaisky
Дата: 22.06.12
. Или представь себе yield item вместо processItem(item), пусть самой отрисовкой занимается еще кто-нибудь.
До последнего не верил в пирамиду Лебедева.
Re[7]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 20:06
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Ленивость оператора «||» приводит к увеличению счетчика только при обработке своих (owner == myself) элементов, в отличие от enumerate, который возвращает увеличенное значение для всех объектов.


Да, я ошибался. На C++ можно использовать такое жульничество:


for(int i = 0, j = 0; i < items.size(); ++i)
{
    if((items[i].owner != myself) || (j++ < limit))
    {
        processItem(items[i]);
    }
}


Как сделать на Питоне я ещё подумаю.
Re[8]: Красиво избежать побочных эффектов
От: Kogrom  
Дата: 22.06.12 20:23
Оценка:
Здравствуйте, Kogrom, Вы писали:

K>Как сделать на Питоне я ещё подумаю.


Вроде бы оно:

import itertools

for f, item in zip([[itertools.count()]]*len(items), items):
    if(item.owner != myself or f[0].next() < limit):
        processItem(item)


Но красоты тут нет, конечно.
Re: Красиво избежать побочных эффектов
От: DarkGray Россия http://blog.metatech.ru/post/ogni-razrabotki.aspx
Дата: 22.06.12 21:21
Оценка:
items.Merge(.Where(.owner != myself),  .Where(.owner == myself).Take(limit))
.Foreach(item => processItem(item))

функция Merge переписывает переданные в нее выражения для последовательной обработки коллекции в один проход
например,
— (.Where(condition-A), .Where(condition-B)) заменяется на .Where(condition-A || condition-B)
— .Where(condition-A).Where(condition-B) заменяется на .Where(condition-A && condition-B)
— .Take(limit) при необходимости заменяется на Where(wave:i=0, {i < limit; i = i + 1})
и т.д.

пометка wave перед переменной означает, что состояние данной переменной разделяется между итерациями ("движется" вместе с курсором текущего элемента)


Foreach умеет комбинироваться вместе с Merge ("пожирая" его)

в итоге, всё это транслируется в
items.Foreach(wave:i = 0)
 item => 
  if (item.owner != myself || item.owner == myself && i < limit)
    processItem(item);
  if (item.owner != myself)
    i = i + 1;


зы
язык — один из внутренних экспериментов
Re: Красиво избежать побочных эффектов
От: Воронков Василий Россия  
Дата: 22.06.12 21:30
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

Сделать-то можно, но проблема в "красиво".

Вариант, который первым пришел в голову (пишу на своем языке, динамика, но скорее всего можно и типизировать):

let comb p max n e | p e = Some (comb p max n)
                   | n < max = Some (comb p max (n+1))
                   | else = None

let filter _ [] = []
    filter comb (x::xs) | res is Some comb = x :: filter comb xs
                        | else = []
                        where res = comb x

filter ((==0) << (%2) `comb` 10 <| 0) [1..100]


Первоначальное условие для наглядности немного поменял на — берем из списка четные числа или n нечетных.
Проверить код можно здесь: http://elalang.net/elac.aspx

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

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