Сформувати всі перестановки списку без суміжних рівних елементів


87

Коли ми сортуємо список, наприклад

a = [1,2,3,3,2,2,1]
sorted(a) => [1, 1, 2, 2, 2, 3, 3]

рівні елементи завжди суміжні в отриманому списку.

Як я можу досягти протилежного завдання - перемішати список так, щоб рівні елементи ніколи (або якомога рідше) не були поруч?

Наприклад, для наведеного списку одним із можливих рішень є

p = [1,3,2,3,2,1,2]

Більш формально, отримуючи перелік a, генеруйте його перестановку, pяка мінімізує кількість пар p[i]==p[i+1].

Оскільки списки великі, генерувати та фільтрувати всі перестановки не можна.

Бонусне питання: як ефективно генерувати всі такі перестановки?

Це код, який я використовую для тестування рішень: https://gist.github.com/gebrkn/9f550094b3d24a35aebd

UPD: Вибір переможця тут був важким вибором, тому що багато людей публікували чудові відповіді. @VincentvanderWeele , @David Eisenstat , @Coady , @ enrico.bacis та @srgerg надали функції, які бездоганно генерують найкращу можливу перестановку. @tobias_k і Девід також відповіли на бонусне запитання (генерувати всі перестановки). Додаткові вказівки Девіду на підтвердження правильності.

Код @VincentvanderWeele видається найшвидшим.


1
Тобто ви дбаєте лише про рівність ? щось на зразок [1, 2, 1, 3, 1, 4, 1, 5]точно таке ж, як [1, 3, 1, 2, 1, 4, 1, 5]за вашим критерієм?
Бакуріу

1
Не може існувати "ефективний" алгоритм. Візьміть список, як [1, 1, 1, ..., 2, 3, 4, ..., N]з 2Nелементами. Ви можете поставити число n > 1між кожною парою послідовних, 1щоб отримати хорошу перестановку. Потім ви переставляєте N/2елементи і отримуєте всі допустимі перестановки (тобто жодна не є поганою, але може бути і більше). Кількість таких перестановок дорівнює O (N ^ 2), тому ви не можете зробити краще, ніж O (N ^ 2). Все ж краще, ніж O (N ^ 3) наївного підходу.
Бакуріу

6
@Bakuriu: Дві речі: (1) щоб було зрозуміло, ваш приклад показує, що не може бути ефективного алгоритму для бонусного питання . (2) Перерахувати всі оптимальні рішення для вашого прикладу - O ((N / 2)!), Що набагато гірше, ніж O (N ^ 2) (тобто ваш приклад набагато сильніший, ніж ви зрозуміли :-)
j_random_hacker

11
@msw: Я роблю веб-сайт, і там є рядок із рекламними блоками від різних постачальників. Я хочу розташувати їх так, щоб жодні блоки від одного постачальника не стояли поруч.
georg

2
Я б не сказав, що це "навіть не близько до дубліката", але передбачуваний дублікат - це інше питання, оскільки враховується відстань між однаковими елементами. Люди, які проголосували за закриття після коментаря WhyCry: будь ласка, звертайте більше уваги в майбутньому.
Девід Айзенстат

Відповіді:


30

Це відповідає нині неповному псевдокоду Тізера. Ідея полягає в тому, щоб взяти найпоширеніший з решти типів предметів, якщо тільки він не був взятий. (Див. Також реалізацію цього алгоритму Коаді.)

import collections
import heapq


class Sentinel:
    pass


def david_eisenstat(lst):
    counts = collections.Counter(lst)
    heap = [(-count, key) for key, count in counts.items()]
    heapq.heapify(heap)
    output = []
    last = Sentinel()
    while heap:
        minuscount1, key1 = heapq.heappop(heap)
        if key1 != last or not heap:
            last = key1
            minuscount1 += 1
        else:
            minuscount2, key2 = heapq.heappop(heap)
            last = key2
            minuscount2 += 1
            if minuscount2 != 0:
                heapq.heappush(heap, (minuscount2, key2))
        output.append(last)
        if minuscount1 != 0:
            heapq.heappush(heap, (minuscount1, key1))
    return output

