2010-04-23 3 views
7

Это вопрос о комбинаторике от не математика, поэтому, пожалуйста, старайтесь нести со мной!Алгоритм минимального изменения, который максимизирует «свопинг»

Учитывая массив из n различных символов, я хочу сгенерировать подмножества из k символов в порядке минимального изменения, то есть порядок, в котором поколение i + 1 содержит ровно один символ, который не был в генерации i. Это не слишком сложно. Тем не менее, я также хочу максимизировать число случаев, когда символ, который заменен из в генерации i + 1, является тем же символом, который был заменен в в гене i. В качестве иллюстрации, при п = 7, к = 3:

аЬс абд Abe * ABF * ABG * AFG AEG * ADG * ACG * ДСА асе * ACF * AEF ADF * ADE BDE BDF BEF BCF * BCE BCD * BCG * bdg beg * bfg * cfg ceg * cdg * cde cdf * cef def deg dfg efg

Звездообразные строки указывают случай, который я хочу максимизировать; например e, который является новым в поколении 3, abe, заменяет d, который был новым в поколении 2, abd. Кажется невозможным, чтобы это происходило в каждом поколении, но я хочу, чтобы это происходило как можно чаще.

Типичные размеры массива, которые я использую, составляют 20-30 и размер подмножества около 5-8.

Я использую нечетный язык, Icon (или фактически его производную Unicon), поэтому я не ожидаю, что кто-нибудь отправит код, который я могу использовать напрямую. Но я буду благодарен за ответы или подсказки в псевдокоде и сделаю все возможное, чтобы перевести C и т. Д. Также я заметил, что такие проблемы часто обсуждаются в терминах массивов целых чисел, и я могу, безусловно, применять решения отправленный в таких терминах к моей собственной проблеме.

Благодаря

Ким Bastin

Редактировать 15 июня 2010:

Я, кажется, попал в глубокую воду, чем я думал, и в то время как я благодарен за все ответы, не все они были актуальны. В качестве примера решения, которое НЕ является адекватным, позвольте мне опубликовать мою собственную процедуру Unicon для генерации k-арных подмножеств набора символов s в минимальном порядке изменения. Вещи, которые вам нужно знать для понимания кода: preposed * означает размер структуры, поэтому, если s является строкой, * s означает размер s (количество содержащихся в нем символов). || представляет собой операцию конкатенации строк. Предполагаемый! производит каждый элемент структуры, например. каждый символ строки, в свою очередь, последовательные проходы. И структура управления «suspend» возвращает результат процедуры, но оставляет процедуру «в ожидании» со всеми локальными переменными, чтобы новые результаты могли быть получены, если процедура вызывается в цикле.

procedure revdoor(s, k) 
# Produces all k-subsets of a string or character set s in a 'revolving 
# door' order. Each column except the first traverses the characters 
# available to it in alphabetical and reverse alphabetical order 
# alternately. The order of the input string is preserved. 
# If called in a loop as revdoor("abcdefg", 3), 
# the order of production is: abc, abd, abe, abf, abg, acg, acf, ace, acd, 
# ade, adf, adg, aeg, aef, afg, bfg, bef, beg, bdg, bdf, bde, bcd, bce, 
# bcf, bcg, cdg, cdf, cde, cef, ceg, cfg, dfg, deg, def, efg 

local i 
static Ctl 

if /Ctl then {    # this means 'if Ctl doesn't exist' 
    if k = 0 then return "" 
    Ctl := list(k, 1)  # a list of k elements, each initialised to 1. 
} 

if Ctl[k] = 1 then { 
    if k = 1 then suspend !s else 
    every i := 1 to *s-k+1 do { 
     suspend s[i] || revdoor(s[i+1:0], k-1) 
    } 
    } else { 
    if k = 1 then suspend !reverse(s) else 
    every i := -k to -*s by -1 do { 
     suspend s[i] || revdoor(s[i+1:0], k-1) 
    } 
    } 

    # the following line multiplies element k of Ctl by -1 if k < size of Ctl 
    # (this controls the order of generation of characters), 
    # and destroys Ctl on final exit from the procedure. 
    if k < *Ctl then Ctl[k] *:= -1 else Ctl := &null 

