Я даю вам N-ту перестановку, ви даєте мені N


20

Введення: послідовність малих літер (ASCII [65; 90]), яка є N th * лексикографічною перестановкою мультисету її символів

* перестановки нумеруються від 0 або 1 вгору

Вихід: базове-10 ціле число N


Рулез

  • Можуть бути дублікати (ось чим ця проблема відрізняється від цієї )
  • Символи упорядковуються за їх значенням ASCII
  • У випадку введення довжиною менше або дорівнює 1, вхід - це перша перестановка, а результат - 0або 1відповідно
  • Перша перестановка - це те, в якому крайній лівий символ має найменше значення, найменший правий символ має найвище значення, а послідовність символів між першим і останнім символом - це перша перестановка мультисети його символів (рекурсивне визначення!)
  • Найкоротший виграш вступу

Приклад

  • Вхід AABвиробляє вихід0
  • Вхід ABAвиробляє вихід1
  • Вхід BAAвиробляє вихід2

  • Вхід ZZZвиробляє вихід0
  • Вхід DCBAвиробляє вихід23

EDIT

Додатковий кудо для того, хто може придумати рішення, яке не створює всіх перестановок, а потім шукає вхід. Це певний виклик.


Привіт і ласкаво просимо на сайт. Наразі це питання є досить незрозумілим. Я не дуже впевнений, як упорядковані перестановки. Чи впорядковані вони лексикографічно? Це слід визначити у вашому запитанні.
Пшеничний майстер

1
У нас також є пісочниця, щоб ви могли отримати такий відгук перед публікацією на нашому головному сайті. Опублікувати там спочатку не обов’язково, але багато разів це дуже корисно.
Пшеничний майстер

Ви сказали " великі регістри", zzzі dcbaце не великі регістри .
Меттью Рох

@SIGSEGV виправлено
kyrill

Чи може індекс виходу базуватись на 1, а не на 0?
Луїс Мендо

Відповіді:




4

Python, 302 287 байт

Dead Possum вже опублікував коротке пітонічне рішення, тож я вирішив піти на додаткові кудо. Це рішення не генерує всіх перестановок. Він може швидко обчислити показник перестановки досить великого рядка; він також правильно обробляє порожній рядок.

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

Код тестування:

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)

вихід

 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

Версія без гольфу:

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

Про lexico_permute_string

Цей алгоритм, завдяки Нараяні Пандіті, походить від https://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order

Зробити наступну перестановку в лексикографічному порядку послідовності a

  1. Знайдіть найбільший індекс j такий, що a [j] <a [j + 1]. Якщо такого індексу немає, перестановка є останньою перестановкою.
  2. Знайдіть найбільший індекс k, більший за j, такий, що a [j] <a [k].
  3. Замініть значення a [j] на значення [k].
  4. Зворотну послідовність від [j + 1] до кінцевого елемента a [n].

FWIW, ви можете побачити анотований варіант цієї функції тут .


FWIW, ось зворотна функція.

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

вихід

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

І ось функція, яку я написав під час розробки, perm_unrankпоказує розподіл субрахунків.

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)

вихід

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

Оце Так! Дивовижне рішення! Тут є кілька біт Python. Я не знайомий з тим, що мені доведеться зараз шукати, щоб повністю зрозуміти це. Молодці!
Девід Конрад

Ви можете змінити його z=0і замінити, t[0]і t[1:]де вони використовуються (в даний час hі t), щоб зберегти 8 байт.
Девід Конрад

Вітаємо, ви отримуєте і додаткові кудо! Хоча Йорг Гюльсерманн був першим, але ваша версія є рекурсивною, тому вона не є такою, як його.
kyrill

Дякую, @kyrill Тепер мені цікаво, як зробити зворотний процес ефективно: виробляти перестановку з її індексу. Напевно, не повинно бути надто важким, щоб змінити звичайну техніко-базову техніку, що використовується для перестановок без повторів ...
PM 2Ring

1
Ось найкоротший я міг придумати. Він повертає Trueзначення 1 або менше, але я думаю, що з вашим кодом це повинно бути добре? f=lambda n:n<2or n*f(n-1)
ArBo


3

05AB1E , 5 байт

œê¹Sk

Спробуйте в Інтернеті!

Незалежно виявлено з відповіді Аднана.


Він побив вас 42 секунди: D
kyrill

@kyrill Незважаючи на те, що ви все-таки прийняли неправильну відповідь, я побив його своєю відповіддю Джелі за 5 хвилин.
Ерік Атголфер

Але це Jelly виробляє 1-індексований вихід. Правила заявляють, що перестановки нумеруються від 0 вгору. Я дав виняток Луїсу Мендо, який прямо заявив про це.
kyrill


6
Так, надавання винятків певним користувачам нахмурюється.
Ерік Аутгольфер

3

PHP, 124 байти

$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 байт

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

Інтернет-версія

Бігайте з

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

Розрахуйте факторно

Розширена версія

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

Виведення для рядка 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
)

Інтернет-версія


Що це за магія? У вас є бонусні бали за оригінальний підхід, але ви все-таки виробляєте всі перестановки та шукаєте введення.
kyrill