Доказ правильності

Для двох типів елементів, з підрахунками k1 і k2, оптимальним рішенням є дефекти k2 - k1 - 1, якщо k1 <k2, 0 дефектів, якщо k1 = k2, і k1 - k2 - 1 дефекти, якщо k1> k2. Випадок = очевидний. Інші симетричні; кожен екземпляр елемента меншості запобігає щонайбільше двом дефектам із загальної кількості k1 + k2 - 1.

Цей жадібний алгоритм повертає оптимальні рішення за наступною логікою. Ми називаємо префікс (часткове рішення) безпечним, якщо він поширюється на оптимальне рішення. Очевидно, що порожній префікс безпечний, і якщо безпечний префікс - це ціле рішення, то це рішення є оптимальним. Досить індуктивно показати, що кожен жадібний крок підтримує безпеку.

Єдиний спосіб, яким жадібний крок вводить дефект, полягає в тому, що залишається лише один тип предмета, і в цьому випадку існує лише один спосіб продовжити, і цей шлях безпечний. В іншому випадку нехай P є (безпечним) префіксом безпосередньо перед розглянутим кроком, нехай P 'є префіксом відразу після, і нехай S є оптимальним рішенням, що поширюється на P. Якщо S також розширює P', то ми закінчили. В іншому випадку нехай P '= Px і S = PQ і Q = yQ', де x і y - елементи, а Q і Q '- послідовності.

Нехай спочатку P не закінчується на y. За вибором алгоритму, x принаймні так само часто зустрічається в Q, як y. Розглянемо максимальні підрядки Q, що містять лише x та y. Якщо в першому підрядку є принаймні стільки знаків х, скільки у, тоді його можна переписати, не вносячи додаткових дефектів, починаючи з х. Якщо в першому підрядку більше y, ніж x, то в якомусь іншому підрядку більше x, ніж y, і ми можемо переписати ці підрядки без додаткових дефектів, щоб x йшов першим. В обох випадках ми знаходимо оптимальне рішення Т, що розширює Р ', за потреби.

Нехай тепер P закінчується на y. Модифікуйте Q, перемістивши перше входження x вперед. Роблячи це, ми вводимо щонайбільше один дефект (де раніше був x) і усуваємо один дефект (yy).

Генерація всіх рішень

Це відповідь tobias_k плюс ефективні тести для виявлення, коли вибір, який зараз розглядається, якимось чином обмежений у всьому світі. Асимптотичний час роботи є оптимальним, оскільки накладні витрати на генерацію складають порядок довжини виходу. Найгірша затримка, на жаль, квадратична; його можна було б звести до лінійного (оптимального) з кращими структурами даних.

from collections import Counter
from itertools import permutations
from operator import itemgetter
from random import randrange


def get_mode(count):
    return max(count.items(), key=itemgetter(1))[0]


def enum2(prefix, x, count, total, mode):
    prefix.append(x)
    count_x = count[x]
    if count_x == 1:
        del count[x]
    else:
        count[x] = count_x - 1
    yield from enum1(prefix, count, total - 1, mode)
    count[x] = count_x
    del prefix[-1]


def enum1(prefix, count, total, mode):
    if total == 0:
        yield tuple(prefix)
        return
    if count[mode] * 2 - 1 >= total and [mode] != prefix[-1:]:
        yield from enum2(prefix, mode, count, total, mode)
    else:
        defect_okay = not prefix or count[prefix[-1]] * 2 > total
        mode = get_mode(count)
        for x in list(count.keys()):
            if defect_okay or [x] != prefix[-1:]:
                yield from enum2(prefix, x, count, total, mode)