end 

Обратите внимание, что выход вышеуказанной процедуры не является оптимальным в моем смысле. Одним из результатов моих исследований пока является то, что максимальная «свопинг-оценка» для списка k-арных подмножеств n элементов не меньше гребня (n-1, k), или в случае n = 7, k = 3, максимальный балл по крайней мере гребен (6, 3) = 20. Я определяю «своп-оценку» списка как количество элементов в списке, новый элемент которого заменяет элемент в предыдущем элементе, который сам был новым. У меня нет математического оборудования, чтобы доказать это, но его легко увидеть с k = 1 или k = 2.Для некоторых (п, к) немного более высокий балл можно, как и в случае п = 7, к = 3:

аЬс абд Abe ABF ABG
ACG ADG AEG AFG
EFG ДФГ CFG BFG
бек БДГ БЦЖ
BCD BCE BCF
BDF BEF
защиту CEF AEF
ADF ACF
ДСА туз
ADE
BDE CDE
CDF CDG
КЭГ
града (обменивать балл = 21)

Можно отметить, что приведенные выше список находится в «сильном порядке минимального изменения» (как слово гольфы: новый персонаж всегда находится в том же положении, что и характер, который он заменяет), что может указывать направление, которое выполняет моя собственная работа. Я надеюсь опубликовать что-то еще через несколько дней.

Kim

+0

Я имел честь работать с иконой раз ; это потрясающий язык. 'if a polygenelubricants

+0

Вы используете букву« n »как в« n различных символах », так и в« генерации n ». Вместо этого я предлагаю использовать «поколение i». – mathmike

+0

Я видел один и тот же алгоритм, когда работал в проекте машинного перевода. Просто попробуйте помочь в поиске. Я не был глубоко связан с этими algs (я помню, что в нем было слово «tiling») –

ответ

1

Это довольно просто. Чтобы максимизировать замену, просто подумайте о символах как числах и увеличьте строку на единицу до тех пор, пока вы не достигнете верхнего предела. Затем проверьте, чтобы вы не использовали один и тот же символ дважды в строке. Я думаю, что это будет работать:

char c[] = {'a', 'b', 'c', 'd', 'e'}; 
const int n = 5; 
const int k = 3; 
char s[k]; 

void print() 
{ 
    for(int i = 0; i < k; ++i) 
     putchar(c[s[i]]); 
    putchar('\n'); 
} 

bool increment(int m) 
{ 
    // reached the limit? 
    if(++s[m] == n && m == 0) 
     return false; 

    next: 
    for(int i = 0; i < m; ++i) 
    { 
     if(s[m] == n) 
     { 
      // carry 
      s[m] = 0; 
      if(!increment(m-1)) 
       return false; 
      goto next; 
     } 
     else if(s[i] == s[m]) 
     { 
      // the character is already used 
      ++s[m]; 
      goto next; 
     } 
    } 
    return true; 
} 

int main(int, char**) 
{ 
    // initialise 
    for(int i = 0; i < k; ++i) 
     s[i] = i; 

    // enumerate all combinations 
    do 
     print(); 
    while(increment(k-1)); 
} 
+0

Насколько я могу понять этот C (отслеживая его с помощью ручки и бумаги - я не смог его собрать под LCC-win32), его первые несколько результатов: abc abd abe acb ... Если я право, это не то, что я хочу, и, похоже, я не сформулировал проблему полностью. Я хочу произвести все комбинации k-ary , а не перестановки, из n символов. См. Мой пример для n = 7, k = 3. acb - это перестановка abc, поэтому не должна производиться, если abc был создан ранее. Ким Бастин –

