Dmitry
Помогающий
Offline
|
|
« : 23-11-2012 16:32 » |
|
Нужна возможность обрабатывать коллекцию элементов типа T1 и на выходе иметь коллекцию элементов типа T2. Типы могут быть как ссылочные, так и значимые. Сразу на ум приходит что-то такое: public static IEnumerable<Item<TResult>> Compute<TInput, TResult>(this IEnumerable<TInput> list, Evaluator<TInput, TResult> evaluator) { return evaluator.Compute(list); }
abstract class Evaluator<TInput, TResult> { public abstract IEnumerable<TResult> Compute(IEnumerable<TInput> list); }
class E1 : Evaluator<int, string> { public override IEnumerable<string> Compute(IEnumerable<int> list) { return list.Select(x => x.Val + "!!!")); } }
class E2 : Evaluator<string, string> { public override IEnumerable<string> Compute(IEnumerable<string> list) { return list.Select(x => x+ "@@@"); } } Теперь можем писать цепочки преобразований: E1 e1 = new E1(); E2 e2 = new E2(); var list = new List<int> {1,2,3}; var newList = list.Compute(e1).Compute(e2); Получаем контроль типов при компиляции. Это замечательно, но как строить цепочки обработок динамически, например, в зависимости от результата выбора пользователя? Простой вариант - переложить проблему в райнтайм с помощью dynamic. Второй вариант - использовать ковариацию интерфейса Enumerator<>. для этого создаём общий базовый класс Item для элементов коллекций и базовый класс Evaluator для обобщенного Evaluator<Tinput, TResult>, а также добавляем необобщенный extension метод для IEnumerable<Item>: public static IEnumerable<Item> Compute(this IEnumerable<Item> list, Evaluator evaluator) { return evaluator.Compute(list); }
public static IEnumerable<Item<TResult>> Compute<TInput, TResult>(this IEnumerable<Item<TInput>> list, Evaluator<TInput, TResult> evaluator) { return evaluator.Compute(list); }
class Evaluator { public abstract IEnumerable<Item> Compute(IEnumerable<Item> list); }
abstract class Evaluator<TInput, TResult> : Evaluator { public override sealed IEnumerable<Item> Compute(IEnumerable<Item> list) { return Compute((IEnumerable<Item<TInput>>)list); } public abstract IEnumerable<Item<TResult>> Compute(IEnumerable<Item<TInput>> list); }
class E1 : Evaluator<int, string> { public override IEnumerable<Item<string>> Compute(IEnumerable<Item<int>> list) { return list.Select(x => new Item<string>(x.Val + "!!!")); } }
class E2 : Evaluator<string, string> { public override IEnumerable<Item<string>> Compute(IEnumerable<Item<string>> list) { return list.Select(x => new Item<string>(x.val + "@@@")); } } Использование: Evaluator e; if (expression) e = new X1(); else e = new X2(); IEnumerable<Item> = list.Compute(e); Теперь захотелось, чтобы в Evaluator.Compute передавались не все элементы, а лишь те, которые ему нужны. Например, по индексу - для этого заведём у него массив Items, в котором хранятся индексы необходимых элементов. Получится просто кошмар: public static IEnumerable<Item<TResult>> Compute<TInput, TResult>(this IEnumerable<Item<TInput>> list, Evaluator<TInput, TResult> evaluator) { var array = list.ToArray(). var items = evaluator.Items.Select(x => array[x]); return evaluator.Compute(items); } И аналогично для необобщенной версии перегрузки. Гораздо логичней был бы такой вариант: public static List<Item<TResult>> Compute<TInput, TResult>(this List<Item<TInput>> list, Evaluator<TInput, TResult> evaluator) { var items = evaluator.Items.Select(x => list[x]); return evaluator.Compute(items).ToList(); } Но тогда опять возвращаемся к первому вопросу - как динамически строить последовательность обработок? Dynamic?
|
|
« Последнее редактирование: 23-11-2012 17:45 от Dmitry »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #1 : 23-11-2012 17:51 » |
|
Dmitry, другими словами, как строить конвейеры обработки потоков (stream). Коллекции сами по себе к этой проблеме имеют слабое отношение. Потоки читаются поэлементно; иногда нужны накапливающие буферы или память состояний (автоматы). Нередко элементы потоков имеют сложную структуру (в том числе сами могут являться потоками).
Это есть попытка написать универсальную библиотеку?
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #2 : 23-11-2012 18:08 » |
|
Dimka, нет-нет, это просто обработка данных. На входе массив, на выходе - другой массив. Обработка осуществляется в несколько шагов, на каждом из которых получается свой массив со своим типом данных. Пользователь для каждого этапа задаёт (выбирает) алгоритм обработки.
|
|
« Последнее редактирование: 23-11-2012 18:11 от Dmitry »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #3 : 23-11-2012 18:23 » |
|
Dmitry, ты ничего не сказал в ответ на предложение работать как с потоками (stream). Если ты видишь здесь какие-то проблемы, то озвучь их.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #4 : 23-11-2012 18:33 » |
|
Если честно, я просто не понял, что ты имеешь в виду. И какая тут связь с потоками
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #5 : 23-11-2012 20:47 » |
|
Например, такая система классов: interface IConverter<Source, Destination> { void Put(Source source);
bool Ready { get; }
Destination Get(); }
class SimpleConverter<Source, Destination> : IConverter<Source, Destination> { private Source source; private bool ready; private Convert convert;
public delegate Destination Convert(Source source);
public SimpleConverter(Convert convert) { this.convert = convert; this.ready = false; }
public void Put(Source source) { this.source = source; this.ready = true; }
public bool Ready { get { return this.ready; } }
public Destination Get() { if (this.Ready) { return this.convert(this.source); } else { throw new InvalidOperationException(); } } }
class ConvertersChain<Source, Middle, Destination> : IConverter<Source, Destination> { private IConverter<Source, Middle> primary; private IConverter<Middle, Destination> secondary;
public ConvertersChain(IConverter<Source, Middle> primary, IConverter<Middle, Destination> secondary) { this.primary = primary; this.secondary = secondary; }
public void Put(Source source) { this.primary.Put(source); if (this.primary.Ready) { this.secondary.Put(this.primary.Get()); } }
public bool Ready { get { return this.secondary.Ready; } }
public Destination Get() { if (this.Ready) { return this.secondary.Get(); } else { throw new InvalidOperationException(); } } }
class CollectionConverter<SourceItem, DestinationItem> { public static void Convert(ICollection<SourceItem> source, ICollection<DestinationItem> destination, IConverter<SourceItem, DestinationItem> converter) { foreach (SourceItem item in source) { converter.Put(item); if (converter.Ready) { destination.Add(converter.Get()); } } } } Позволяет строить произвольные по сложности обработки конвейеры. Например, линейный с задержкой: class MovingAverage : IConverter<double, double> { private double[] buffer; private int bufferSize;
public MovingAverage(int windowSize) { this.buffer = new double[windowSize]; this.bufferSize = 0; }
public void Put(double source) { if (!this.Ready) { this.buffer[this.bufferSize] = source; this.bufferSize += 1; } else { for (int i = 1; i < this.bufferSize; i += 1) { this.buffer[i - 1] = this.buffer[i]; } this.buffer[this.bufferSize - 1] = source; } }
public bool Ready { get { return this.bufferSize == this.buffer.Length; } }
public double Get() { if (this.Ready) { double item = 0; for (int i = 0; i < this.bufferSize; i += 1) { item += this.buffer[i]; } item /= this.bufferSize; return item; } else { throw new InvalidOperationException(); } } }
class Program {
static void Main(string[] args) { const int n = 10; Random random = new Random(); double[] source = new double[n]; for (int i = 0; i < n; i += 1) { source[i] = random.NextDouble(); } List<int> destination = new List<int>(); Console.WriteLine(string.Join(", ", source)); CollectionConverter<double, int>.Convert(source, destination, new ConvertersChain<double, double, int>( new ConvertersChain<double, double, double>( new SimpleConverter<double, double>(x => 10.0 * x), new MovingAverage(3) ), new SimpleConverter<double, int>(x => Convert.ToInt32(Math.Round(x))) ) ); Console.WriteLine(string.Join(", ", destination)); Console.ReadKey(); } } Простые преобразования можно сразу задавать лямбдами. Более сложные требуют реализации отдельным классом. Но, в зависимости от предметной области, и здесь возможны разные шаблонные обобщения.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #6 : 23-11-2012 23:41 » |
|
Dimka, очень интересно! Отметил для себя несколько трудностей, но уже поздно, я подумаю об этом завтра
|
|
« Последнее редактирование: 24-11-2012 00:55 от Dmitry »
|
Записан
|
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #7 : 24-11-2012 18:22 » |
|
Всё-таки конвейеры больше подходят для обработки именно потоков данных, когда преобразования не зависят от индексации элементов. В моём случае возможны преобразования 1 к 1 и n к 1. Первый случай простой - это конвертер SimpleConverter с добавленным счётчиком, значение которого передаем в делегат. Второй случай, например, имеется массив есть целых чисел, нужно подсчитать суммы двух его подмножеств, элементы которых задаются индексами. Написал конвертер, который накапливает элементы в отдельном буфере для каждого подмножества (я назвал их блоками) и при заполнении нужного буфера делегирует его обработку производному классу. abstract class BlockConverter<Source, Destination> : IConverter<Source, Destination> { private Source[][] buffers; private int[] bufferSizes; private int currentBlockIndex; private int numberOfBlocks; private int itemCount; private int[] blockIndexes;
protected BlockConverter(IEnumerable<int> blockIndexes) { int[] sortedDistinctBlockIndexes = blockIndexes.Distinct().OrderBy(x => x).ToArray(); this.numberOfBlocks = sortedDistinctBlockIndexes.Length; if (sortedDistinctBlockIndexes[0] != 0 || sortedDistinctBlockIndexes[numberOfBlocks - 1] != numberOfBlocks - 1) throw new ArgumentException();
bufferSizes = new int[numberOfBlocks]; buffers = new Source[numberOfBlocks][]; for (int i = 0; i < numberOfBlocks; i++) { int size = blockIndexes.Count(x => x == i); buffers[i] = new Source[size]; }
this.blockIndexes = blockIndexes.ToArray(); } public void Put(Source source) { if(itemCount > blockIndexes.Length) { currentBlockIndex = 0; itemCount = 0; for (int i = 0; i < numberOfBlocks; i++) { bufferSizes[i] = 0; } } int blockIndex = blockIndexes[itemCount]; buffers[blockIndex][bufferSizes[blockIndex]] = source; bufferSizes[blockIndex]++; itemCount++; }
public bool Ready { get { return buffers[currentBlockIndex].Length == bufferSizes[currentBlockIndex]; } }
public Destination Get() { if (this.Ready) { currentBlockIndex++; return Compute(buffers[currentBlockIndex - 1]); } else { throw new InvalidOperationException(); } }
public abstract Destination Compute(Source[] items); } да, именование у меня хромает... Например, суммирование: class SummBlockConverter : BlockConverter<int, int> { public SummBlockConverter(IEnumerable<int> blockIndexes) : base(blockIndexes) { }
public override int Compute(int[] block) { return block.Sum(); } } Использование: class Program { static void Main(string[] args) { int[] blockIndexes = new int[10] { 0, 1, 2, 2, 2, 1, 0, 1, 2, 2 }; int[] In = new int[10] { 4, 5, 1, 4, 5, 5, 6, 5, 5, 3 };
var Out = new List<int>();
CollectionConverter<int, int>.Convert(In, Out, new SummBlockConverter(blockIndexes));
Out.ForEach(Console.WriteLine);
Console.Read(); } } Выводит 10, 15, 17. А если последний элемент отнести не к последней группе, а, например, к 1, то на ней конвейер и остановится, ведь у нас на один put - один get. И вывод будет - 10, 18, а третий элемент потеряли
|
|
« Последнее редактирование: 24-11-2012 18:30 от Dmitry »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #8 : 24-11-2012 23:26 » |
|
Dmitry, я не понимаю, причём тут преобразования без индексации. Математического множества (неупорядоченного) в компьютере нету. Порядок есть всегда, просто не всегда он важен.
Вообще все эти идеи давно реализованы в разных скриптовых языках, в том же Ruby, Python и т.п. Так что велосипед изобретать смысла нет, а достаточно лишь подсмотреть, как это делается. И систематически использовать математику - то, что применяется в функциональном программировании.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #9 : 25-11-2012 09:57 » |
|
На том же самом интерфейсе несколько обновлённое множество классов типовых конверторов: class SimpleConverter<Source, Destination> : IConverter<Source, Destination> { private Source source; private bool ready; private Convert convert;
public delegate Destination Convert(Source source);
public SimpleConverter(Convert convert) { this.convert = convert; this.ready = false; }
public void Put(Source source) { this.source = source; this.ready = true; }
public bool Ready { get { return this.ready; } }
public Destination Get() { if (this.Ready) { this.ready = false; return this.convert(this.source); } else { throw new InvalidOperationException(); } } }
class ConvertersChain<Source, Middle, Destination> : IConverter<Source, Destination> { private IConverter<Source, Middle> primary; private IConverter<Middle, Destination> secondary;
private void pass() { if (!this.secondary.Ready && this.primary.Ready) { this.secondary.Put(this.primary.Get()); } }
public ConvertersChain(IConverter<Source, Middle> primary, IConverter<Middle, Destination> secondary) { this.primary = primary; this.secondary = secondary; }
public void Put(Source source) { this.primary.Put(source); this.pass(); }
public bool Ready { get { return this.secondary.Ready; } }
public Destination Get() { if (this.Ready) { Destination result = this.secondary.Get(); this.pass(); return result; } else { throw new InvalidOperationException(); } } }
class IndexedSimpleConverter<Source, Destination> : IConverter<Source, Destination> { private Source source; private bool ready; private int index; private Convert convert;
public delegate Destination Convert(int index, Source item);
public IndexedSimpleConverter(Convert convert) { this.index = 0; this.convert = convert; this.ready = false; }
public void Put(Source source) { this.source = source; this.ready = true; }
public bool Ready { get { return this.ready; } }
public Destination Get() { if (this.Ready) { Destination result = this.convert(this.index, this.source); this.ready = false; this.index += 1; return result; } else { throw new InvalidOperationException(); } } }
struct IndexedItem<ItemType> { public int Index; public ItemType Item;
public IndexedItem(int index, ItemType item) { this.Index = index; this.Item = item; } }
class IndexPackConverter<Source> : IConverter<Source, IndexedItem<Source>> { private IndexedSimpleConverter<Source, IndexedItem<Source>> converter;
public IndexPackConverter() { this.converter = new IndexedSimpleConverter<Source, IndexedItem<Source>>( (index, item) => new IndexedItem<Source>(index, item) ); }
public void Put(Source item) { this.converter.Put(item); }
public bool Ready { get { return this.converter.Ready; } }
public IndexedItem<Source> Get() { return this.converter.Get(); } }
class IndexUnpackConverter<Destination> : IConverter<IndexedItem<Destination>, Destination> { private SimpleConverter<IndexedItem<Destination>, Destination> converter;
public IndexUnpackConverter() { this.converter = new SimpleConverter<IndexedItem<Destination>, Destination>( pack => pack.Item ); }
public void Put(IndexedItem<Destination> item) { this.converter.Put(item); }
public bool Ready { get { return this.converter.Ready; } }
public Destination Get() { return this.converter.Get(); } }
class FilterConverter<Source> : IConverter<Source, Source> { private Predicate<Source> filter; private bool ready; private Source item;
public FilterConverter(Predicate<Source> filter) { this.filter = filter; this.ready = false; }
public void Put(Source item) { if(this.filter(item)) { this.item = item; this.ready = true; } }
public bool Ready { get { return this.ready; } }
public Source Get() { if(this.Ready) { this.ready = false; return this.item; } else { throw new InvalidOperationException(); } } }
class BufferConverter<Source> : IConverter<Source, Source[]> { private Source[] buffer; private int bufferSize; private bool ready;
public BufferConverter(int bufferCapacity) { this.buffer = new Source[bufferCapacity]; this.bufferSize = 0; this.ready = false; }
public void Put(Source source) { if (this.bufferSize < this.buffer.Length) { this.buffer[this.bufferSize] = source; this.bufferSize += 1; } else { this.ready = true; for (int i = 1; i < this.bufferSize; i += 1) { this.buffer[i - 1] = this.buffer[i]; } this.buffer[this.bufferSize - 1] = source; } }
public bool Ready { get { return this.ready; } }
public Source[] Get() { if (this.Ready) { this.ready = false; return this.buffer; } else { throw new InvalidOperationException(); } } }
class CollectionConverter<SourceItem, DestinationItem> { public static void Convert(ICollection<SourceItem> source, ICollection<DestinationItem> destination, IConverter<SourceItem, DestinationItem> converter) { foreach (SourceItem item in source) { converter.Put(item); while (converter.Ready) { destination.Add(converter.Get()); } } } } Здесь несколько переработана обработка флага Ready: учтено, что конвертор может генерировать несколько выходных значений по 1 входному. Поэтому из каждого предыдущего конвертора вытягиваются все значения, пока его Ready не возвратится в false. Часть тобою описанных проблем это снимает. И тогда задача вычисления целых значений скользящего среднего для увеличенных на 10 исходных данных, отфильтрованных так, что берутся только чётные по счёту, выглядит так: class Program {
static void Main(string[] args) { const int n = 20; Random random = new Random(); double[] source = new double[n]; for (int i = 0; i < n; i += 1) { source[i] = random.NextDouble(); } Console.WriteLine(string.Join(", ", source));
List<int> destination = new List<int>();
CollectionConverter<double, int>.Convert(source, destination, new ConvertersChain<double, double[], int>( new ConvertersChain<double, double, double[]>( new ConvertersChain<double, IndexedItem<double>, double>( new ConvertersChain<double, IndexedItem<double>, IndexedItem<double>>(
new IndexPackConverter<double>(), new FilterConverter<IndexedItem<double>>( pack => pack.Index % 2 == 0 )), new IndexUnpackConverter<double>()), new BufferConverter<double>(3)), new SimpleConverter<double[], int>(window => { double average = 0; foreach(double item in window) { average += item * 10; } average /= window.Length; return Convert.ToInt32(Math.Round(average)); })) );
Console.WriteLine(string.Join(", ", destination));
Console.ReadKey(); } } Здесь индексация вводится явным образом лишь для фильтра по порядковому номеру; в остальных случаях индексация не нужна. Если строить фильтры по значению, в них тоже не будет зависимости от порядка.
|
|
« Последнее редактирование: 25-11-2012 10:01 от Dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #10 : 25-11-2012 14:39 » |
|
Разобранный пример очень удачно вписывается в концепцию конвейерной обработки - среднее вычисляется для элементов, идущих последовательно, т.е. элементы группируются в буфере, а потом обрабатываются на следующем конверторе. Удобно. А если элементы перемешаны? Например, пользователь мышкой нащелкал и выбрал - хочу две суммы, сумму для таких-то элементов и для вот этих вот. Нужно вводить ещё сортирующий конвертор, или что-то типа того, что я изобразил в ответе 7, с множественными бУферами. Либо запускать обработку несколько раз с разными фильтрами. А если нужно посчитать для одних элементов среднее, а для других сумму (хотя такое вряд ли понадобится)? Придётся ещё ветвления организовывать. Всё-таки, по-моему, тот кривоватый доморощенный подход из моего первого поста тут больше подходит.
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #11 : 25-11-2012 16:49 » |
|
Dmitry, по-моему, сортировки буферов проблемы не представляют.
Что касается ветвлений, здесь мне не вполне понятно. Есть 1 исходная коллекция, нужно получить 1 результирующую. Любое ветвление приведёт к 2 результирующим последовательностям. И чего с ними делать? Объединять? А как? Т.е. я хочу сказать, что у тебя нет внятной постановки задачи: ты сам толком не знаешь, чего хочешь.
Если же речь идёт о том, чтобы по условию обрабатывать очередной элемент так или иначе, то это обычный простой конвертор, у которого lamda-функция содержит оператор ?: или if-else выражение.
Теперь о "пользователь нащёлкал": ты об этом ничего не говоришь, и рассуждения о преобразованиях коллекций мы ведём абстрактные. Если есть конкретная задача, зачем о ней умалчивать? Сферические кони в вакууме никогда не были особо полезными.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #12 : 25-11-2012 17:20 » |
|
Функционал предполагается такой. Пользователь выбирает тип данных и количество элементов. Это исходная коллекция. Далее пользователь из этой коллекции выбирает набор элементов и операцию, которую нужно совершить с этим набором (напр. суммирование). Таких пар - набор и операция - может быть несколько. Результаты применения всех преобразований (возвращаемые значения имеют один тип) задают новую коллекцию, либо добавляются в текущую. И всё повторяется. Операции (преобразования) могут быть двух типов, по соотношению входные/выходные элементы - n -> 1, либо n-> n Сейчас допишу по-своему, но там с проектированием не всё удачно..
|
|
« Последнее редактирование: 25-11-2012 17:34 от Dmitry »
|
Записан
|
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #13 : 25-11-2012 20:48 » |
|
Как я уже говорил, с проектированием дела плохи. Классы MultiplyConverter и SingleConverter (кстати фишка, их имена можно поменять местами ) пришлось подогнать под один интерфейс. Альтернативой было в ConverterGroup и Tools проверять переданные конверторы - унаследованы они от MultiplyConverter или SingleConverter. public abstract class Converter<TInput, TResult> { protected int[] indexes;
public abstract IEnumerable<TResult> Compute(IList<TInput> list);
protected IEnumerable<TInput> getRequiredItems(IList<TInput> list) { return this.indexes == null ? list : this.indexes.Select(x => list[x]); }
public int[] Indexes { set { indexes = value; } } }
public class ConverterGroup<TInput, TResult> : Converter<TInput, TResult> { private List<Converter<TInput, TResult>> converters;
public ConverterGroup(params Converter<TInput,TResult>[] converters ) { this.converters = new List<Converter<TInput, TResult>>(converters); }
public void Add(Converter<TInput, TResult> converter) { if (converter == null) throw new ArgumentNullException("converter"); converters.Add(converter); }
public override IEnumerable<TResult> Compute(IList<TInput> list) { IEnumerable<TResult> result = new List<TResult>(); foreach (var evaluator in converters) { result = result.Concat(evaluator.Compute(list)); } return result; } }
public abstract class SingleConverter<TInput, TResult> : Converter<TInput, TResult> { public override IEnumerable<TResult> Compute(IList<TInput> list) { if (indexes == null) { for (int i = 0; i < list.Count; i++) { yield return ComputeCore(list[i], i); } } else { int index = 0; foreach (var item in getRequiredItems(list)) { yield return ComputeCore(item, indexes[index]); index++; } } }
public abstract TResult ComputeCore(TInput item, int index); }
public abstract class MultiplyConverter<TInput, TResult> : Converter<TInput, TResult> { public override IEnumerable<TResult> Compute(IList<TInput> list) { yield return ComputeCore(getRequiredItems(list)); }
public abstract TResult ComputeCore(IEnumerable<TInput> list); }
public class SummConverter : MultiplyConverter<int, int> { public override int ComputeCore(IEnumerable<int> list) { return list.Sum(); } }
public class AverageConverter : MultiplyConverter<int, int> { public override int ComputeCore(IEnumerable<int> list) { return (int)list.Average(); } }
public class CustomConverter<TInput, TResult> : MultiplyConverter<TInput, TResult> { private Convert convert;
public delegate TResult Convert(TInput[] items);
public CustomConverter(Convert convert) { this.convert = convert; }
public override TResult ComputeCore(IEnumerable<TInput> list) { return convert(list.ToArray()); } }
public class LinearConverter<TInput, TResult> : SingleConverter<TInput, TResult> { private Convert convert;
public delegate TResult Convert(TInput item, int index);
public LinearConverter(Convert convert) { this.convert = convert; }
public override TResult ComputeCore(TInput item, int index) { return convert(item, index); } } static class Tools { public static IList<T> AddCompute<T>(this IList<T> list, Converter<T, T> converter) { return list.Concat(converter.Compute(list)).ToList(); }
public static IList<TResult> NewCompute<TInput, TResult>(this IList<TInput> list, Converter<TInput, TResult> converter) { return converter.Compute(list).ToList(); } } Значения чётных по порядку элементов умножаются на 2, считаются среднее значение 0 и 5 элемента, сумма 1 и 2 и сумма всех вместе. Потом к этим трём элемента добавляется наибольшее значение из среднего и первой суммы. class Program { static void Main(string[] args) { int[] input = { 1, 4, 3, 3, 2, 6 };
IList<int> output = input .NewCompute(new LinearConverter<int, int>((item, index) => index % 2 == 0 ? item * 2 : item)) .NewCompute(new ConverterGroup<int, int>( new AverageConverter() { Indexes = new int[] { 0, 5 } }, new SummConverter() { Indexes = new int[] { 1, 2 } }, new SummConverter())) .AddCompute(new CustomConverter<int, int>(items => items.Max()) { Indexes = new int[] { 0, 1 } });
Console.WriteLine(string.Join(", ", output));
Console.Read(); } }
|
|
« Последнее редактирование: 25-11-2012 21:30 от Dmitry »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #14 : 25-11-2012 22:29 » |
|
Dmitry, в целом похоже на попытку создать язык программирования.
Раз пользователь выбирает операции, то их перечень фиксированный. Вот и перечисли все возможные операции, а также типы.
Отображение последовательности в атомарное значение, и отображение одной и той же последовательности в разные, но однотипные атомарные значения проблемы не представляет. Но в целом эта затея выглядит так, что ты хочешь конструировать структуры данных (гетерогенные массивы). Это богатая тема.
А какова прикладная область всей этой разработки? Просто складывается впечатление, что ты изобретаешь велосипед. .NET - платформа многоязыковая. И для таких динамических вещей удобнее использовать подходящие языки. А вот строгость по типам данных тебе тут вовсе не нужна, так как пользователь оперирует доступными ему пользовательскими типами (как метаинформацией), а не типами на уровне самого языка программирования.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #15 : 26-11-2012 17:12 » |
|
Всё довольно прозаично - это будет конструктор алгоритмов для обработки данных разных психодиагностических методик (тестов). Что касается строгости к типам - у меня в первом посте этой темы как раз был вопрос из-за этого... возможно, я ещё вернусь к этому. Уже добавил F# в воображаемый список вещей, подлежащих изучению. И спасибо за беседу и код, возьму на вооружение.
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #16 : 26-11-2012 17:43 » |
|
Можно, например, JScript.NET взять. Во-первых, он компилируемый. Во-вторых, компилируется он так, что исходный код интерпретируется во "вкомпилированном" интерпретаторе. В частности, eval-конструкция работает, и можно исполнять код, передаваемый в виде строки как данные. Т.е. можно генерировать код алгоритма по ходу исполнения.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
|