def enum(seq):
    count = Counter(seq)
    if count:
        yield from enum1([], count, sum(count.values()), get_mode(count))
    else:
        yield ()


def defects(lst):
    return sum(lst[i - 1] == lst[i] for i in range(1, len(lst)))


def test(lst):
    perms = set(permutations(lst))
    opt = min(map(defects, perms))
    slow = {perm for perm in perms if defects(perm) == opt}
    fast = set(enum(lst))
    print(lst, fast, slow)
    assert slow == fast


for r in range(10000):
    test([randrange(3) for i in range(randrange(6))])

23

Псевдокод:

  1. Сортувати список
  2. Повторіть першу половину відсортованого списку та заповніть усі парні індекси списку результатів
  3. Прокрутіть другу половину відсортованого списку та заповніть усі непарні індекси списку результатів

Ви матимете лише p[i]==p[i+1]якщо більше половини вхідних даних складається з одного і того ж елемента, і в цьому випадку немає іншого вибору, ніж розміщення одного і того ж елемента в послідовних місцях (за принципом прорізу).


Як зазначалося в коментарях, цей підхід може мати занадто багато конфліктів, якщо один із елементів виникає принаймні n/2разів (або n/2+1для непарних n; це узагальнює (n+1)/2)для як для парних, так і для непарних). Таких елементів є щонайбільше два, і якщо їх два, алгоритм працює чудово. Проблемним є лише випадок, коли є один елемент, який трапляється принаймні половину часу. Ми можемо просто вирішити цю проблему, знайшовши елемент і впоравшись з ним першим.

Я недостатньо знаю про python, щоб правильно це написати, тому я дозволив скопіювати реалізацію OP попередньої версії з github:

# Sort the list
a = sorted(lst)

# Put the element occurring more than half of the times in front (if needed)
n = len(a)
m = (n + 1) // 2
for i in range(n - m + 1):
    if a[i] == a[i + m - 1]:
        a = a[i:] + a[:i]
        break

result = [None] * n

# Loop over the first half of the sorted list and fill all even indices of the result list
for i, elt in enumerate(a[:m]):
    result[2*i] = elt

# Loop over the second half of the sorted list and fill all odd indices of the result list
for i, elt in enumerate(a[m:]):
    result[2*i+1] = elt

return result

Наскільки я розумію, це те, що робить @jojo - не завжди оптимально.
georg

10
Це не вдається для [0, 1, 1]або [0, 0, 1], залежно від того, використовуєте ви індекси на основі 0 або 1.
flornquake

@georg Справді, це той самий підхід, що і в моїй відповіді. (Зверніть увагу, що Хостер відповів переді мною!). Однак у моєму коді кроки 2. та 3. поєднуються, що оптимізує ефективність.
jojo

3
@flornquake Хороший улов! Я боюся, це стара-стара помилка, яка виникла за одним. Отже, такий підхід не є оптимальним, оскільки може мати 1 конфлікт занадто багато.
Вінсент ван дер Віл

1
@Heuster: усі вогні зелені! "0 несправностей".
georg

10

Вже наведений алгоритм прийняття найпоширенішого предмета, що залишився, а не попереднього, є правильним. Ось проста реалізація, яка оптимально використовує купу для відстеження найпоширеніших.

import collections, heapq
def nonadjacent(keys):
    heap = [(-count, key) for key, count in collections.Counter(a).items()]
    heapq.heapify(heap)
    count, key = 0, None
    while heap:
        count, key = heapq.heapreplace(heap, (count, key)) if count else heapq.heappop(heap)
        yield key
        count += 1
    for index in xrange(-count):
        yield key

>>> a = [1,2,3,3,2,2,1]
>>> list(nonadjacent(a))
[2, 1, 2, 3, 1, 2, 3]

Хороший приклад того, як НЕ писати алгоритми на Python. Це просто, але потрібно 30 хвилин, щоб просто засвоїти синтаксис.
alex904