0

Ким, ваше описание проблемы звучит очень похоже (домашнее задание) попытки описать самое простое решение для перечисления всех K-комбинации множества из п элементов - без предоставления фактического решение слишком легко. В любом случае, см. Ниже мой снимок. Я использовал Java, но важные детали не отличаются от С.

public class Homework 
{ 
    /** 
    * Prints all k-combinations of a set of n elements. Answer to this 
    * question: http://stackoverflow.com/questions/2698551 
    */ 
    public static void main(String[] args) 
    { 
    Combinations combinations = new Combinations(7, 3); 
    System.out.printf(
     "Printing all %d %d-combinations of a set with %d elements:\n", 
     combinations.size(), combinations.k, combinations.n); 
    for (int[] c : combinations) 
     System.out.println(Arrays.toString(c)); 
    } 

    /** 
    * Provides an iterator for all k-combinations of a set of n elements. 
    */ 
    static class Combinations implements Iterable<int[]> 
    { 
    public final int n, k; 

    public Combinations(int n, int k) 
    { 
     if (n < 1 || n < k) 
     throw new IllegalArgumentException(); 
     this.n = n; 
     this.k = k; 
    } 

    @Override 
    public Iterator<int[]> iterator() 
    { 
     return new Iterator<int[]>() 
     { 
     private int[] c; 

     @Override 
     public void remove() { throw new UnsupportedOperationException(); } 

     @Override 
     public int[] next() 
     { 
      if (c == null) 
      { 
      c = new int[k]; 
      for (int i = 0; i < k; i++) 
       c[i] = i; 
      } 
      else 
      { 
      int i = c.length - 1; 
      while (i >= 0 && c[i] == n - k + i) 
       i--; 

      if (i < 0) 
       throw new NoSuchElementException(); 

      c[i]++; 
      for (int j = i + 1; j < c.length; j++) 
       c[j] = c[i] + j - i; 
      } 
      return c.clone(); // remove defensive copy if performance is more important 
     } 

     @Override 
     public boolean hasNext() { return c == null || c[0] < n - k; } 
     }; 
    } 

    /** 
    * Returns number of combinations: n!/(k! * (n - k)!). 
    */ 
    public BigInteger size() 
    { 
     BigInteger s = BigInteger.valueOf(n); 
     for (int i = n - 1; i > n - k; i--) 
     s = s.multiply(BigInteger.valueOf(i)); 
     for (int i = k; i > 1; i--) 
     s = s.divide(BigInteger.valueOf(i)); 
     return s; 
    } 
    } 
} 

Вот выход для примера:

Printing all 35 3-combinations of a set with 7 elements: 
[0, 1, 2] [0, 1, 3] [0, 1, 4] [0, 1, 5] [0, 1, 6] [0, 2, 3] [0, 2, 4] [0, 2, 5] [0, 2, 6] [0, 3, 4] 
[0, 3, 5] [0, 3, 6] [0, 4, 5] [0, 4, 6] [0, 5, 6] [1, 2, 3] [1, 2, 4] [1, 2, 5] [1, 2, 6] [1, 3, 4] 
[1, 3, 5] [1, 3, 6] [1, 4, 5] [1, 4, 6] [1, 5, 6] [2, 3, 4] [2, 3, 5] [2, 3, 6] [2, 4, 5] [2, 4, 6] 
[2, 5, 6] [3, 4, 5] [3, 4, 6] [3, 5, 6] [4, 5, 6] 
+1

Спасибо за ваш ответ. К сожалению, вы, похоже, не слишком внимательно прочитали мой вопрос. Я хочу генерировать комбинации k-out-of-n в порядке минимального изменения, т. Е. Каждое новое поколение должно менять ровно один символ. Существуют известные алгоритмы с этим свойством, но ваше решение не отображает его: обратите внимание на переход [0,1,6] [0,2,3], который меняет два элемента. Специфическое требование в моем вопросе - максимизировать немедленную замену новых элементов - вообще не рассматривается. –

