Genera tutte le permutazioni di un elenco senza elementi uguali adiacenti


87

Quando ordiniamo una lista, come

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

gli elementi uguali sono sempre adiacenti nell'elenco risultante.

Come posso ottenere il compito opposto: mescolare l'elenco in modo che gli elementi uguali non siano mai (o il più raramente possibile) adiacenti?

Ad esempio, per l'elenco precedente una delle possibili soluzioni è

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

Più formalmente, dato un elenco a, generane una permutazione pche minimizza il numero di coppie p[i]==p[i+1].

Poiché gli elenchi sono grandi, la generazione e il filtraggio di tutte le permutazioni non è un'opzione.

Domanda bonus: come generare tutte queste permutazioni in modo efficiente?

Questo è il codice che sto utilizzando per testare le soluzioni: https://gist.github.com/gebrkn/9f550094b3d24a35aebd

UPD: Scegliere un vincitore qui è stata una scelta difficile, perché molte persone hanno pubblicato risposte eccellenti. @VincentvanderWeele , @David Eisenstat , @Coady , @ enrico.bacis e @srgerg hanno fornito funzioni che generano la migliore permutazione possibile in modo impeccabile. Anche @tobias_k e David hanno risposto alla domanda bonus (genera tutte le permutazioni). Punti aggiuntivi a David per la prova di correttezza.

Il codice di @VincentvanderWeele sembra essere il più veloce.


1
Quindi ti interessa solo l' uguaglianza ? qualcosa di simile [1, 2, 1, 3, 1, 4, 1, 5]è esattamente lo stesso [1, 3, 1, 2, 1, 4, 1, 5]del tuo criterio?
Bakuriu

1
Non può esserci un algoritmo "efficiente". Prendi una lista come [1, 1, 1, ..., 2, 3, 4, ..., N]con gli 2Nelementi. Puoi mettere un numero n > 1tra ogni coppia di consecutivi 1per ottenere una buona permutazione. Quindi permuti gli N/2elementi e ottieni tutte le permutazioni valide (il che significa che nessuna è cattiva, ma potrebbero essercene di più). Il numero di tali permutazioni è O (N ^ 2), quindi non puoi fare meglio di O (N ^ 2). Comunque meglio di O (N ^ 3) dell'approccio ingenuo.
Bakuriu

6
@ Bakuriu: due cose: (1) per essere chiari, il tuo esempio mostra che non può esserci un algoritmo efficiente per la domanda bonus . (2) Enumerare tutte le soluzioni ottimali per il tuo esempio è O ((N / 2)!), Che è molto peggio di O (N ^ 2) (ovvero il tuo esempio è molto più forte di quanto
pensavi

11
@msw: Sto creando un sito Web e c'è una riga con blocchi di annunci di diversi fornitori. Voglio sistemarli in modo che nessun blocco dello stesso provider stia fianco a fianco.
georg

2
Non direi che questo "non è nemmeno vicino a un duplicato", ma il presunto duplicato è una questione diversa, poiché viene considerata la distanza tra elementi identici. Persone che hanno votato per la chiusura dopo il commento di WhyCry: per favore, prestate più attenzione in futuro.
David Eisenstat

Risposte:


30

Questo è sulla falsariga dello pseudocodice attualmente incompleto di Thijser. L'idea è di prendere il più frequente dei restanti tipi di oggetti a meno che non sia stato appena preso. (Vedi anche l'implementazione di Coady di questo algoritmo.)

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

Prova di correttezza

Per due tipi di elementi, con conteggi k1 e k2, la soluzione ottimale ha difetti k2 - k1 - 1 se k1 <k2, 0 difetti se k1 = k2 e k1 - k2 - 1 difetti se k1> k2. Il caso = è ovvio. Gli altri sono simmetrici; ogni istanza dell'elemento di minoranza previene al massimo due difetti su un totale di k1 + k2 - 1 possibile.

Questo algoritmo avido restituisce soluzioni ottimali, secondo la seguente logica. Chiamiamo sicuro un prefisso (soluzione parziale) se si estende a una soluzione ottimale. Chiaramente il prefisso vuoto è sicuro, e se un prefisso sicuro è una soluzione completa, allora quella soluzione è ottimale. È sufficiente mostrare in modo induttivo che ogni passo avido mantiene la sicurezza.

L'unico modo in cui un passaggio avido introduce un difetto è se rimane un solo tipo di oggetto, nel qual caso c'è solo un modo per continuare, e quel modo è sicuro. Altrimenti, sia P il prefisso (sicuro) appena prima del passaggio in esame, sia P 'il prefisso subito dopo e sia S una soluzione ottima che estende P. Se S estende anche P', allora abbiamo finito. Altrimenti, siano P '= Px e S = PQ e Q = yQ', dove xey sono elementi e Q e Q 'sono sequenze.

Supponiamo prima che P non termini con y. A scelta dell'algoritmo, x è almeno tanto frequente in Q quanto y. Considera le sottostringhe massime di Q contenenti solo x e y. Se la prima sottostringa ha almeno tante x quante y, allora può essere riscritta senza introdurre difetti aggiuntivi per iniziare con x. Se la prima sottostringa ha più y di x, allora qualche altra sottostringa ha più x di y, e possiamo riscrivere queste sottostringhe senza ulteriori difetti in modo che x sia la prima. In entrambi i casi troviamo una soluzione ottima T che estende P ', secondo necessità.

Supponiamo ora che P finisca con y. Modificare Q spostando la prima occorrenza di x in primo piano. In tal modo, introduciamo al massimo un difetto (dove era x) ed eliminiamo un difetto (il yy).

Generazione di tutte le soluzioni

Questa è la risposta di tobias_k più test efficienti per rilevare quando la scelta attualmente in esame è in qualche modo vincolata a livello globale. Il tempo di esecuzione asintotico è ottimale, poiché l'overhead di generazione è dell'ordine della lunghezza dell'output. Il ritardo nel caso peggiore purtroppo è quadratico; potrebbe essere ridotto a lineare (ottimale) con strutture dati migliori.

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

Pseudocodice:

  1. Ordina l'elenco
  2. Ripeti la prima metà dell'elenco ordinato e riempi tutti gli indici pari dell'elenco dei risultati
  3. Ripeti la seconda metà dell'elenco ordinato e riempi tutti gli indici dispari dell'elenco dei risultati

Avrai solo p[i]==p[i+1]se più della metà dell'input è costituito dallo stesso elemento, nel qual caso non c'è altra scelta che mettere lo stesso elemento in punti consecutivi (secondo il principio del buco del piccione).


Come sottolineato nei commenti, questo approccio può avere un conflitto di troppo nel caso in cui uno degli elementi si verifichi almeno n/2volte (o n/2+1per dispari n; questo generalizza (n+1)/2)sia per pari che per dispari). Ci sono al massimo due di questi elementi e se ce ne sono due, l'algoritmo funziona bene. L'unico caso problematico è quando c'è un elemento che si verifica almeno la metà delle volte. Possiamo semplicemente risolvere questo problema trovando l'elemento e affrontandolo prima.

Non so abbastanza su Python per scriverlo correttamente, quindi mi sono preso la libertà di copiare l'implementazione dell'OP di una versione precedente da 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

Dalla mia comprensione, questo è ciò che fa @jojo , non sempre ottimale.
georg

10
Ciò non riesce per [0, 1, 1]o [0, 0, 1], a seconda che si utilizzi indici basati su 0 o su 1.
flornquake

@georg In effetti questo è lo stesso approccio della mia risposta. (Nota che Heuster ha risposto prima di me!). Nel mio codice invece i passaggi 2. e 3. sono combinati, ottimizzando così l'efficienza.
jojo

3
@flornquake Buona cattura! Temo che sia il buon vecchio errore off-by-one. Quindi, questo approccio non è ottimale, poiché potrebbe avere 1 conflitto di troppo.
Vincent van der Weele

1
@Heuster: tutte le luci sono verdi! "0 difetti".
georg

10

L'algoritmo già fornito per prendere l'elemento più comune rimasto che non è l'elemento precedente è corretto. Ecco un'implementazione semplice, che utilizza in modo ottimale un heap per tenere traccia dei più comuni.

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]

Buon esempio su come NON scrivere algoritmi in Python. È semplice ma richiede circa 30 minuti solo per digerire la sintassi.
alex904

8

È possibile generare tutte le permutazioni "perfettamente non ordinate" (che non hanno due elementi uguali in posizioni adiacenti) utilizzando un algoritmo di backtracking ricorsivo. In effetti, l'unica differenza per generare tutte le permutazioni è che tieni traccia dell'ultimo numero ed escludi alcune soluzioni di conseguenza:

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 []

Si noti che in questa forma la funzione non è molto efficiente, poiché crea molte sottoelenchi. Inoltre, possiamo accelerarlo guardando prima i numeri più vincolati (quelli con il conteggio più alto). Ecco una versione molto più efficiente che utilizza solo countsi numeri.

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))

Puoi usarlo per generare solo la nextpermutazione perfetta, o listtenerle tutte. Ma nota che se non c'è una permutazione perfettamente non ordinata, questo generatore di conseguenza non produrrà risultati.

>>> 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

Per aggirare questo problema, potresti usarlo insieme a uno degli algoritmi proposti nelle altre risposte come ripiego. Ciò garantirà di restituire una permutazione perfettamente non ordinata, se presente, o una buona approssimazione in caso contrario.

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

Questo usa la memoria O (N ^ 2) ... per ogni elemento nella permutazione stai facendo una copia della lista per la chiamata ricorsiva. Inoltre, essendo ricorsivo, fallisce con lunghezze "piccole".
Bakuriu

@Bakuriu D'accordo, è quello che volevo dire con "non ottimizzato per l'efficienza" ... anche se devo ammettere che non ho escluso O (n ^ 2) spazio, ma hai ragione ... cercherò di migliorarlo .
tobias_k

O (N ^ 2) è sempre dietro la schiena quando hai una resursion come T(n+1) = something + T(n).
Bakuriu

@tobias_k: potresti pubblicare una funzione per una sola permanente, per il test?
georg

@georg Sure: next(unsort2(collections.Counter(a)));-) Ma poiché questo algoritmo genera tutte le possibilità, perché non controllarle tutte? È solo 38 per quella lista di test a 7 elementi.
tobias_k

5

In Python puoi fare quanto segue.

Considera di avere un elenco ordinato l, puoi fare:

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]

Queste sono solo operazioni sul posto e dovrebbero quindi essere piuttosto veloci ( O(N)). Nota che passerai da l[i] == l[i+1]a in l[i] == l[i+2]modo che l'ordine con cui finisci sia tutt'altro che casuale, ma da come ho capito la domanda non è la casualità che stai cercando.

L'idea è di dividere l'elenco ordinato nel mezzo, quindi scambiare ogni altro elemento nelle due parti.

Per l= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]questo porta al = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]

Il metodo non riesce a sbarazzarsi di tutti l[i] == l[i + 1]non appena l'abbondanza di un elemento è maggiore o uguale alla metà della lunghezza dell'elenco.

Mentre quanto sopra funziona bene fintanto che l'abbondanza dell'elemento più frequente è inferiore alla metà della dimensione dell'elenco, la seguente funzione gestisce anche i casi limite (il famoso problema off-by-one) in cui ogni altro elemento che inizia con il il primo deve essere il più abbondante:

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

Grazie! Questo fallisce per [3, 2, 1, 2, 1, 3, 2](restituisce [2, 1, 3, 1, 2, 2, 3], dovrebbe essere (3, 2, 1, 2, 1, 3, 2)) - vedi l'essenza
georg

@georg scusa, colpa mia ho dimenticato a +1. Riprova adesso.
jojo

Ancora problemi, [1, 3, 3, 3, 3, 1, 1]=>[3, 1, 3, 3, 1, 3, 1]
georg

@georg come ho sottolineato, funziona fintanto che il più abbondante è presente meno della metà della lunghezza della lista, cosa che non è il caso in questo esempio.
jojo

@georg Quindi ho aggiunto la parte che gestisce l'errore di uno. Questa parte non è particolarmente veloce (circa uguale all'algoritmo suggerito da Thijser), anche se verrà eseguita solo in rari casi.
jojo

5

Ecco un buon algoritmo:

  1. Prima di tutto conta per tutti i numeri la frequenza con cui si verificano. Posiziona la risposta su una mappa.

  2. ordina questa mappa in modo che i numeri che si verificano più spesso vengano prima.

  3. Il primo numero della tua risposta è il primo numero nella mappa ordinata.

  4. Ricorri alla mappa con il primo ora più piccolo.

Se vuoi migliorare l'efficienza, cerca modi per aumentare l'efficienza della fase di smistamento.


Sì, questo è ciò che ha fatto @tobias_k. Sembra funzionare bene!
georg

@georg È un po 'diverso ... Uso il contatore solo per ridurre la complessità dello spazio, ma non testare i numeri in base a un ordine specifico (pensato che potrebbe essere un'altra accelerazione). Ciò che è diverso è che la mia soluzione fornisce sempre tutte le permutazioni "perfette", se ce ne sono , mentre questo dovrebbe fornire la soluzione migliore (?) (Perfetta o meno).
tobias_k

3
Questo pseudocodice non è del tutto corretto; se l'elemento conta è 5 x, 2 y, 2 z, allora metterà insieme x senza bisogno. Vedi la mia risposta per una soluzione.
David Eisenstat

1
Concordato. Ad esempio [1,1,1,2,3] questo produrrà ad esempio [1,1,2,1,3] invece di [1,2,1,3,1].
tobias_k

Il passaggio 3 è effettivamente controproducente. Se un numero è comune (almeno due voci in più rispetto al numero successivo più frequente), il passaggio 3 utilizzerà quel numero due volte di seguito, senza alcuna giustificazione.
MSalters

5

In risposta alla domanda bonus: questo è un algoritmo che trova tutte le permutazioni di un insieme in cui nessun elemento adiacente può essere identico. Credo che questo sia l'algoritmo più efficiente concettualmente (sebbene altri possano essere più veloci nella pratica perché si traducono in codice più semplice). Non usa la forza bruta, genera solo permutazioni uniche e i percorsi che non portano a soluzioni vengono interrotti al primo punto.

Userò il termine "elemento abbondante" per un elemento in un insieme che ricorre più spesso di tutti gli altri elementi combinati, e il termine "abbondanza" per il numero di elementi abbondanti meno il numero di altri elementi.
es. l'insieme abacnon ha elemento abbondante, gli insiemi abacae aabcaahanno acome elemento abbondante e abbondanza 1 e 2 rispettivamente.

  1. Inizia con un set come:

aaabbcd

  1. Separare le prime occorrenze dalle ripetizioni:

prime: abcd
ripetizioni: aab

  1. Trova l'elemento abbondante nelle ripetizioni, se presente, e calcola l'abbondanza:

elemento
abbondante: abbondanza: 1

  1. Genera tutte le permutazioni dei primi in cui il numero di elementi dopo l'elemento abbondante non è inferiore all'abbondanza: (quindi nell'esempio la "a" non può essere l'ultima)

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. Per ogni permutazione, inserisci il set di caratteri ripetuti uno per uno, seguendo queste regole:

5.1. Se l'abbondanza dell'insieme è maggiore del numero di elementi dopo l'ultima occorrenza dell'elemento abbondante nella permutazione fino ad ora, passa alla permutazione successiva.
ad esempio, quando la permutazione è fin qui abc, un insieme con elementi abbondanti apuò essere inserito solo se l'abbondanza è 2 o meno, quindi aaaabcva bene, aaaaabcnon lo è.

5.2. Seleziona l'elemento dall'insieme la cui ultima occorrenza nella permutazione viene prima.
ad esempio, quando la permutazione fino ad ora è abcbae il set è ab, selezionareb

5.3. Inserisci l'elemento selezionato almeno 2 posizioni a destra della sua ultima occorrenza nella permutazione.
ad esempio, quando si inserisce bnella permutazione babca, i risultati sono babcbaebabcab