8

Ви можете генерувати всі "ідеально несортовані" перестановки (які не мають двох рівних елементів у сусідніх позиціях), використовуючи алгоритм рекурсивного зворотного відстеження. Насправді, єдина відмінність від генерації всіх перестановок полягає в тому, що ви відстежуєте останнє число і відповідно виключаєте деякі рішення:

def unsort(lst, last=None):
    if lst:
        for i, e in enumerate(lst):
            if e != last:
                for perm in unsort(lst[:i] + lst[i+1:], e):
                    yield [e] + perm
    else:
        yield []

Зверніть увагу, що в цій формі функція не дуже ефективна, оскільки створює безліч підсписків. Крім того, ми можемо пришвидшити це, переглянувши спочатку найбільш обмежені числа (ті, що мають найбільший рахунок). Ось набагато ефективніша версія, використовуючи лише countsцифри.

def unsort_generator(lst, sort=False):
    counts = collections.Counter(lst)
    def unsort_inner(remaining, last=None):
        if remaining > 0:
            # most-constrained first, or sorted for pretty-printing?
            items = sorted(counts.items()) if sort else counts.most_common()
            for n, c in items:
                if n != last and c > 0:
                    counts[n] -= 1   # update counts
                    for perm in unsort_inner(remaining - 1, n):
                        yield [n] + perm
                    counts[n] += 1   # revert counts
        else:
            yield []
    return unsort_inner(len(lst))

Ви можете використовувати це для створення просто nextідеальної перестановки або listутримання всіх. Але зауважте, що якщо немає ідеально несортованої перестановки, то цей генератор, отже, не дасть результатів.

>>> lst = [1,2,3,3,2,2,1]
>>> next(unsort_generator(lst))
[2, 1, 2, 3, 1, 2, 3]
>>> list(unsort_generator(lst, sort=True))
[[1, 2, 1, 2, 3, 2, 3], 
 ... 36 more ...
 [3, 2, 3, 2, 1, 2, 1]]
>>> next(unsort_generator([1,1,1]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Щоб обійти цю проблему, ви можете використовувати це разом з одним із алгоритмів, запропонованих в інших відповідях, як запасний варіант. Це гарантуватиме повернення ідеально несортованої перестановки, якщо така є, або хорошого наближення в іншому випадку.

def unsort_safe(lst):
    try:
        return next(unsort_generator(lst))
    except StopIteration:
        return unsort_fallback(lst)

Для цього використовується пам’ять O (N ^ 2) ... для кожного елемента в перестановці, для якої ви робите копію списку для рекурсивного виклику. Крім того, будучи рекурсивним, він виходить з ладу з "малою" довжиною.
Бакуріу

@Bakuriu Погодився, це те, що я мав на увазі під "не оптимізованим для ефективності" ... хоча я повинен визнати, що я не крім простору O (n ^ 2), але ти маєш рацію ... Я спробую це вдосконалити .
tobias_k

O (N ^ 2) завжди за спиною, коли у вас є воскресіння, таке як T(n+1) = something + T(n).
Бакуріу

@tobias_k: чи можете ви опублікувати функцію лише для однієї завивки для тестування?
georg

@georg Звичайно: next(unsort2(collections.Counter(a)));-) Але оскільки цей альго генерує всі можливості, чому б не перевірити їх усі? Її лише 38 для цього 7 елементарного списку тестів.
tobias_k

5

У python ви можете зробити наступне.

Вважаючи, що у вас є відсортований список l, ви можете зробити:

length = len(l)
odd_ind = length%2
odd_half = (length - odd_ind)/2
for i in range(odd_half)[::2]:
    my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

Це просто операції на місці, і тому вони повинні бути досить швидкими ( O(N)). Зауважте, що ви перейдете з l[i] == l[i+1]на, l[i] == l[i+2]тож порядок, який ви отримаєте, є чим завгодно, але випадковим, але, наскільки я розумію питання, ви шукаєте не випадковість.

Ідея полягає в тому, щоб розділити відсортований список посередині, а потім обміняти кожен інший елемент у двох частинах.

Бо l= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]це призводить доl = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]