+0

Вы правы, я должен был больше обратить внимание на требование минимального изменения максимальной замены. Я не знал о алгоритмах минимального изменения (краткий веб-поиск доказал их существование, но я не видел никакого кода без членства в ACM и т. Д.).В основном замена последних элементов, по-видимому, гарантирует высокую скорость обмена для них, но другие заказы могут максимизировать это. Если количество комбинаций управляемо, я уверен, что по крайней мере некоторый подход к линейному программированию может оптимизировать порядок комбинаций по желанию. Конечно, предпочтительным является решение. Могу ли я спросить, зачем вам это нужно? –

0

Вместо того, чтобы начать с алгоритмом, я пытался думать о способ найти форму для максимального «обмена», чтобы вы знали, на что стрелять. Часто из такого доказательства может возникнуть алгоритм для создания желаемой структуры.

Прошло много времени с университета, но я попытался подумать о комбинаторной модели, которая поможет понять это, без особого успеха.

Я начал с представления множества комбинаций в виде вершин в графе с ребрами, соответствующими «смежности» (только одного элемента) комбинаций. Итак:

  • "п выбрать K" вершины
  • каждая вершина имеет степень к (пк)
  • число ребер = "п выбрать К" * к (пк)/2 = "п выбрать 2" * «n-2 select k-1»

Существует много симметрии к этим графикам. Граф одинаков для любого заданного {n, k}, как и для {n, n-k}. Если k = 1 или k = n-1, это полный график на n вершин (каждая комбинация отличается от всех остальных только одним символом). Однако я не вижу очевидного алгоритма.

Редактировать: Моя следующая мысль заключалась в том, чтобы представить график с немного иной интерпретацией. Вы можете думать о каждом {n, k} -комбинации как о последовательности n бит, где есть k 1s. Позиция 1s соответствует тому, какой из n символов присутствует в комбинации. Таким образом, для n = 7 k = 3 abc равно 1110000, adg 1001001, efg - 0000111. С этим вы также можете представить точки, лежащие в углах n-мерного гиперкуба. Поэтому для данной подпоследовательности ребра соответствуют вашим критериям «минимального подкачки», если они являются копланарными: я считаю их «срезающими плоскостями» через гиперкуб.

Вы ищете путь Гамильтона через этот график комбинаций, соответствующий вашим особым критериям.

Другой способ подумать о вашей проблеме состоит в том, чтобы свести к минимуму количество раз в последовательности, которую вы do изменить какой символ в комбинации будет изменен.

+0

Я думаю, что оптимизированная стратегия поиска будет более легко найдена, чем алгоритм, который непосредственно создает порядок максимизации подкачки. Легко начать такой заказ - см. Список для n = 7, k = 3 ниже кода, который я разместил, - и легко увидеть шаблоны. Продолжение до конца сложнее, и любой шаблон трудно различить. Надеюсь, вы продолжаете думать по своим линиям, которые примерно параллельны моей. –

+0