@kyrill PHP може збільшити рядки php.net/manual/en/language.operators.increment.php Логіка не шукає введення. Це більше порівняння із вхідними даними
Йорг Гюльсерманн

@kyrill на 5 байт більше, я міг би замінити print+$n´ with ´die("$n")´ and the loop will stop after the permutation is found. And I must add $ n = 0` в циклі, тоді
передача

1
Я не читаю PHP, але думаю, що ваш розширений алгоритм досить схожий на мій. FWIW, я не помітив цього, поки не написав свою відповідь.
PM 2Ring

1
@ PM2Ring Можливо, я не зміг прочитати справді вашу версію python
Jörg Hülsermann

3

Джулія, 121 125 байт

Не конкурентоспроможний, оскільки він не поводиться з дублікатами букв правильно. Я переніс це з іншої мови, з частини рішення проблеми Project Euler, яку я робив кілька років тому, і перша, 121-байтна версія мала помилку, оскільки я перенесла використання перестановленої рядки та відсортовану, канонічну посилання рядок.

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)

Для великих входів ця версія використовує bignums вартістю 8 додаткових байтів:

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)

Безголівки:

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

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

Наприклад, алфавіт може бути перетворений на досить надумане речення "Quartz glyph job vex'd cwm finks". Це речення є 259,985,607,122,410,643,097,474,123-ї лексикографічною перестановкою літер алфавіту. (Приблизно 260 септиліонних перестановок.) Ця програма виявляє, що на моїй машині приблизно 65 мкс.

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

Зауважте, що число закінчується на ... 122, а не на ... 123, оскільки ОП вимагає перестановки нумерувати з 0, а не з 1.

Джулія, 375 байт

Я залишив відступ для читабельності, але кількість байтів без нього.

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

Це лише прямий порт Джулії, геніальне рішення Python для PM 2Ring. Я зголодніла, тому вирішила, що хочу печиво врешті-решт. Цікаво побачити схожість та відмінності між двома мовами. Я реалізував itertools.groupby(в обмеженій формі) як g(w).

Але логіка не моя, тож перейдіть і підтвердіть відповідь PM 2Ring .

Замінити f=factorialз , f(x)=factorial(BigInt(x))якщо ви хочете , щоб мати можливість обробляти великі входи , як р ( «QUARTZGLYPHJOBVEXDCWMFINKS»).


Відмінно. Ви отримуєте печиво! Просто зафіксуйте імена змінних у версії, що не використовується.
kyrill

1
Насправді я хочу повернути своє печиво. Ваша програма повертає неправильний результат для BAA- очікуваного 2, фактичного 3.
kyrill

@kyrill Ага, здається, я неправильно зрозумів дублікати. У такому випадку я не впевнений, що бачу спосіб, який би уникнув створення всіх перестановок.
Девід Конрад

FWIW, моя відповідь робить подібне, але для введення рядків з повторними символами.
PM 2Ring

3

MATL , 13 12 11 байт

1 байт збережено завдяки ГБ !

tY@Xu=!Af1)

Результат 1-базований.

Спробуйте в Інтернеті! Або перевірити всі тестові випадки .

Пояснення

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

Тепер ви просто видалите qправо?
kyrill

@kyrill Рівно :-)
Луїс Мендо

1
А що з S? Вам справді потрібно сортувати його до перестановки?
ГБ

@GB Добрий момент, це не потрібно! Я забув, що функція "всі перестановки" сортується на основі значень, а не на індексах. Спасибі!
Луїс Мендо

2

Математика, 33 31 байт

Зміна специфікації проблеми дозволила заощадити 2 байти.

Permutations@Sort@#~Position~#&

Чиста функція, що приймає список як вхідний і повертає невід'ємне ціле число Nу формі {{N}}.


1
Можна скинути -1.
Мартін Ендер

@MartinEnder Спочатку була вимога індексувати перестановки від 0.
kyrill

@kyrill Так, але ви його видалили, тож Грег може зберегти ці два байти.
Мартін Ендер

2

JavaScript (ES6), 130 байт

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)

Менше гольфу

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
}

Тест

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>


Ну, ви не отримуєте файли cookie, але ви отримуєте додатковий кредит на реалізацію власної функції для генерації перестановок ;-)
kyrill



1

Scala, 40 байт

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

Для його використання призначте цю функцію змінній:

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

Спробуйте в Інтернеті на ideone

На жаль, permutationsповертає ітератор, у якого немає sortedметоду, тому його потрібно перетворити на aSeq


1

C ++, 96 байт

Тут ми можемо повністю використовувати стандартну бібліотеку. Список листів передається як ітератори початку / кінця в стандартному стилі C ++.

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

Нам не потрібно генерувати всі перестановки - оскільки ми маємо перетворення від однієї перестановки до попередника, ми просто підраховуємо, скільки ітерацій потрібно, щоб досягти нульового значення.

Тестова програма:

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

Результати тесту:

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

Це оригінальний підхід. Додаткові кудо і вам!
kyrill


0

Рубін, 50 байт

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

->x{x.chars.permutation.map{|v|v*""}.sort.index x}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.