Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Collection<T1> -> Collection<T2>  (Прочитано 15280 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Dmitry
Помогающий

ru
Offline Offline

« : 23-11-2012 16:32 » 

Нужна возможность обрабатывать коллекцию элементов типа T1 и на выходе иметь коллекцию элементов типа T2. Типы могут быть как ссылочные, так и значимые. Сразу на ум приходит что-то такое:
Код: (C#)
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+ "@@@");
    }
}

Теперь можем писать цепочки преобразований:
Код: (C#)
 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>:
Код: (C#)
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 + "@@@"));
    }
}
Использование:
Код: (C#)
Evaluator e;
 if (expression) e = new X1();
            else e = new X2();
IEnumerable<Item> = list.Compute(e);

Теперь захотелось, чтобы в Evaluator.Compute передавались не все элементы, а лишь те, которые ему нужны. Например, по индексу - для этого заведём у него массив Items, в котором хранятся индексы необходимых элементов.
Получится просто кошмар:
Код: (C#)
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);
}
И аналогично для необобщенной версии перегрузки.
Гораздо логичней был бы такой вариант:
Код: (C#)
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
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #1 : 23-11-2012 17:51 » 

Dmitry, другими словами, как строить конвейеры обработки потоков (stream). Коллекции сами по себе к этой проблеме имеют слабое отношение. Потоки читаются поэлементно; иногда нужны накапливающие буферы или память состояний (автоматы). Нередко элементы потоков имеют сложную структуру (в том числе сами могут являться потоками).

Это есть попытка написать универсальную библиотеку?
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dmitry
Помогающий

ru
Offline Offline

« Ответ #2 : 23-11-2012 18:08 » 

Dimka, нет-нет, это просто обработка данных. На входе массив, на выходе - другой массив. Обработка осуществляется в несколько шагов, на каждом из которых получается свой массив со своим типом данных.  Пользователь для каждого этапа задаёт (выбирает) алгоритм обработки.
« Последнее редактирование: 23-11-2012 18:11 от Dmitry » Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #3 : 23-11-2012 18:23 » 

Dmitry, ты ничего не сказал в ответ на предложение работать как с потоками (stream). Если ты видишь здесь какие-то проблемы, то озвучь их.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dmitry
Помогающий

ru
Offline Offline

« Ответ #4 : 23-11-2012 18:33 » 

Если честно, я просто не понял, что ты имеешь в виду. И какая тут связь с потоками Жаль
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #5 : 23-11-2012 20:47 » 

Например, такая система классов:
Код: (C#)
    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());
                }
            }
        }
    }
