Скажем, у меня есть список целых чисел, где каждый элемент представляет собой число от 1 до 20. (Это не то, что я пытаюсь отсортировать.)
Теперь у меня есть массив «операций», где каждая операция:
- Удаляет определенные (известные) номера из списка
- и Добавляет некоторые другие (известные) номера в список
- и Невозможно обработать список, если он содержит определенные (известные) номера в начале операции - вызовите эти Prevent
Редактировать: в каждом из может быть ноль или более чисел * Добавляет , Удаляет и Запретить для каждой операции, и каждое число может отображаться как ноль или больше раз в каждой группе для какой-то операции. Для любой операции добавляет и удаляет не пересекаются, предотвращает и удаляет не пересекаются, но добавляет и Предотвратить может перекрываться.
Я хочу отсортировать массив операций так, чтобы для каждой операции:
- Если операция содержит Запретить элементов, то после операции помещается , удаляющая эти числа. Если не сразу после этого, не может быть операции Adds , которая добавляет эти числа обратно между последними Removes и Prevent .
- Если операция Удаляет элементов, все операции, которые Добавляет любой из этих элементов, ставятся перед ней.
В случае циклической зависимости цепочка операций должна удалить как можно больше чисел и сообщить мне, что она не может удалить все числа.
Существует ли название / реализация для этого типа алгоритма, который превосходит тот, который у меня есть ниже?
Добавлено 8/23: вознаграждение за покрытие требований сортировки с учетом как кодов операций (набор структур), так и InstructionSemantics
(набор битовых флагов из перечисления).
Добавлено позже. 8/23: Я улучшил производительность на 89: 1, эвристически предварительно отсортировав исходный массив. Смотрите мой текущий ответ для деталей.
namespace Pimp.Vmx.Compiler.Transforms
{
using System;
using System.Collections.Generic;
using System.Reflection.Emit;
internal interface ITransform
{
IEnumerable<OpCode> RemovedOpCodes { get; }
IEnumerable<OpCode> InsertedOpCodes { get; }
IEnumerable<OpCode> PreventOpCodes { get; }
InstructionSemantics RemovedSemantics { get; }
InstructionSemantics InsertedSemantics { get; }
InstructionSemantics PreventSemantics { get; }
}
[Flags]
internal enum InstructionSemantics
{
None,
ReadBarrier = 1 << 0,
WriteBarrier = 1 << 1,
BoundsCheck = 1 << 2,
NullCheck = 1 << 3,
DivideByZeroCheck = 1 << 4,
AlignmentCheck = 1 << 5,
ArrayElementTypeCheck = 1 << 6,
}
internal class ExampleUtilityClass
{
public static ITransform[] SortTransforms(ITransform[] transforms)
{
throw new MissingMethodException("Gotta do something about this...");
}
}
}
<ч />
Редактировать: Под этой строкой находится справочная информация о том, что я на самом деле делаю, на случай, если люди задаются вопросом, почему я спрашиваю об этом. Это не меняет проблему, просто показывает область действия.
У меня есть система, которая считывает список элементов и отправляет его в другой «модуль» для обработки. Каждый элемент является инструкцией в моем промежуточном представлении в компиляторе - обычно это число от 1 до ~ 300 плюс некоторая комбинация из примерно 17 доступных модификаторов (перечисление флагов). Сложность системы обработки (ассемблер машинного кода) пропорциональна количеству возможных уникальных входных данных (число + флаги), где я должен вручную кодировать каждый отдельный обработчик. Кроме того, мне нужно написать как минимум 3 независимые системы обработки (X86, X64, ARM) - количество фактического кода обработки, которое я могу использовать для нескольких систем обработки, минимально.
Вставляя «операции» между чтением и обработкой, я могу гарантировать, что некоторые элементы никогда не появятся для обработки - я делаю это, выражая числа и / или флаги через другие числа. Я могу кодировать каждую «операцию преобразования» в черный ящик, описывая ее эффекты, что экономит мне массу сложности на каждую операцию. Операции являются сложными и уникальными для каждого преобразования, но намного проще, чем система обработки. Чтобы показать, сколько времени это экономит, одна из моих операций полностью удаляет 6 флагов, записывая их требуемые эффекты в виде примерно 6 чисел (без флагов).
Для того, чтобы держать вещи в черном ящике, я хочу, чтобы алгоритм упорядочения выполнял все операции, которые я пишу, приказывал им оказывать наибольшее влияние и информировал меня о том, насколько успешно я упростил данные, которые в конечном итоге достигнут система (ы) обработки. Естественно, я нацеливаюсь на самые сложные элементы в промежуточном представлении и упрощаю их до базовой арифметики указателей, где это возможно, что легче всего обрабатывать в ассемблерах. :)
После всего сказанного я добавлю еще одну заметку. Эффекты операции описаны как «атрибутные эффекты» в списке инструкций. В целом, операции ведут себя хорошо, но некоторые из них удаляют только числа, которые идут после других чисел (например, удаляют все 6, которые не следуют за 16). Другие удаляют все экземпляры определенного числа, которое содержит определенные флаги. Я займусь этим позже - ПОСЛЕ того, как я выясню основную проблему гарантированного добавления / удаления / предотвращения, перечисленную выше.
Добавлено 8/23: На этом изображении вы можете увидеть инструкцию call
(серая), которая обработана InstructionSemantics.NullCheck
преобразованием RemoveNullReferenceChecks
для удаления флаг семантики в обмен на добавление другого вызова (без добавленной семантики к добавленному вызову). Теперь ассемблеру не нужно понимать / обрабатывать InstructionSemantics.NullCheck
, потому что он никогда их не увидит. Не критикуйте код ARM - на данный момент это заполнитель .