Метод не дозволяє позбутися всього, l[i] == l[i + 1]як тільки кількість одного елемента перевищує або дорівнює половині довжини списку.

Незважаючи на те, що вищезазначене працює чудово, доки кількість найпоширеніших елементів менше половини розміру списку, наступна функція також обробляє граничні випадки (відомий окремий випуск), коли кожен інший елемент, що починається з перший повинен бути найпоширенішим:

def no_adjacent(my_list):
    my_list.sort()
    length = len(my_list)
    odd_ind = length%2
    odd_half = (length - odd_ind)/2
    for i in range(odd_half)[::2]:
        my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

    #this is just for the limit case where the abundance of the most frequent is half of the list length
    if max([my_list.count(val) for val in set(my_list)]) + 1 - odd_ind > odd_half:
        max_val = my_list[0]
        max_count = my_list.count(max_val)
        for val in set(my_list):
            if my_list.count(val) > max_count:
               max_val = val
               max_count = my_list.count(max_val)
        while max_val in my_list:
            my_list.remove(max_val)
        out = [max_val]
        max_count -= 1
        for val in my_list:
            out.append(val)
            if max_count:
                out.append(max_val)
                max_count -= 1
        if max_count:
            print 'this is not working'
            return my_list
            #raise Exception('not possible')
        return out
    else:
        return my_list

Дякую! Це не вдається [3, 2, 1, 2, 1, 3, 2](повертається [2, 1, 3, 1, 2, 2, 3], має бути (3, 2, 1, 2, 1, 3, 2)) - див. Суть
georg

@georg вибачте, мій поганий я забув a +1. Повторіть спробу зараз.
jojo

Проблеми все ще, [1, 3, 3, 3, 3, 1, 1]=>[3, 1, 3, 3, 1, 3, 1]
georg

@georg, як я вже зазначав, він працює до тих пір, поки найбільш поширений присутній менше половини довжини списку, що не є випадком у цьому прикладі.
jojo

@georg Отже, я додав частину, яка обробляє помилку "по одному". Ця частина не особливо швидка (приблизно така ж, як алгоритм, запропонований Тейзером), хоча вона буде виконуватися лише у рідкісних випадках.
jojo

5

Ось хороший алгоритм:

  1. Перш за все розраховуйте всі цифри, як часто вони трапляються. Розмістіть відповідь на карті.

  2. відсортуйте цю карту так, щоб числа, які найчастіше трапляються, були на першому місці.

  3. Перше число вашої відповіді - це перше число на відсортованій карті.

  4. Курортна карта з першою зараз на одну меншу.

Якщо ви хочете підвищити ефективність, шукайте шляхи підвищення ефективності етапу сортування.


Так, це зробив @tobias_k. Здається, це працює добре!
georg

@georg Це трохи інакше ... Я використовую лічильник лише для зменшення складності простору, але я не перевіряю цифри в якомусь конкретному порядку (думав, що це може бути ще одним прискоренням). Різне в тому, що моє рішення завжди дає всі „ідеальні” перестановки, якщо такі є , тоді як це повинно дати найкраще (?) Рішення (ідеальне чи ні).
tobias_k

3
Цей псевдокод не зовсім правильний; якщо кількість пунктів складає 5 x, 2 y, 2 z, то це зайво складатиме x. Дивіться мою відповідь на виправлення.
Девід Айзенстат

1
Домовились. Наприклад, для [1,1,1,2,3] це дасть напр. [1,1,2,1,3] замість [1,2,1,3,1].
tobias_k

Крок 3 насправді є непродуктивним. Якщо число є загальним (принаймні на два записи більше, ніж наступне найбільш часто зустрічається число), крок 3 використовуватиме це число двічі поспіль, без будь-якого обґрунтування.
MSalters