Спасибо, что подтолкнул меня в направлении теории графов. Я нахожу, что моя проблема эквивалентна минимальной проблеме обложки клики, т. Е. Нахождению наименьшего набора кликов, который включает каждую вершину в графе ... которая является NP-полной проблемой :( –

0

Для хорошего ответа, будет ли прием списка всех комбинаций одновременно приемлемым, или вам нужно их вычислить по одному за раз? Другими словами, вам нужна функция:

Combination nextCombo(); 

или будет

vector<Combination> allCombinations(); 

быть приемлемым?

Если вычисление комбинаций в партии допустимо, возможно, что итерационно-углубляющий поиск A * (или просто поиск A *, но я подозреваю, что он исчерпал память) будет работать. При допустимой эвристике A * гарантированно даст оптимальный результат. Мне не хватает времени, поэтому я решил опубликовать этот частичный ответ и отредактировать сообщение, если у меня будет время написать код.

A * - алгоритм поиска графа. В этом случае узлы представляют собой списки используемых комбинаций (в списке не допускаются дубликаты). Мой план состоял в том, чтобы использовать представление битовой строки для узлов. n = 30 помещается в 32-битное целое число. Мы можем произвольно переставить любое решение так, чтобы первая комбинация начиналась с 0 и заканчивалась в 1, т. Е. 000 ... 1111. Узел с более коротким списком подключается к более длинному, если два списка совпадают до тех пор, пока последний элемент и последний элемент не будут отличаться только тем, что один 0-бит перевернут на 1 и один 1 бит, перевернутый на 0. расстояние между ними равно 0, если было «своп» и 1, если был обмен.

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

Чтобы проиллюстрировать эту эвристику, рассмотрим последовательность abc abd abe * abf * abg afg сверху. (буквы будут числами в моем лечении, но это незначительная разница). Эта последовательность (которая была бы один узел в поисковом-графе) будет иметь следующие места отмечено:

 
1 2 3 
*a  
b *b 
c c *c 
d d *d 
e e *e 
    *f *f 
     *g 

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

Если у меня есть время, я отредактирую это, чтобы опубликовать код и выполнить алгоритм.


Re: результат полноты NP (в комментарии к Зак Томпсону). График, на котором мы ищем минимально затратный гамильтонов путь, имеет особую структуру. Например, обычно задача NP-полного гамильтонова пути может быть решена в O (n) раз с алгоритмом «перечислять все комбинации», где n - количество узлов на графике. Эта структура позволяет, однако, хотя на общем графике покрытие вершин сложно, на вашем графике может быть многочлен (даже линейный или квадратичный). Конечно, поскольку граф имеет множество узлов, например. n = 30, k = 8, у вас все еще может быть много вычислений впереди вас.

+0

Спасибо. Похоже, что мы получаем где-то В ответ на ваш вступительный вопрос ответ в любой форме - подмножество по подмножеству или все сразу в списке - будет одинаково приемлемым. Несмотря на явно обескураживающий вывод о том, что проблема в общих терминах NP-полная, За последние несколько дней я добился определенного прогресса. Однако сейчас мне нужно уехать в поездку в ближайшее время и не смогу больше писать по этому вопросу до 5 июля. Надеюсь, что один или оба из нас (или кто-то еще), возможно, взломали проблему. –

0

Я работал над этой проблемой в 2010 году, но тогда не смог найти решение. Несколько дней назад я еще раз взглянул на некоторые мои заметки с того времени и предположил, что я был очень близок к решению. Через несколько минут у меня был ключ.

Для рекапитализации: требование представляет собой строгий минимальный порядок изменения k-подмножеств строки s, так что LIFO (последний в первом случае) максимизируется. Я рассматриваю это как максимизированную «замену» в более ранних сообщениях.

Я вызываю алгоритм maxlifo (максимально LIFO) после ключевого требования. Он принимает два параметра, строку s, которые не должны содержать дублированных символов, и положительное целое число k, не превышающее размер s. Алгоритм является рекурсивным, т. Е. Maxlifo (s, k) использует вывод maxlifo (s, k-1) вплоть до k = 1. Результат возвращается как список.

Ниже я приводил неофициальное объяснение с примерами, используя строку «abcdefg» и различные значения k. Затем следует пример реализации в качестве процедуры Unicon. (Я не владею ни одним из наиболее часто используемых языков.)

Случай k = 1 тривиален - он возвращает элементы s в порядке от первого до последнего.

При к> 1, применяются следующие правила к выходу maxlifo (с, K-1):

(1) Для каждого элемента вывода maxlifo (с, к-1), список в строке k-подмножеств, построенных из этого элемента с каждым отсутствующим символом s по очереди. Порядок символов в подмножествах такой же, как в s.

(2) Работая со второй строки вниз, замените пустые «заполнители» для всех вхождений подмножеств, которые появляются в более ранней строке. Каждое k-подмножество s теперь появляется только один раз.

(3) В каждом непустом ряду отметьте начальным! каждое подмножество такое, что в следующей строке есть местозаполнитель в том же положении. Эта маркировка означает «первая». Точно одно подмножество будет помечено таким образом в каждой непустой строке.

(4) Удалите все строки, которые являются полностью пустыми (содержат только заполнители).

(5) В каждой оставшейся строке, кроме последней, отметьте окончательный! подмножество в позиции, соответствующем подмножеству, помеченному «первым» в следующей нижней строке. Эта маркировка означает «последняя».

Теперь мы можем перечислить окончательное maxlifo-упорядочение подмножеств. Каждая строка сверху вниз упорядочена и ее элементы добавлены в этот порядок в выходной список.

(6) В каждой строке сверху вниз:

(6.1) Удалить все пустые заполнители.

(6.2) Добавить в список результатов подмножество, обозначенное «первым» (начальное!) и удалите его из строки.

(6.3) Если в строке остались оставшиеся подмножества, либо крайнее левое, либо самое правое подмножество будет отмечено «последним» (final!). Если самое правое подмножество отмечено «последним», добавьте подмножества в выходной список в порядке слева направо, иначе в порядке справа налево.

(7) После обработки всех строк верните выходной список.

Пример использования maxlifo ("ABCDEFG", 2):

Стлб1 содержит вывод maxlifo ("ABCDEFG", 1). Строки Col2 содержат клики, сформированные с остальными персонажами с:

Col1 Col2 
---- ---------------------------- 
a  ab ac ad ae af ag 
b  ab bc bd be bf bg 
c  ac bc cd ce cf cg 
d  ad bd cd de df dg 
e  ae be ce de ef eg 
f  af bf cf df ef fg 
g  ag bg cg dg eg fg 

Пустые из подмножеств, которые появляются в более ранней строке:

a  ab ac ad ae af ag 
b   bc bd be bf bg 
c     cd ce cf cg 
d      de df dg 
e       ef eg 
f        fg 
g        

Отметьте «первый» подмножество в каждой строке (один с заготовкой под ним):

a  !ab ac ad ae af ag 
b   !bc bd be bf bg 
c    !cd ce cf cg 
d      !de df dg 
e       !ef eg 
f        !fg 
g        

Удалить все полностью пустые строки (только один в данном случае):

a  !ab ac ad ae af ag 
b   !bc bd be bf bg 
c    !cd ce cf cg 
d      !de df dg 
e       !ef eg 
f        !fg 

Отметьте «последнее» подмножество в каждой строке (одно с подмножеством «первым» под ним).

a  !ab ac! ad ae af ag 
b   !bc bd! be bf bg 
c    !cd ce! cf cg 
d      !de df! dg 
e       !ef eg! 
f        !fg 

Выход каждой строки в порядке описано выше: «первый», без опознавательных знаков, «наконец»:

           Ordered rows: 
a  !ab ac! ad ae af ag   ab ag af ae ad ac 
b   !bc bd! be bf bg   bc bg bf be bd 
c    !cd ce! cf cg   cd cg cf ce 
d      !de df! dg   de dg df 
e       !ef eg!   ef eg 
f        !fg   fg 

Выход: [AB аг аф ае объявления переменного тока до н.э. BG Б.Ф. быть шд кд CG ср ce df dg de ef, например fg]

Примеры для 3 < = k < = 6 приведены менее подробно. Пустые строки, удаленные на шаге 4, остаются на месте.

maxlifo ("ABCDEFG", 3):

             Ordered rows: 
ab  !abc abd abe abf abg!     abc abd abe abf abg 
ag   acg adg aeg! !afg     afg acg adg aeg 
af   acf adf! !aef       aef acf adf 
ae   ace! !ade        ade ace 
ad   !acd!          acd 
ac      
bc   !bcd bce bcf bcg!     bcd bce bcf bcg 
bg     bdg beg! !bfg     bfg bdg beg 
bf     bdf! !bef       bef bdf 
be     !bde!        bde 
bd         
cd     !cde cdf cdg!     cde cdf cdg 
cg      ceg! !cfg     cfg ceg 
cf      !cef!       cef 
ce        
de      !def deg!     def deg 
dg        !dfg!     dfg 
df        
ef        !efg     efg 
eg      
fg      

Выход: [аЬс абд Abe ABF ABG AFG ACG ADG AEG AEF ACF ADF ADE асе ДСА
BCD BCE BCF BCG BFG БДГ бек BEF BDF BDE CDE CDF CDG CFG CEG CEF защиту град ДФГ EFG]

maxlifo ("ABCDEFG", 4):

          Ordered rows: 
abc   !abcd abce! abcf abcg  abcd abcg abcf abce 
abd    !abde abdf! abdg  abde abdg abdf 
abe      !abef abeg!  abef abeg 
abf        !abfg!  abfg 
abg         
afg    acfg! adfg !aefg  aefg adfg acfg 
acg    !acdg aceg!    acdg aceg 
adg      !adeg!    adeg 
aeg         
aef    acef! !adef    adef acef 
acf    !acdf!     acdf 
adf         
ade    !acde!     acde 
ace         
acd         
bcd    !bcde bcdf! bcdg  bcde bcdg bcdf 
bce      !bcef bceg!  bcef bceg 
bcf        !bcfg!  bcfg 
bcg         
bfg      bdfg! !befg  befg bdfg 
bdg      !bdeg!    bdeg 
beg         
bef      !bdef!    bdef 
bdf         
bde         
cde      !cdef cdeg!  cdef cdeg 
cdf        !cdfg!  cdfg 
cdg         
cfg        !cefg!  cefg 
ceg         
cef         
def        !defg  defg 
deg       
dfg       
efg       

Выход: [ABCD abcg abcf ABCE ABDE abdg abdf ABEF abeg AbfG aefg ADFG acfg ACDG aceg ADEG АДЕФ acef ACDF AcdE BCDE bcdg BCDF BCEF bceg bcfg befg bdfg bdeg bdef CDEF CDEG cdfg cefg DEFG]

maxlifo (» ABCDEFG», 5):

          Ordered rows: 
abcd  !abcde abcdf abcdg!   abcde abcdf abcdg 
abcg    abceg! !abcfg   abcfg abceg 
abcf    !abcef!     abcef 
abce         
abde    !abdef abdeg!   abdef abdeg 
abdg      !abdfg!   abdfg 
abdf       
abef      !abefg!   abefg 
abeg       
abfg       
aefg    acefg! !adefg   adefg acefg 
adfg    !acdfg!     acdfg 
acfg       
acdg    !acdeg!     acdeg 
aceg       
adeg       
adef    !acdef!     acdef 
acef       
acdf       
acde       
bcde    !bcdef bcdeg!   bcdef bcdeg 
bcdg      !bcdfg!   bcdfg 
bcdf       
bcef      !bcefg!   bcefg 
bceg       
bcfg       
befg      !bdefg!   bdefg 
bdfg       
bdeg       
bdef       
cdef      !cdefg   cdefg 
cdeg       
cdfg       
cefg       
defg       

Выход: [ABCDE ABCDF abcdg abcfg abceg abcef abdef abdeg abdfg abefg adefg acefg acdfg acdeg ACDEF BCDEF bcdeg bcdfg bcefg bdefg cdefg]

maxlifo ("АБВГДЕЖ", 6):

          Ordered rows: 
abcde    !abcdef abcdeg!  abcdef abcdeg 
abcdf      !abcdfg!  abcdfg 
abcdg        
abcfg      !abcefg!  abcefg 
abceg        
abcef        
abdef      !abdefg!  abdefg 
abdeg        
abdfg        
abefg        
adefg        
acefg      !acdefg!  acdefg 
acdfg        
acdeg        
acdef        
bcdef      !bcdefg  bcdefg 
bcdeg        
bcdfg        
bcefg        
bdefg        
cdefg       

Выход: [ABCDEF abcdeg abcdfg abcefg abdefg acdefg BCDEFG]

Юникон реализация:

procedure maxlifo(s:string, k:integer) 
# A solution to my combinatorics problem from 2010. 
# Return a list of the k subsets of the characters of a string s 
# in a minimal change order such that last-in first-out is maximised. 
# String s must not contain duplicate characters and in the present 
# implementation must not contain "!", which is used as a marker. 

    local ch, cand, Hit, inps, i, j, K, L, Outp, R, S 

    # Errors 
    if *cset(s) ~= *s then 
    stop("Duplicate characters in set in maxlifo(", s, ", ", k, ")") 
    if find("!", s) then 
    stop("Illegal character in set in maxlifo(", s, ", ", k, ")") 
    if k > *s then 
    stop("Subset size larger than set size in maxlifo(", s, ", ", k, ")") 

    # Special cases 
    if k = 0 then return [] 
    if k = *s then return [s] 

    Outp := [] 
    if k = 1 then { 
    every put(Outp, !s) 
    return Outp 
    } 

    # Default case 
    S := set() 
    K := [] 

    # Build cliques from output of maxlifo(s, k-1) with the remaining 
    # characters in s, substituting empty strings as placeholders for 
    # subsets already listed. 
    every inps := !maxlifo(s, k-1) do { 
    R := [] 
    every ch := !s do 
     if not find(ch, inps) then { 
     cand := reorder(inps ++ ch, s) 
     if member(S, cand) then cand := "" else insert(S, cand) 
     put(R, cand) 
     } 
    put(K, R) 
    } 

    # Mark ‘first’ subset in each row with initial "!" 
    every i := 1 to *K - 1 do { 
    every j := 1 to *K[i] do 
     if K[i, j] ~== "" & K[i+1, j] == "" then { 
     K[i, j] := "!" || K[i, j] 
     break 
     } 
    } 

    # Remove rows containing only placeholders 
    every i := *K to 1 by -1 do { 
    every if !K[i] ~== "" then break next 
    delete(K, i) 
    } 

    # Mark ‘last’ subset in each row with final "!" 
    every i := 1 to *K - 1 do 
    every j := 1 to *K[i] do 
     if K[i+1, j][1] == "!" then { 
     K[i, j] ||:= "!" 
     break 
     } 

    # Build output list 
    every R := !K do { 

    # Delete placeholders from row (no longer needed and in the way) 
    every j := *R to 1 by -1 do if R[j] == "" then delete(R, j) 

    # Handle ‘first’ subset and remove from row 
    # N.B. ‘First’ subset will be leftmost or rightmost in row 
    if R[1][1] == "!" then 
     put(Outp, trim(get(R), '!', 0)) 
     else put(Outp, trim(pull(R), '!', 0)) 

    # Handle any remaining subsets, ‘last’ subset last, stripping '!' markers 
    # N.B. ‘Last’ subset will be leftmost or rightmost in row after removal 
    # of ‘first’ subset. 
    if R[-1][-1] == "!" then while put(Outp, trim(get(R), '!', 0)) else 
     while put(Outp, trim(pull(R), '!', 0)) 
    } 

    return Outp 

end 


procedure reorder(cs:cset, s:string) 
# Reorder cset cs according to string s 

    local r 
    # If no s, return set in alphabetical order 
    if /s then return string(cs) 

    r := "" 
    s ? while tab(upto(cs)) do r ||:= move(1) 
    return r 

end 
Смежные вопросы