5.4. Ripeti il ​​passaggio 5 con ciascuna permutazione risultante e il resto del set.

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    -

Questo algoritmo genera permutazioni uniche. Se vuoi conoscere il numero totale di permutazioni (dove abaviene contato due volte perché puoi cambiare le a), moltiplica il numero di permutazioni uniche con un fattore:

F = N 1 ! * N 2 ! * ... * N n !

dove N è il numero di occorrenze di ogni elemento dell'insieme. Per un setabcdabcaba questo sarebbe 4! * 3! * 2! * 1! o 288, che dimostra quanto sia inefficiente un algoritmo che genera tutte le permutazioni invece che solo quelle uniche. Per elencare tutte le permutazioni in questo caso, basta elencare le permutazioni uniche 288 volte :-)

Di seguito è riportata un'implementazione (piuttosto goffa) in Javascript; Sospetto che un linguaggio come Python possa essere più adatto per questo genere di cose. Eseguire lo snippet di codice per calcolare le permutazioni separate di "abracadabra".

// 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
grazie per questo! vedrà se questo può essere accorciato un po 'in javascript.
stt106

4

L'idea è di ordinare gli elementi dal più comune al meno comune, prendere il più comune, diminuirne il conteggio e rimetterlo nell'elenco mantenendo l'ordine decrescente (ma evitando di mettere per primo l'ultimo elemento utilizzato per evitare ripetizioni quando possibile) .

Questo può essere implementato utilizzando Countere 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

Esempio

>>> 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]

Questo fallisce, ad esempio: [1, 1, 2, 3]dove ci sono soluzioni come [1, 2, 1, 3].
Bakuriu

Sì, me ne sono appena reso conto, scusa
enrico.bacis

Grazie! Questo non produce sempre il risultato ottimale, ad esempio [1, 2, 3, 2, 3, 2, 2]perché restituisce [2, 3, 1, 2, 3, 2, 2](1 errore), mentre l'ideale è (2, 1, 2, 3, 2, 3, 2)) - vedi il succo.
georg

@georg Vero, bella cattura, l'ho aggiornato mantenendo il semplice principio che usa.
enrico.bacis

@ enrico.bacis: grazie! La nuova versione funziona perfettamente. Ho aggiornato il succo. Peccato che non possa più votarti.
georg

2
  1. Ordina l'elenco.
  2. Genera un "migliore shuffle" dell'elenco utilizzando questo algoritmo

Fornirà il minimo di elementi dalla lista nella loro posizione originale (in base al valore dell'elemento) quindi proverà, per il tuo esempio, a mettere gli 1, 2 e 3 lontano dalle loro posizioni ordinate.


Ho provato best_shufflee generato [1,1,1,2,3] -> [3, 1, 2, 1, 1]- non è l'ideale!
georg

2

Inizia con l'elenco ordinato di lunghezza n. Sia m = n / 2. Prendi i valori a 0, poi m, poi 1, poi m + 1, poi 2, poi m + 2 e così via. A meno che tu non abbia più della metà dei numeri uguali, non otterrai mai valori equivalenti in ordine consecutivo.


Grazie per l'idea. Penso che questo sia ciò che @Heuster ha implementato.
georg

2

Per favore perdona la mia risposta in stile "anch'io", ma la risposta di Coady non potrebbe essere semplificata in questo modo?

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)

Modifica: ecco una versione di Python 2 che restituisce un elenco:

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

Sì, sembra funzionare bene (tranne per il fatto che sono su py2 e la funzione dovrebbe restituire un elenco).
georg

@georg Ok, ho aggiunto una versione di python 2 che restituisce un elenco.
srgerg

2
  1. Contare il numero di volte in cui ogni valore viene visualizzato
  2. Selezionare i valori in ordine dal più frequente al meno frequente
  3. Aggiungi il valore selezionato all'output finale, incrementando l'indice di 2 ogni volta
  4. Reimposta l'indice a 1 se l'indice è fuori dai limiti
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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.