5

Відповідь на бонусне запитання: це алгоритм, який знаходить усі перестановки набору, де жоден сусідній елемент не може бути ідентичним. Я вважаю, що це найефективніший концептуально алгоритм (хоча інші можуть бути швидшими на практиці, оскільки вони перекладаються в простіший код). Він не використовує грубу силу, він лише генерує унікальні перестановки, і шляхи, що не ведуть до рішень, обриваються в самий ранній момент.

Я буду використовувати термін "достатній елемент" для елемента в наборі, який зустрічається частіше, ніж усі інші елементи разом узятих, а термін "достаток" для кількості рясних елементів мінус кількість інших елементів.
наприклад, множина abacне має елемента в достатку, множини abacaі aabcaaмають aяк елемент в достатку, так і достаток 1 і 2 відповідно.

  1. Почніть з такого набору, як:

aaabbcd

  1. Відокремте перші випадки від повторень:

перші: abcd
повторюється: aab

  1. Знайдіть рясний елемент у повторах, якщо такі є, і обчисліть кількість:

рясний елемент:
достаток: 1

  1. Сформувати всі перестановки перших, де кількість елементів після елемента в достатній мірі не менше, ніж кількість: (тому у прикладі "a" не може бути останнім)

abcd, abdc, acbd, acdb, adbc, adcb, bacd, badc, bcad , bcda, bdac ,
bdca, cabd, cadb, cbad , cbda, cdab , cdba, dabc, dacb , abac, dbca , dcab, dcba

  1. Для кожної перестановки вставте набір повторюваних символів по одному, дотримуючись цих правил:

5.1. Якщо достатність множини перевищує кількість елементів після останнього появи елемента, що міститься в перестановці, перейдіть до наступної перестановки.
наприклад, коли перестановка поки що є abc, набір з достатньою кількістю елемента aможна вставити, лише якщо достатність становить 2 або менше, тож aaaabcце нормально, aaaaabcчи не так.

5.2. Виберіть елемент із набору, останнє виникнення якого в перестановці приходить першим.
наприклад, коли перестановка поки що є, abcbaа set є ab, виберітьb

5.3. Вставте вибраний елемент принаймні на 2 позиції праворуч від останньої дії у перестановці.
наприклад, при вставці bв перестановку babca, результати є babcbaіbabcab

5.4. Повторіть крок 5 з кожною отриманою перестановкою та рештою набору.

EXAMPLE:
set = abcaba
firsts = abc
repeats = aab

perm3  set    select perm4  set    select perm5  set    select perm6

abc    aab    a      abac   ab     b      ababc  a      a      ababac  
                                                               ababca  
                                          abacb  a      a      abacab  
                                                               abacba  
                     abca   ab     b      abcba  a      -
                                          abcab  a      a      abcaba  
acb    aab    a      acab   ab     a      acaba  b      b      acabab  
                     acba   ab     b      acbab  a      a      acbaba  
bac    aab    b      babc   aa     a      babac  a      a      babaca  
                                          babca  a      -
                     bacb   aa     a      bacab  a      a      bacaba  
                                          bacba  a      -  
bca    aab    -
cab    aab    a      caba   ab     b      cabab  a      a      cababa  
cba    aab    -

Цей алгоритм генерує унікальні перестановки. Якщо ви хочете дізнатися загальну кількість перестановок (де abaвраховується двічі, оскільки ви можете перемикати a), помножте кількість унікальних перестановок на коефіцієнт:

F = N 1 ! * N 2 ! * ... * П н !

де N - кількість випадків кожного елемента в наборі. Для набору abcdabcabaце буде 4! * 3! * 2! * 1! або 288, що демонструє, наскільки неефективним є алгоритм, який генерує всі перестановки замість лише унікальних. Щоб перерахувати всі перестановки в цьому випадку, просто перелічіть унікальні перестановки 288 разів :-)

Нижче наведено (досить незграбну) реалізацію в Javascript; Я підозрюю, що така мова, як Python, може бути більш підходящою для такого роду. Запустіть фрагмент коду для обчислення відокремлених перестановок "абракадабри".

// FIND ALL PERMUTATONS OF A SET WHERE NO ADJACENT ELEMENTS ARE IDENTICAL
function seperatedPermutations(set) {
    var unique = 0, factor = 1, firsts = [], repeats = [], abund;

    seperateRepeats(set);
    abund = abundance(repeats);
    permutateFirsts([], firsts);
    alert("Permutations of [" + set + "]\ntotal: " + (unique * factor) + ", unique: " + unique);

    // SEPERATE REPEATED CHARACTERS AND CALCULATE TOTAL/UNIQUE RATIO
    function seperateRepeats(set) {
        for (var i = 0; i < set.length; i++) {
            var first, elem = set[i];
            if (firsts.indexOf(elem) == -1) firsts.push(elem)
            else if ((first = repeats.indexOf(elem)) == -1) {
                repeats.push(elem);
                factor *= 2;
            } else {
                repeats.splice(first, 0, elem);
                factor *= repeats.lastIndexOf(elem) - first + 2;
            }
        }
    }

    // FIND ALL PERMUTATIONS OF THE FIRSTS USING RECURSION
    function permutateFirsts(perm, set) {
        if (set.length > 0) {
            for (var i = 0; i < set.length; i++) {
                var s = set.slice();
                var e = s.splice(i, 1);
                if (e[0] == abund.elem && s.length < abund.num) continue;
                permutateFirsts(perm.concat(e), s, abund);
            }
        }
        else if (repeats.length > 0) {
            insertRepeats(perm, repeats);
        }
        else {
            document.write(perm + "<BR>");
            ++unique;
        }
    }

    // INSERT REPEATS INTO THE PERMUTATIONS USING RECURSION
    function insertRepeats(perm, set) {
        var abund = abundance(set);
        if (perm.length - perm.lastIndexOf(abund.elem) > abund.num) {
            var sel = selectElement(perm, set);
            var s = set.slice();
            var elem = s.splice(sel, 1)[0];
            for (var i = perm.lastIndexOf(elem) + 2; i <= perm.length; i++) {
                var p = perm.slice();
                p.splice(i, 0, elem);
                if (set.length == 1) {
                    document.write(p + "<BR>");
                    ++unique;
                } else {
                    insertRepeats(p, s);
                }
            }
        }
    }

    // SELECT THE ELEMENT FROM THE SET WHOSE LAST OCCURANCE IN THE PERMUTATION COMES FIRST
    function selectElement(perm, set) {
        var sel, pos, min = perm.length;
        for (var i = 0; i < set.length; i++) {
            pos = perm.lastIndexOf(set[i]);
            if (pos < min) {
                min = pos;
                sel = i;
            }
        }
        return(sel);
    }

    // FIND ABUNDANT ELEMENT AND ABUNDANCE NUMBER
    function abundance(set) {
        if (set.length == 0) return ({elem: null, num: 0});
        var elem = set[0], max = 1, num = 1;
        for (var i = 1; i < set.length; i++) {
            if (set[i] != set[i - 1]) num = 1
            else if (++num > max) {
                max = num;
                elem = set[i];
            }
        }
        return ({elem: elem, num: 2 * max - set.length});
    }
}

seperatedPermutations(["a","b","r","a","c","a","d","a","b","r","a"]);


1
спасибі за це! побачить, чи можна це трохи скоротити у javascript.
stt106

4

Ідея полягає в тому, щоб відсортувати елементи від найпоширеніших до найменш загальних, взяти найпоширеніші, зменшити кількість і повернути його назад у список, зберігаючи спадання (але уникаючи розміщення останнього використаного елемента першим, щоб запобігти повторенням, коли це можливо) .

