Задание: последовательно обработать объекты, каждый из которых принадлежит определенному юзеру, но своих обработать только первые столько-то (потому что сам юзер мог задействовать undo). В C-подобном синтаксисе получается элементарно:
Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?
RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?
R>но здесь два прохода по коллекции, чего, возможно, захочется избежать
Да невнимательно условия прочитал, такое на потоки плохо ложится.
Можно попробовать сложный фильтр возвращающий кортеж (element type, bool) где
второй элемент это флаг сигнализирующий что это родной элемент и в конце
take_while с условием. но сложновато получается по сравнению с императивным
циклом да.
Здравствуйте, Roman Odaisky, Вы писали:
RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?
ИМХО наиболее идеологически правильно просто не допускать таких задач и таких условий, в которых есть опасность наделать ошибок. Для данной задачи ИМХО лучше не сделаешь. Можно конечно сделать изврат с созданием однократно используемого предиката с внутренним состоянием, и фильтрацией с использованием этого предиката, но это извратище из извратов.
То есть псевдокод:
Predicate p = new AllUsersButOnlyLimitedMine(limit);
processByFilter(items, p, processFunction);
Может идеологически и более правильно, но тут настолько легко ошибиться, забыв перед вызовом создать новый предикат, отсчитывающий с нуля, что ну их нахрен, такие извраты, лучше по старинке .
Здравствуйте, rameel, Вы писали:
R>но здесь два прохода по коллекции, чего, возможно, захочется избежать
Зачем избегать? Избегать, это уже начинаются оптимизации. А хочется же наиболее чистого кода с минимальной вероятностью ошибки. Соответственно наиболее идеологически правильно дробить сложное навороченное правило на 2 простых правила, и выполнять их по отдельности. Время то будет один черт линейное. А когда появится проблема, что хотелось бы пробегать по коллекции один раз, уже придется простотой и идеологической правильностью жертвовать. На двух стульях сразу не усидеть!
Здравствуйте, Roman Odaisky, Вы писали:
RO>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке?
Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.
Здравствуйте, elmal, Вы писали:
R>>но здесь два прохода по коллекции, чего, возможно, захочется избежать E>Зачем избегать? Избегать, это уже начинаются оптимизации. А хочется же наиболее чистого кода с минимальной вероятностью ошибки. Соответственно наиболее идеологически правильно дробить сложное навороченное правило на 2 простых правила, и выполнять их по отдельности. Время то будет один черт линейное. А когда появится проблема, что хотелось бы пробегать по коллекции один раз, уже придется простотой и идеологической правильностью жертвовать. На двух стульях сразу не усидеть!
Задание было — обойти всё последовательно. Там графические объекты один на другой накладываются, надо рисовать в правильном порядке.
Здравствуйте, samius, Вы писали:
RO>>Но известно, что побочные эффекты — зло. Если потребуется заменить условие на чуть более сложное, есть опасность наделать ошибок. Как выглядит эквивалентный, но идеологически правильный код на вашем любимом языке? S>Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.
processItem — это нечто вроде canvas.drawShape. Но никто не мешает переписать чисто функционально наподобие foldl getCanvasCombinedWithShape originalCanvas itemsSubset. Как только сформировать этот itemsSubset?
Здравствуйте, Kogrom, Вы писали:
K>Если я верно понял задание, то:
К сожалению, нет: считает все элементы, а не только свои. Если limit = 2, а items = [чужое, чужое, свое, свое], не сработает.
K>for i, item in enumerate(items): K> if(item.owner != myself or i < limit): K> processItem(item)
Здравствуйте, 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)]
Здравствуйте, Roman Odaisky, Вы писали:
RO>Здравствуйте, samius, Вы писали:
S>>Побочные эффекты здесь не только в условии. processItem — тоже не без них, судя по всему.
RO>processItem — это нечто вроде canvas.drawShape. Но никто не мешает переписать чисто функционально наподобие foldl getCanvasCombinedWithShape originalCanvas itemsSubset. Как только сформировать этот itemsSubset?
я считаю что правильно формировать с помощью свертки, где состоянием пойдет счетчик таких что (owner != myself), и последующей фильтрации.
Если последовательность/язык не ленивы, то накапливать нужные элементы прямо в передаваемом состоянии свертки.
Здравствуйте, Roman Odaisky, Вы писали:
RO>limit должен распространяться только на свои элементы.
В общем, я тупо взял Ваш алгоритм и засунул счётчик внутрь него. Ничего в работе алгоритма не изменилось. Если у меня работает неверно, то значит в Вашем исходном алгоритме ошибка
Кстати, вывод и прорисовка кругов — это побочный эффект.
Здравствуйте, Kogrom, Вы писали:
RO>>limit должен распространяться только на свои элементы. K>В общем, я тупо взял Ваш алгоритм и засунул счётчик внутрь него. Ничего в работе алгоритма не изменилось.
Ленивость оператора «||» приводит к увеличению счетчика только при обработке своих (owner == myself) элементов, в отличие от enumerate, который возвращает увеличенное значение для всех объектов.
K>Кстати, вывод и прорисовка кругов — это побочный эффект.
Если рассматривать обсуждаемый кусок как функцию (canvas, items) → canvas, то можно считать, что побочных эффектов нет, см. сообщение выше
Здравствуйте, Roman Odaisky, Вы писали:
RO>Ленивость оператора «||» приводит к увеличению счетчика только при обработке своих (owner == myself) элементов, в отличие от enumerate, который возвращает увеличенное значение для всех объектов.
Да, я ошибался. На C++ можно использовать такое жульничество:
функция 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;
Вариант, который первым пришел в голову (пишу на своем языке, динамика, но скорее всего можно и типизировать):
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
Принцип простой — строим такой предикат, который возвращает результат (в виде варианта) и самого себя — состояние при этом протаскивается через стек. Однако использовать стандартный фильтр с таким предикатом нельзя — тот ждет, что предикат будет возвращать булевое. Поэтому приходится писать еще и свой фильтр, который декомпозирует предикат. Проще говоря на каждой итерации приходится создавать новый предикат.
Этого можно избежать, если написать другую функцию фильтр, в которую захардкодить все проверки и также тащить состояние через стек. Фактически ваш код просто переписывается в рекурсивную функцию — это достаточно просто. Но от ФП тут фактически уже вообще ничего не остается. В общем, не знаю, надо еще подумать