Позволяет строить произвольные по сложности обработки конвейеры. Например, линейный с задержкой:
Код: (C#)
    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
Помогающий

ru
Offline Offline

« Ответ #6 : 23-11-2012 23:41 » 

Dimka, очень интересно!
Отметил для себя несколько трудностей, но уже поздно, я подумаю об этом завтра Улыбаюсь
« Последнее редактирование: 24-11-2012 00:55 от Dmitry » Записан
Dmitry
Помогающий

ru
Offline Offline

« Ответ #7 : 24-11-2012 18:22 » 

Всё-таки конвейеры больше подходят для обработки именно потоков данных, когда преобразования не зависят от индексации элементов. В моём случае возможны преобразования 1 к 1 и n к 1. Первый случай простой - это конвертер SimpleConverter с добавленным счётчиком, значение которого передаем в делегат. Второй случай, например, имеется массив есть целых чисел, нужно подсчитать суммы двух его подмножеств, элементы которых задаются индексами.
Написал конвертер, который накапливает элементы в отдельном буфере для каждого подмножества (я назвал их блоками) и при заполнении нужного буфера делегирует его обработку производному классу.

Код: (C#)
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);
}
да, именование у меня хромает...
Например, суммирование:
Код: (C#)
class SummBlockConverter : BlockConverter<int, int>
{
    public SummBlockConverter(IEnumerable<int> blockIndexes) : base(blockIndexes)
    {
    }

    public override int Compute(int[] block)
    {
        return block.Sum();
    }
}

Использование:
Код: (C#)
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
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #8 : 24-11-2012 23:26 » 

Dmitry, я не понимаю, причём тут преобразования без индексации. Математического множества (неупорядоченного) в компьютере нету. Порядок есть всегда, просто не всегда он важен.

Вообще все эти идеи давно реализованы в разных скриптовых языках, в том же Ruby, Python и т.п. Так что велосипед изобретать смысла нет, а достаточно лишь подсмотреть, как это делается. И систематически использовать математику - то, что применяется в функциональном программировании.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #9 : 25-11-2012 09:57 » 

На том же самом интерфейсе несколько обновлённое множество классов типовых конверторов:
Код: (C#)
    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 исходных данных, отфильтрованных так, что берутся только чётные по счёту, выглядит так:
Код: (C#)
    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
Помогающий

ru
Offline Offline

« Ответ #10 : 25-11-2012 14:39 » 

Разобранный пример очень удачно вписывается в концепцию конвейерной обработки - среднее вычисляется для элементов, идущих последовательно, т.е. элементы группируются в буфере, а потом обрабатываются на следующем конверторе. Удобно. А если элементы перемешаны? Например, пользователь мышкой нащелкал и выбрал - хочу две суммы, сумму для таких-то элементов и для вот этих вот. Нужно вводить ещё сортирующий конвертор, или что-то типа того, что я изобразил в ответе 7, с множественными бУферами. Либо запускать обработку несколько раз с разными фильтрами.
А если нужно посчитать для одних элементов среднее, а для других сумму (хотя такое вряд ли понадобится)? Придётся ещё ветвления организовывать.
Всё-таки, по-моему, тот кривоватый доморощенный подход из моего первого поста тут больше подходит.
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #11 : 25-11-2012 16:49 » 

Dmitry, по-моему, сортировки буферов проблемы не представляют.

Что касается ветвлений, здесь мне не вполне понятно. Есть 1 исходная коллекция, нужно получить 1 результирующую. Любое ветвление приведёт к 2 результирующим последовательностям. И чего с ними делать? Объединять? А как? Т.е. я хочу сказать, что у тебя нет внятной постановки задачи: ты сам толком не знаешь, чего хочешь.

Если же речь идёт о том, чтобы по условию обрабатывать очередной элемент так или иначе, то это обычный простой конвертор, у которого lamda-функция содержит оператор ?: или if-else выражение.

Теперь о "пользователь нащёлкал": ты об этом ничего не говоришь, и рассуждения о преобразованиях коллекций мы ведём абстрактные. Если есть конкретная задача, зачем о ней умалчивать? Сферические кони в вакууме никогда не были особо полезными.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dmitry
Помогающий

ru
Offline Offline

« Ответ #12 : 25-11-2012 17:20 » 

Функционал предполагается такой. Пользователь выбирает тип данных и количество элементов. Это исходная коллекция. Далее пользователь из этой коллекции выбирает набор элементов и операцию, которую нужно совершить с этим набором (напр. суммирование). Таких пар - набор и операция -  может быть несколько. Результаты применения всех преобразований (возвращаемые значения имеют один тип) задают новую коллекцию, либо добавляются в текущую. И всё повторяется.
Операции (преобразования) могут быть двух типов, по соотношению входные/выходные элементы - n -> 1, либо n-> n
Сейчас допишу по-своему, но там с проектированием не всё удачно..
« Последнее редактирование: 25-11-2012 17:34 от Dmitry » Записан
Dmitry
Помогающий

ru
Offline Offline

« Ответ #13 : 25-11-2012 20:48 » 

Как я уже говорил, с проектированием дела плохи. Классы MultiplyConverter и SingleConverter (кстати фишка, их имена можно поменять местами  Улыбаюсь) пришлось подогнать под один интерфейс. Альтернативой было в ConverterGroup и Tools проверять переданные конверторы - унаследованы они от MultiplyConverter или SingleConverter.
Код: (C#)
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);
    }
}

Код: (C#)
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 и сумма всех вместе. Потом к этим трём элемента добавляется наибольшее значение из среднего и первой суммы.
Код: (C#)
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
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #14 : 25-11-2012 22:29 » 

Dmitry, в целом похоже на попытку создать язык программирования.

Раз пользователь выбирает операции, то их перечень фиксированный. Вот и перечисли все возможные операции, а также типы.

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

А какова прикладная область всей этой разработки? Просто складывается впечатление, что ты изобретаешь велосипед. .NET - платформа многоязыковая. И для таких динамических вещей удобнее использовать подходящие языки. А вот строгость по типам данных тебе тут вовсе не нужна, так как пользователь оперирует доступными ему пользовательскими типами (как метаинформацией), а не типами на уровне самого языка программирования.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Dmitry
Помогающий

ru
Offline Offline

« Ответ #15 : 26-11-2012 17:12 » 

Всё довольно прозаично - это будет конструктор алгоритмов для обработки данных разных психодиагностических методик (тестов).
Что касается строгости  к типам - у меня в первом посте этой темы как раз был вопрос из-за этого... возможно, я ещё вернусь к этому.
Уже добавил F# в воображаемый список вещей, подлежащих изучению.
И спасибо за беседу и код, возьму на вооружение.
Записан
Dimka
Деятель
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #16 : 26-11-2012 17:43 » 

Можно, например, JScript.NET взять. Во-первых, он компилируемый. Во-вторых, компилируется он так, что исходный код интерпретируется во "вкомпилированном" интерпретаторе. В частности, eval-конструкция работает, и можно исполнять код, передаваемый в виде строки как данные. Т.е. можно генерировать код алгоритма по ходу исполнения.
Записан

Программировать - значит понимать (К. Нюгард)
Невывернутое лучше, чем вправленное (М. Аврелий)
Многие готовы скорее умереть, чем подумать (Б. Рассел)
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines