Ti do l'ennesima permutazione, tu mi dai l'N


20

Input: una sequenza di lettere maiuscole (ASCII [65; 90]) che è il N ° * permutazione lessicografico della multinsieme dei suoi personaggi

* le permutazioni sono numerate da 0 o 1 verso l'alto

Uscita: base-10 intero N


Rulez

  • Potrebbero esserci dei duplicati (è così che questa sfida differisce da questa )
  • I caratteri sono ordinati in base al loro valore ASCII
  • Nel caso di un input di lunghezza inferiore o uguale a 1, l'input è la prima permutazione e il risultato è 0o 1rispettivamente
  • La prima permutazione è quella in cui il carattere più a sinistra ha il valore più basso, il carattere più a destra ha il valore più alto e la sequenza di caratteri tra il primo e l'ultimo carattere è la prima permutazione del multiset dei suoi caratteri (definizione ricorsiva!)
  • Vince l'ingresso più breve

Esempio

  • L'input AABproduce output0
  • L'input ABAproduce output1
  • L'input BAAproduce output2

  • L'input ZZZproduce output0
  • L'input DCBAproduce output23

MODIFICARE

Complimenti extra a chi può trovare una soluzione che non produce tutte le permutazioni e quindi cerca l'input. Questa è una sfida.


Ciao e benvenuto nel sito. Questa domanda non è al momento chiara. Non sono davvero sicuro di come siano ordinate le permutazioni. Sono lessicograficamente ordinati? Questo dovrebbe essere definito nella tua domanda.
Wheat Wizard

1
Abbiamo anche un sandbox in modo da poter ricevere questo tipo di feedback prima di pubblicare sul nostro sito principale. Non è obbligatorio pubblicare prima lì, ma molte volte è molto utile.
Wheat Wizard

Hai detto "Maiuscolo" zzze dcbanon è maiuscolo.
Matthew Roh,

@SIGSEGV corretto
kyrill

L'indice di output può essere basato su 1 anziché su 0?
Luis Mendo,

Risposte:




4

Python, 302 287 byte

Dead Possum ha già pubblicato una breve soluzione Pythonic, quindi ho deciso di andare per i complimenti extra. Questa soluzione non genera tutte le permutazioni. Può calcolare rapidamente l'indice di permutazione di una stringa piuttosto grande; gestisce anche correttamente una stringa vuota.

from math import factorial as f
from itertools import groupby as g
def p(t,b=''):
 if len(t)<2:return 0
 z,b=0,b or sorted(t)
 for i,c in enumerate(b):
  w=b[:i]+b[i+1:]
  if c==t[0]:return z+p(t[1:],w)
  if i<1 or c!=b[i-1]:
   n=f(len(w))
   for _,v in g(w):n//=f(len(list(v)))
   z+=n

Codice di prova:

def lexico_permute_string(s):
    ''' Generate all permutations of `s` in lexicographic order '''
    a = sorted(s)
    n = len(a) - 1
    while True:
        yield ''.join(a)
        for j in range(n-1, -1, -1):
            if a[j] < a[j + 1]:
                break
        else:
            return
        v = a[j]
        for k in range(n, j, -1):
            if v < a[k]:
                break
        a[j], a[k] = a[k], a[j]
        a[j+1:] = a[j+1:][::-1]

def test_all(base):
    for i, s in enumerate(lexico_permute_string(base)):
        rank = p(s)
        assert rank == i, (i, s, rank)
        print('{:2} {} {:2}'.format(i, s, rank))
    print(repr(base), 'ok\n')

for base in ('AAB', 'abbbbc'):
    test_all(base)

def test(s):
    print('{!r}\n{}\n'.format(s, p(s)))

for s in ('ZZZ', 'DCBA', 'a quick brown fox jumps over the lazy dog'):
    test(s)

produzione

 0 AAB  0
 1 ABA  1
 2 BAA  2
'AAB' ok

 0 abbbbc  0
 1 abbbcb  1
 2 abbcbb  2
 3 abcbbb  3
 4 acbbbb  4
 5 babbbc  5
 6 babbcb  6
 7 babcbb  7
 8 bacbbb  8
 9 bbabbc  9
10 bbabcb 10
11 bbacbb 11
12 bbbabc 12
13 bbbacb 13
14 bbbbac 14
15 bbbbca 15
16 bbbcab 16
17 bbbcba 17
18 bbcabb 18
19 bbcbab 19
20 bbcbba 20
21 bcabbb 21
22 bcbabb 22
23 bcbbab 23
24 bcbbba 24
25 cabbbb 25
26 cbabbb 26
27 cbbabb 27
28 cbbbab 28
29 cbbbba 29
'abbbbc' ok

'ZZZ'
0

'DCBA'
23

'a quick brown fox jumps over the lazy dog'
436629906477779191275460617121351796379337

Versione non golfata:

''' Determine the rank (lexicographic index) of a permutation 
    The permutation may contain repeated items

    Written by PM 2Ring 2017.04.03
'''

from math import factorial as fac
from itertools import groupby

def lexico_permute_string(s):
    ''' Generate all permutations of `s` in lexicographic order '''
    a = sorted(s)
    n = len(a) - 1
    while True:
        yield ''.join(a)
        for j in range(n-1, -1, -1):
            if a[j] < a[j + 1]:
                break
        else:
            return
        v = a[j]
        for k in range(n, j, -1):
            if v < a[k]:
                break
        a[j], a[k] = a[k], a[j]
        a[j+1:] = a[j+1:][::-1]

def perm_count(s):
    ''' Count the total number of permutations of sorted sequence `s` '''
    n = fac(len(s))
    for _, g in groupby(s):
        n //= fac(sum(1 for u in g))
    return n

def perm_rank(target, base):
    ''' Determine the permutation rank of string `target`
        given the rank zero permutation string `base`,
        i.e., the chars in `base` are in lexicographic order.
    '''
    if len(target) < 2:
        return 0
    total = 0
    head, newtarget = target[0], target[1:]
    for i, c in enumerate(base):
        newbase = base[:i] + base[i+1:]
        if c == head:
            return total + perm_rank(newtarget, newbase)
        elif i and c == base[i-1]:
            continue
        total += perm_count(newbase)

base = 'abcccdde'
print('total number', perm_count(base))

for i, s in enumerate(lexico_permute_string(base)):
    rank = perm_rank(s, base)
    assert rank == i, (i, s, rank)
    #print('{:2} {} {:2}'.format(i, s, rank))
print('ok')

Di lexico_permute_string

Questo algoritmo, dovuto a Narayana Pandita, proviene da https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order

Produrre la permutazione successiva in ordine lessicografico di sequenza a

  1. Trova l'indice più grande j tale che a [j] <a [j + 1]. Se tale indice non esiste, la permutazione è l'ultima permutazione.
  2. Trova l'indice più grande k maggiore di j tale che a [j] <a [k].
  3. Scambia il valore di a [j] con quello di a [k].
  4. Invertire la sequenza da a [j + 1] fino a includere l'elemento finale a [n].

FWIW, puoi vedere una versione annotata di quella funzione qui .


FWIW, ecco la funzione inversa.

def perm_unrank(rank, base, head=''):
    ''' Determine the permutation with given rank of the 
        rank zero permutation string `base`.
    '''
    if len(base) < 2:
        return head + ''.join(base)

    total = 0
    for i, c in enumerate(base):
        if i < 1 or c != base[i-1]:
            newbase = base[:i] + base[i+1:]
            newtotal = total + perm_count(newbase)
            if newtotal > rank:
                return perm_unrank(rank - total, newbase, head + c)
            total = newtotal
# Test

target = 'a quick brown fox jumps over the lazy dog'
base = ''.join(sorted(target))
rank = perm_rank(target, base)
print(target)
print(base)
print(rank)
print(perm_unrank(rank, base))

produzione

a quick brown fox jumps over the lazy dog
        aabcdeefghijklmnoooopqrrstuuvwxyz
436629906477779191275460617121351796379337
a quick brown fox jumps over the lazy dog

Ed ecco una funzione che ho scritto durante lo sviluppo perm_unrankche mostra la suddivisione dei sottoconti.

def counts(base):
    for i, c in enumerate(base):
        newbase = base[:i] + base[i+1:]
        if newbase and (i < 1 or c != base[i-1]):
            yield c, perm_count(newbase)
            for h, k in counts(newbase):
                yield c + h, k 

def show_counts(base):
    TAB = ' ' * 4
    for s, t in counts(base):
        d = len(s) - 1
        print('{}{} {}'.format(TAB * d, s, t))

# Test
base = 'abccc'
print('total number', perm_count(base))
show_counts(base)

produzione

a 4
    ab 1
        abc 1
            abcc 1
    ac 3
        acb 1
            acbc 1
        acc 2
            accb 1
            accc 1
b 4
    ba 1
        bac 1
            bacc 1
    bc 3
        bca 1
            bcac 1
        bcc 2
            bcca 1
            bccc 1
c 12
    ca 3
        cab 1
            cabc 1
        cac 2
            cacb 1
            cacc 1
    cb 3
        cba 1
            cbac 1
        cbc 2
            cbca 1
            cbcc 1
    cc 6
        cca 2
            ccab 1
            ccac 1
        ccb 2
            ccba 1
            ccbc 1
        ccc 2
            ccca 1
            cccb 1

Wow! Soluzione incredibile! Ci sono alcuni pezzetti di Python qui non ho familiarità con il fatto che dovrò andare a cercare ora per capirlo appieno. Molto bene!
David Conrad,

Puoi cambiarlo z=0e sostituirlo t[0]e t[1:]dove sono usati (attualmente he t) per salvare 8 byte.
David Conrad,

Congratulazioni, ottieni anche i complimenti in più! Anche se Jörg Hülsermann è stato il primo, ma la tua versione è ricorsiva, quindi non è la stessa della sua.
kyrill

Grazie, @kyrill Ora mi chiedo come eseguire il processo inverso in modo efficiente: produrre la permutazione dal suo indice. Immagino che non dovrebbe essere troppo difficile modificare la solita tecnica di base fattoriale usata per permutazioni senza ripetizioni ...
PM 2Ring

1
Ecco il più breve che ho potuto inventare. Restituisce Trueper valori di 1 o inferiori, ma penso che con il tuo codice dovrebbe andare tutto bene? f=lambda n:n<2or n*f(n-1)
ArBo


3

05AB1E , 5 byte

œê¹Sk

Provalo online!

Scoperto in modo indipendente dalla risposta di Adnan.


Ti ha battuto di 42 secondi: D
kyrill

@kyrill Anche se hai ancora accettato la risposta sbagliata, l'ho battuto con la mia risposta Jelly di 5 minuti.
Erik the Outgolfer,

Ma Jelly produce un output indicizzato. Le regole stabiliscono che le permutazioni sono numerate da 0 in su. Ho fatto un'eccezione a Luis Mendo che l'ha chiesto esplicitamente.
kyrill,


6
Sì, dare eccezioni a determinati utenti è disapprovato.
Erik the Outgolfer,

3

PHP, 124 byte

$a=str_split($argn);sort($a);for($i=$j=join($a);$i<=strrev($j);$i++)$i==$argn?print+$n:(($c=count_chars)($i)!=$c($j)?:$n++);

PHP, 136 byte

foreach($t=($c=count_chars)($argn)as$k=>$v)$i=$s.=str_repeat(chr($k),$v);for(;$i<=strrev($s);$i++)$i==$argn?print+$n:($c($i)!=$t?:$n++);

Versione online

Corri con

echo '<string>' | php -nR '<code>'

Calcola con fattoriale

Versione estesa

function f($i){return array_product(range(1,$i));} #factorial
function p($s){
return f(strlen($s))/array_product(array_map("f",count_chars($s,1)));
} # factorial / divide through product of factorials for each char
$a=$argn;
$b="";
$r=[];
for($d=0;$a;$d++) # loop range before used chars in string 
{
    for($i=0;$i<strlen($a);$i++){ # loop for every char in the rest string 
        if($a[$i]<$a[0]) # if char is before first char order by ascii
        $r[$d.$a[$i]]=p(substr_replace($a,"",$i,1)); # add range before
    }
    $a=substr($a,1); # remove first char
}
echo array_sum($r); # Output the range before the used permutation

Uscita per la stringa PPCG

Array
(
    [0C] => 3    # in first run C is before P startposition = 3
    [0G] => 3    # in first run G is before P startposition = 3+3
    [1C] => 2    # in second run PC is before PP startposition = 3+3+2
    [1G] => 2    # in second run PG is before PP startposition = 3+3+2+2=8
)

Versione online


Che tipo di magia è questa? Hai punti bonus per l'approccio originale, ma produci comunque tutte le permutazioni e poi cerchi input.
kyrill,

@kyrill PHP può incrementare le stringhe php.net/manual/en/language.operators.increment.php La logica non cerca input. È più un confronto con l'input
Jörg Hülsermann,