Це можна реалізувати за допомогою Counterта bisect:

from collections import Counter
from bisect import bisect

def unsorted(lst):
    # use elements (-count, item) so bisect will put biggest counts first
    items = [(-count, item) for item, count in Counter(lst).most_common()]
    result = []

    while items:
        count, item = items.pop(0)
        result.append(item)
        if count != -1:
            element = (count + 1, item)
            index = bisect(items, element)
            # prevent insertion in position 0 if there are other items
            items.insert(index or (1 if items else 0), element)

    return result

Приклад

>>> print unsorted([1, 1, 1, 2, 3, 3, 2, 2, 1])
[1, 2, 1, 2, 1, 3, 1, 2, 3]

>>> print unsorted([1, 2, 3, 2, 3, 2, 2])
[2, 3, 2, 1, 2, 3, 2]

Це не вдається, наприклад: [1, 1, 2, 3]там, де є рішення, такі як [1, 2, 1, 3].
Бакуріу

Так, я щойно зрозумів, що, вибачте
enrico.bacis

Дякую! Це не завжди дає оптимальний результат, наприклад, якщо [1, 2, 3, 2, 3, 2, 2]він повертається [2, 3, 1, 2, 3, 2, 2](1 помилка), тоді як ідеал є (2, 1, 2, 3, 2, 3, 2)) - див. Суть.
georg

@georg Правда, приємний улов, я оновив його, дотримуючись простого принципу, який він використовує.
enrico.bacis

@ enrico.bacis: дякую! Нова версія працює бездоганно. Я оновив суть. Шкода, що я більше не можу вас проголосувати.
georg

2
  1. Сортувати список.
  2. Створіть "найкраще перемішування" списку, використовуючи цей алгоритм

Він дасть мінімум предметів зі списку в їх початковому місці (за значенням елемента), тому він спробує, наприклад, поставити одиниці 1, 2 і 3 подалі від їх відсортованих позицій.


Я пробував, best_shuffleі це генерувало [1,1,1,2,3] -> [3, 1, 2, 1, 1]- не ідеально!
georg

2

Почніть з відсортованого списку довжини n. Нехай m = n / 2. Візьмемо значення 0, потім m, потім 1, потім m + 1, потім 2, потім m + 2 тощо. Якщо у вас більше ніж половина чисел однакові, ви ніколи не отримаєте еквівалентні значення в послідовному порядку.


Дякую за ідею. Я думаю, що це те, що реалізував @Heuster.
georg

2

Будь ласка, вибачте мою відповідь у стилі "я теж", але чи не можна було спростити відповідь Коді до цього?

from collections import Counter
from heapq import heapify, heappop, heapreplace
from itertools import repeat

def srgerg(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        yield val
    yield from repeat(val, -freq)

Редагувати: Ось версія python 2, яка повертає список:

def srgergpy2(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    result = list()
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        result.append(val)
    result.extend(repeat(val, -freq))
    return result

Так, це, здається, працює нормально (за винятком того, що я на py2, і функція повинна повернути список).
georg

@georg Добре, я додав версію python 2, яка повертає список.
srgerg

2
  1. Кількість разів, коли кожне значення відображається
  2. Виберіть значення в порядку від найчастішого до найменшого
  3. Додайте вибране значення до кінцевого результату, щоразу збільшуючи індекс на 2
  4. Скиньте індекс до 1, якщо індекс виходить за межі
from heapq import heapify, heappop
def distribute(values):
    counts = defaultdict(int)
    for value in values:
        counts[value] += 1
    counts = [(-count, key) for key, count in counts.iteritems()]
    heapify(counts)
    index = 0
    length = len(values)
    distributed = [None] * length
    while counts:
        count, value = heappop(counts)
        for _ in xrange(-count):
            distributed[index] = value
            index = index + 2 if index + 2 < length else 1
    return distributed
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.