@kyrill per 5 byte in più Ho potuto sostituire print+$n´ with ´die("$n")´ and the loop will stop after the permutation is found. And I must add $ n = 0` nel loop quindi il cast in numero intero non funziona in questa modifica
Jörg Hülsermann

1
Non leggo PHP, ma penso che il tuo algoritmo espanso sia abbastanza simile al mio. FWIW, non l'ho notato fino a dopo aver scritto la mia risposta.
PM 2Ring

1
@ PM2Ring Potrebbe non riuscire a leggere davvero la tua versione di Python
Jörg Hülsermann,

3

Julia, 121 125 byte

Non competitiva, in quanto non tratta correttamente le lettere duplicate. L'ho portato da un'altra lingua, da parte di una soluzione a un problema di Project Euler che ho riscontrato diversi anni fa, e la prima versione a 121 byte aveva un bug perché avevo trasposto l'uso della stringa permutata e il riferimento canonico ordinato corda.

f(p)=(n=0;z=length(p)-1;s=join(sort(collect(p)));for c in p z-=(n+=factorial(z)*(search(s, c)-1);p=replace(s,c,"",1);1)end;n)

Per input di grandi dimensioni, questa versione utilizza bignum al costo di 8 byte extra:

f(p)=(n=0;z=BigInt(length(p)-1);s=join(sort(collect(p)));for c in p z-=(n+=factorial(z)*(search(s, c)-1);p=replace(s,c,"",1);1)end;n)

Ungolfed:

function f(perm)
    nth = 0
    size = length(perm) - 1
    sorted = join(sort(collect(p)))
    for ch in sorted
        index = search(perm, ch) - 1
        nth += factorial(size) * index
        perm = replace(perm, ch, "" , 1) # replace at most one copy
        size -= 1
    end
    return nth
end

Utilizza un sistema numerico factoriadico , qv Pertanto, non produce tutte le permutazioni e per input di grandi dimensioni funzionerà enormemente più velocemente di quelli che lo fanno.

Ad esempio, l'alfabeto può essere permutato nella frase piuttosto inventata "Lavoro al glifo di quarzo vex'd cwm finks". Quella frase è la 259.985.607.122.410.643.097.474.123esima permutazione lessicografica delle lettere dell'alfabeto. (Circa 260 settilioni di permutazione.) Questo programma lo rileva in circa 65 µs sulla mia macchina.

julia> @time f("quartzglyphjobvexdcwmfinks")
  0.000065 seconds (570 allocations: 19.234 KB)
259985607122410643097474122

Si noti che il numero termina in ... 122 anziché ... 123 perché OP ha richiesto che le permutazioni fossero numerate da 0 anziché da 1.

Julia, 375 byte

Ho lasciato il rientro per leggibilità, ma il conteggio dei byte è senza.

p(t,b="")=begin
    l=length
    if l(t)<2 return 0 end
    f=factorial
    g(w)=(a=[];
        while w!=""
            s=""
            for c in w if c==w[1] s="$s$c" end end
            w=replace(w,s,"")
            push!(a,s)
        end;a)
    b=b>""?b:join(sort(collect(t)))
    z=0
    for(i,c) in enumerate(b)
        w=b[1:i-1]*b[i+1:end]
        if c==t[1] return z+p(t[2:end],w)
        elseif i>1&&c==b[i-1] continue end
        n=f(l(w))
        for v in g(w) n=div(n,f(l(v))) end
        z+=n
    end
    z
end

Questa è solo una porta diretta di Julia della brillante soluzione Python di PM 2Ring. Avevo fame, quindi ho deciso che dopo tutto volevo il biscotto. È interessante vedere le somiglianze e le differenze tra le due lingue. Ho implementato itertools.groupby(in forma limitata) come g(w).

Ma la logica non è mia, quindi vai e vota la risposta di PM 2Ring .

Sostituire f=factorialcon f(x)=factorial(BigInt(x))se si desidera essere in grado di gestire input di grandi dimensioni come p ("QUARTZGLYPHJOBVEXDCWMFINKS").


Eccellente. Ottieni il biscotto! Basta correggere i nomi delle variabili nella versione non modificata.
kyrill

1
In realtà rivoglio indietro i miei biscotti. Il tuo programma restituisce risultati errati per BAA- previsti 2, effettivi 3.
kyrill

@kyrill Ah, sembra che abbia frainteso i duplicati. In tal caso, non sono sicuro di riuscire a vedere un modo per evitarlo producendo tutte le permutazioni.
David Conrad,

FWIW, la mia risposta fa una cosa simile, ma per stringhe di input con caratteri ripetuti.
PM 2Ring

3

MATL , 13 12 11 byte

1 byte salvato grazie a GB !

tY@Xu=!Af1)

L'output è basato su 1.

Provalo online! Oppure verifica tutti i casi di test .

Spiegazione

t      % Input string implicitly. Duplicate
Y@     % All permutations, sorted; each in a different row
Xu     % Unique rows
=      % Compare for equality, with broadcast
!      % Transpose
A      % All: true for columns that contain all entries true
f      % Find: indices of nonzero elements
1)     % Get first of those indices. Implicitly display

Ora rimuoverai semplicemente il qgiusto?
kyrill

@kyrill Esattamente :-)
Luis Mendo il

1
E la S? Hai davvero bisogno di ordinarlo prima della permutazione?
GB

@GB Un buon punto, non è necessario! Ho dimenticato che la funzione "tutte le permutazioni" ordina in base ai valori, non agli indici. Grazie!
Luis Mendo,

2

Mathematica, 33 31 byte

La modifica delle specifiche del problema ha consentito un risparmio di 2 byte.

Permutations@Sort@#~Position~#&

Funzione pura che accetta un elenco come input e restituisce un numero intero non negativo Nnel modulo {{N}}.


1
Puoi rilasciare il -1.
Martin Ender,

@MartinEnder Inizialmente era richiesto che le permutazioni fossero indicizzate da 0.
kyrill

@kyrill Sì, ma l'hai rimosso, quindi Greg può salvare quei due byte.
Martin Ender,

2

JavaScript (ES6), 130 byte

s=>(o=new Set,p=(s,q)=>s.map((t,i)=>p(t=[...s],q.concat(t.splice(i,1))))[0]||o.add(q.join``))([...s],[])&&[...o].sort().indexOf(s)

Meno golf

s=>{
  o = new Set; // use a set to avoid duplicates

  // recursive function to build all permutations (no cookie for me)
  p = (s, q) => 
  {
    y = s.map( (t,i) => // execute for each char at position i
          (
             t = [...s], // using t as local variable, store a copy of s
             x = t.splice(i,1), // remove char from t in position i, and store in array x
             p(t, q.concat(x)) // recursive call
          ));
    if (!y[0]) // if s was empty, I have a new permutation in q
      o.add( q.join('')) // convert to string and add to output set 
  }
  // call p to enumerate all permutations
  p( [...s], [] ) // convert string s to array, q starts empty

  o = [...o].sort() // get elements of o and sort lexicographically 
  return o.indexOf(s) // return the position of the input in o
}

Test

F=
s=>(o=new Set,p=(s,q)=>s.map((t,i)=>p(t=[...s],q.concat(t.splice(i,1))))[0]||o.add(q.join``))([...s],[])&&[...o].sort().indexOf(s)

function update() {
  O.textContent = F(I.value)
}

update()
<input id=I value='DCBA' oninput='update()'>
<pre id=O></pre>


Bene, non ricevi un cookie, ma ottieni ulteriore credito per l'implementazione della tua funzione per generare permutazioni ;-)
kyrill



1

Scala, 40 byte

s=>s.permutations.toSeq.sorted indexOf s

Per usarlo, assegnare questa funzione a una variabile:

val f:(String=>Int)=s=>s.permutations.toSeq.sorted indexOf s
println(f("BAA"))

Provalo online su ideone

Sfortunatamente, permutationsrestituisce un iteratore, che non ha un sortedmetodo, quindi deve essere convertito in unSeq


1

C ++, 96 byte

Possiamo fare pieno uso della libreria standard qui. L'elenco delle lettere viene passato come iteratori di inizio / fine in stile C ++ standard.

#include<algorithm>
int f(char*a,char*z){int i=0;while(std::prev_permutation(a,z))++i;return i;}

Non abbiamo bisogno di generare tutte le permutazioni - poiché abbiamo una trasformazione da una permutazione al suo predecessore, contiamo semplicemente quante iterazioni sono necessarie per raggiungere il valore zeroth.

Programma di test:

#include<cstring>
#include<iostream>
int main(int argc, char **argv)
{
    while (*++argv)
        std::cout << *argv << ": "
                  << f(*argv, *argv+std::strlen(*argv)) << std::endl;
}

Risultati del test:

BAA: 0
BAA: 1
BAA: 2
ZZZ: 0
DCBA: 23
: 0

Questo è un approccio originale. Complimenti extra anche a te!
Kyrill,


0

Rubino, 50 byte

Mi aspettavo che questo fosse più breve. Non avrei aggiunto il sortse i documenti non avessero detto "l'implementazione non fornisce garanzie sull'ordine in cui vengono prodotte le permutazioni".

->x{x.chars.permutation.map{|v|v*""}.sort.index x}
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.