Страницы: 1
RSS
Комбинаторика. Метод перестановок. Получить все перестановки элементов одномерного массива, Combinatorics. Permutation method. Get all Permutations of 1D-Array
 
Приветствую!
Мозги до комбинаторики не доросли, прошу помощи  :)

Дано: одномерный массив из N элементов
Задача: получить методом перестановок N! строк ,в которых все элементы массива сцеплены через заданный разделитель.
Код без самого главного

UPD 25/08/2022: Решение: #15.
UPD 05/05/2023: Вариант с комбинациями всех элементов переданного массива: #28
Изменено: Jack Famous - 05.05.2023 18:56:43
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
С учетом твоей любви к
Цитата
Jack Famous написал:
Option Base 1
разбирайся сам
https://prog-cpp.ru/permutation/?ysclid=l77ydq2a1l721349170
Вольный перевод
Скрытый текст
как из индексов получить элементы из твоего массива и слепить - думаю вопрос не сложен для тебя.
По вопросам из тем форума, личку не читаю.
 
Jack Famous,
ReDim aRes(1 To WorksheetFunction.Fact(11))
дальше не работает
 
Алексей, добрый день.
В Вашем примере списка 16 уникальных элементов - многовато для реального применения.
Нужно хранить 20 922 789 888 000 вариантов
Изменено: ZVI - 25.08.2022 01:05:17
 
ZVI,
подождите, вопрос же не в том где работает, а где нет. вопрос в том как получить результат
Jack Famous,
если количество результатов не лезет в столбец куда пихать остальные?
если не лезет во все столбцы... следующий лист? куда пихать следующие результаты?
это стандартные вопросы для программиста (нужно понимать куда пихать результаты)
Изменено: Ігор Гончаренко - 25.08.2022 01:15:22
Программисты - это люди, решающие проблемы, о существовании которых Вы не подозревали, методами, которых Вы не понимаете!
 
Цитата
Ігор Гончаренко написал: ... вопрос же не в том где работает, а где нет
Игорь, любое решение с исходными условиями упрется в объем памяти.
Каждая строка съест ~100 Байт памяти, итого: 100 * 16!  = 2 092 278 988 800 000 Байт.
Даже если столько памяти насобирать, то сколько времени будет работать код?
И какой тогда смысл в конкретном коде?
Изменено: ZVI - 25.08.2022 01:26:11
 
На сколько я понимаю,  permutation algorithm is more important for Jeck  :D . Приведенный по ссылке метод скорее всего не единственный и возможно не самый быстрый.
По вопросам из тем форума, личку не читаю.
 
Вариант реализации перестановок, сочетаний и размещений на VBA можно посмотреть здесь:
http://www.excelworld.ru/forum/3-36449-1
16! - это очень большое число, даже чтобы перебрать все варианты
Изменено: MCH - 25.08.2022 08:09:59
 
MCH, тезка, а метод получается идентичен.
По вопросам из тем форума, личку не читаю.
 
Всем привет и спасибо за внимание к теме!
Сократил массив до 8 элементов (забыл про волшебство факториала) — теперь 8! = 40 320. Option Base 1 убрал (просто забыл удалить).

Цитата
БМВ: Вольный перевод
непонел, прости  :cry: UPD: что-то зашевелилось в мозгу - пробую…

Цитата
MCH: Вариант реализации
у меня эта ссылка, как и все коды из файла давно сохранены. Проблема в том, что я даже переделать не могу под массив значений. Там числа и счётчики…
Изменено: Jack Famous - 25.08.2022 09:37:35
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Питонист бы сказал - Используйте генераторы ))))
 
Цитата
nilske: Питонист бы сказал - Используйте генераторы ))))
моё субъективное мнение — пайтон слишком "балует" своих программистов готовыми библиотеками и скоро они совсем забудут про алгоритмы  :D
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Цитата
Jack Famous написал:
Проблема в том, что я даже переделать не могу под массив значений.
Код
Sub main()
    Dim arr
    Dim sep As String, txt As String
    Dim n As Long, i As Long, rws As Long, maxrows As Long
    
    arr = Array("маша", "петя", "вася", "ира", "таня", "глаша", "юра", "федя")
    sep = "|"
    maxrows = Rows.Count
    
    n = UBound(arr) - LBound(arr) + 1
    ReDim a(1 To n) As Long
    For i = 1 To n
        a(i) = i - 1 + LBound(arr)
    Next i
    
    rws = 0
    Do
        rws = rws + 1
        If rws > maxrows Then
            Exit Do
        End If
        txt = arr(a(1))
        For i = 2 To n
            txt = txt & sep & arr(a(i))
        Next i
        Cells(rws, 1) = txt
    Loop While NextPerm(a(), n)
End Sub

Function NextPerm(a() As Long, n As Long) As Boolean  'следующая перестановка в лексикографическом порядке
    Dim i As Long, k As Long, t As Long, tmp As Long
    For k = n - 1 To 1 Step -1
        If a(k) < a(k + 1) Then Exit For
    Next k
    If k Then
        For i = n To k + 1 Step -1
            If a(k) < a(i) Then
                tmp = a(k)
                a(k) = a(i)
                a(i) = tmp
                Exit For
            End If
        Next i
        NextPerm = True
    End If
    t = n
    For i = k + 1 To (n + k) \ 2
        tmp = a(i)
        a(i) = a(t)
        a(t) = tmp
        t = t - 1
    Next i
End Function
 
MCH, большое спасибо! Смотрю - потом отпишусь…
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Вариант от БМВ из #2 даёт всего 54 строки (наверное, я что-то не так "адаптировал").
Пока нельзя использовать с Option Base 1.

Вариант от MCH отрабатывает полностью корректно. 9! (362 880) строк генерирует за 0,6 сек с учётом обёртки. Ускорил на 0,1 сек за счёт сцепки стрингового массива а на накопления строковой переменной.
Не зависит от Option Base и нижней границы передаваемого массива (учтено при сборке массива индексов).
Код для массива из 9 слов

БМВ, MCH, большое спасибо!
Также спасибо всем участникам обсуждения. Если у кого будут другие варианты на VBA (или, например, на PQ) то делитесь
Изменено: Jack Famous - 25.08.2022 12:27:32
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Цитата
ZVI написал:
Игорь, любое решение с исходными условиями упрется в объем памяти.
не любое, можно результаты писать сразу в файл, тогда решение упрется в обьем доступной памяти на диске (ну,... и во время(((
Код
Sub Main()
  StartPeres Array("a", "b", "c", "d", "e", "f", "g", "h", "i")
End Sub

Sub StartPeres(a)
  Dim a, fs, f
  Set fs = CreateObject("Scripting.FileSystemObject")
  Set f = fs.CreateTextFile("c:\peres.txt", True)
  f.writeline Join(a): Peres a, f, LBound(a)
  f.Close
End Sub

Sub Peres(a, f, i)
  Dim j&, v
  For j = i + 1 To UBound(a)
    If j = i + 1 And j < UBound(a) Then Peres a, f, i + 1
    v = a(j): a(j) = a(i): a(i) = v
    f.writeline Join(a): Peres a, f, i + 1
    v = a(j): a(j) = a(i): a(i) = v
  Next
End Sub
Изменено: Ігор Гончаренко - 27.08.2022 11:34:12
Программисты - это люди, решающие проблемы, о существовании которых Вы не подозревали, методами, которых Вы не понимаете!
 
Цитата
Ігор Гончаренко написал: ...не любое, можно результаты писать сразу в файл, тогда решение упрется в обьем доступной памяти на диске
Игорь, любое. В моем сообщение о "памяти" - это о любой памяти.
Для сохранения результата потребовалось бы 523 шт 4-х терабайтных диска - это что, не проблема (дисковой) памяти?
На выполнение кода потребуется ~400 лет, напоминает известный "главный вопрос жизни, вселенной и всего такого", когда специально созданный компьютер непрерывно работал 7.5 млн лет и выдал результат 42.
Изменено: ZVI - 28.08.2022 02:42:04
 
моему решению по барабану начальный размер массива Алексея
мое решение выдает ВСЕ ВОЗМОЖНЫЕ перестановки
т.е. строго решает поставленную задачу
если не решает - покажите мне какую))
У МЕНЯ НЕТ никаких СЕРТИФИКАТОВ. я руководствуюсь математикой и возможностями компьютера
Изменено: Ігор Гончаренко - 28.08.2022 00:23:35
Программисты - это люди, решающие проблемы, о существовании которых Вы не подозревали, методами, которых Вы не понимаете!
 
Игорь, я уже все написал, что хотел по проблемам памяти и времени вычисления - читайте.
Ничего другого в этой теме я не обсуждал и не намерен.
По "руководствуюсь ... возможностями компьютера" тоже написал: потребуется полтысячи 4 ТБ-х дисков и 400 лет времени расчета по Вашему решению.  Если не видите проблем с ограничениями памяти - Ваше дело.
Ваш код не соответствует изначальной постановке задачи, задайте не 9, а 16 уникальных значения в массиве длиной по 4 символа каждый и сообщите, когда код сбойнет из-за нехватки места на диске.

P.S. Алексей учел, что 16! это слишком много и исправил на 8!
Изменено: ZVI - 28.08.2022 02:49:23
 
Цитата
ZVI: потребуется полтысячи 4 ТБ-х дисков и 400 лет времени ... Если не видите проблем с ограничениями памяти - Ваше дело.
:D
Проще, всё -таки, массив сократить  :D
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Вариант в PQ (помедленнее VBA, 9 элементов):

Код
let
  delimiter = Table.FirstValue ( Excel.CurrentWorkbook(){[ Name = "delimiter" ]}[Content] ),
  valuesTbl = List.Buffer ( List.Transform ( Table.ToColumns ( Excel.CurrentWorkbook(){[ Name = "values" ]}[Content] ){0}, Text.From ) ),
  values = List.Buffer (
    List.Accumulate ( List.Positions ( valuesTbl ), {}, ( s, c ) => s & { { valuesTbl{c}, List.RemoveRange ( valuesTbl, c, 1 ) } } )
  ),
  func = ( vals as list ) =>
    [
      transform = List.Combine (
        List.Transform (
          vals,
          ( x ) =>
            List.Accumulate ( List.Positions ( x{1} ), {}, ( s, c ) => s & { { x{0} & delimiter & x{1}{c}, List.RemoveRange ( x{1}, c, 1 ) } } )
        )
      ),
      last = List.Transform ( transform, ( x ) => x{0} ),
      next = @func ( transform ),
      isLast = List.IsEmpty ( transform{0}{1} ),
      result = if isLast then last else next
    ][result],
  result = Table.FromColumns ( { func ( values ) }, type table [ combs = text ] )
in
  result
Изменено: surkenny - 28.08.2022 04:31:47
 
surkenny, спасибо за вариант! Если я правильно понимаю, то 4 секунды для 9! на PQ — это очень хорошо  :idea:
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Приветствую!
    MCH, прошу помощи!
    Возникла необходимость в опциональном параметре AllVariants, позволяющем, помимо сочетаний всех элементов, добавлять также сочетания всех комбинаций элементов (не знаю, как это правильно называется, но на скрине всё должно быть понятно).
Скрин и Код
Изменено: Jack Famous - 04.05.2023 13:56:23
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
На вскидку получается размещения из 3 по 1 (3 варианта)  плюс размещения из 3 по 2 (6 вариантов) и плюс размещения из 3 по 3 (6 вариантов)
Можно использовать генерацию размещений (любой из алгоритмов) последовательно перебирая выборку от 1 до n
Можно сделать бинарный перебор (для 3х чисел - от 1 до 7: 001, 010, 011, 100, 101, 110, 111), где 1 - есть значение в выборке, 0 - отсутствует значение в выборке, далее к каждой выборке применить перестановки, тогда будут сгенерированы все возможные сочетания с перестановками

Размещение из n по k:
Допустим есть 3 значения (n = 3): 1, 2, 3
Размещения 2х элементов (k = 2): 12, 21, 13, 31, 23, 32

Как можно реализовать задачу используя текущие реализации функций:
Цикл k от 1 до n
Запускаем перебор сочетаний из n по k (k=1: 1,2,3; k=2: 12, 13, 23; k=3: 123)
Для каждого сочетания запускаем перестановки
Получаем желаемый результат

при k = n будут просто все перестановки
как и было в состоянии "Есть"
Изменено: MCH - 04.05.2023 14:20:16
 
MCH, ну вот я сейчас пишу функцию получения из массива элементов массива массивов сочетаний всех элементов, чтобы к каждому массиву потом применить существующую функцию. Думал, можно как-то быстрее сделать, включив дополнительные элементы в общий цикл при установленном параметре.
Цитата
MCH: Можно сделать бинарный перебор
а вот это мне может сильно помочь — спасибо!  :idea:

Цитата
MCH: Как можно реализовать задачу используя текущие реализации функций:
большое спасибо! Бинарный перебор навёл меня на примерно такую же мысль  :idea:
Изменено: Jack Famous - 04.05.2023 14:20:41
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Как вариант.
Код
Option Base 1
Option Explicit
Option Private Module
'==================================================================================================
' https://www.planetaexcel.ru/forum/index.php?PAGE_NAME=message&FID=1&TID=151642&TITLE_SEO=151642-kombinatorika.-metod-perestanovok.-poluchit-vse-perestanovki-elementov-odnomernogo-massiva
Private Function NextPerm(a() As Long, n&) As Boolean  'Next Permutattion by lexicographical order
Dim i&, k&, t&, tmp&
 
For k = n - 1 To 1 Step -1
    If a(k) < a(k + 1) Then Exit For
Next k
 
If k Then
    For i = n To k + 1 Step -1
        If a(k) < a(i) Then tmp = a(k): a(k) = a(i): a(i) = tmp: Exit For
    Next i
    NextPerm = True
End If
 
t = n
 
For i = k + 1 To (n + k) \ 2
    tmp = a(i): a(i) = a(t): a(t) = tmp: t = t - 1
Next i
End Function
'--------------------------------------------------------------------------------------------------
Function PRDX_Combine_Arr1D_AllElements(aElem() As String, aJoin_Ret() As String, Optional sep$ = ", ", Optional AllVariants As Boolean) As Boolean
Dim aTmp$(), aInd&()
Dim nEl&, rNew&, i&
 
nEl = UBound(aElem) - LBound(aElem) + 1: If nEl < 2 Or nEl > 11 Then Stop: End ' 11 is ~49 sec
 
If nEl = 2 Then
    ReDim aJoin_Ret(2)
    aJoin_Ret(1) = Join(aElem, sep)
    aJoin_Ret(2) = aElem(UBound(aElem)) & (sep & aElem(LBound(aElem)))
    GoTo fin
End If
 
ReDim aJoin_Ret(WorksheetFunction.Fact(nEl))
ReDim aInd(UBound(aJoin_Ret)): ReDim aTmp(nEl)
 
For i = 1 To nEl
    aInd(i) = i - 1 + LBound(aElem)
Next i
 
Do
    For i = 1 To nEl
        aTmp(i) = aElem(aInd(i))
    Next i
    rNew = rNew + 1: aJoin_Ret(rNew) = Join(aTmp, sep)
Loop While NextPerm(aInd(), nEl)
 
fin: PRDX_Combine_Arr1D_AllElements = True
End Function
'--------------------------------------------------------------------------------------------------
Sub Test_PRDX_Combine_Arr1D_AllElements()
Dim a$(), b As Variant
 
ReDim a(4)
a(1) = "маша"
a(2) = "петя"
a(3) = "даша"
a(4) = "вася"
 
If Not PRDX_Combine_Arr1D_AllElements2(a, b) Then Exit Sub
Debug.Print Join(b, vbLf)
End Sub

Private Function PRDX_Combine_Arr1D_AllElements2(arr As Variant, brr As Variant) As Boolean
    Dim crr As Variant
    Dim irr As Variant
    ReDim irr(LBound(arr) To UBound(arr))
    ReDim crr(0 To 0)
    Dim bExit As Boolean
    
    Dim ii As Long
    For ii = LBound(irr) To UBound(irr)
        irr(ii) = 1
    Next
    
    Do
        recu irr, 1, bExit, crr
        If bExit Then Exit Do
        irr(LBound(irr)) = irr(LBound(irr)) + 1
    Loop
    
    If UBound(crr) > 0 Then
        Dim drr As Variant
        drr = GetDrr(crr)
        If UBound(drr) > 0 Then
            brr = GetBrr(drr, arr)
            PRDX_Combine_Arr1D_AllElements2 = True
        End If
    End If
End Function

Private Sub recu(irr As Variant, ii As Long, bExit As Boolean, crr As Variant)
    If irr(ii) > UBound(irr) Then
        irr(ii) = 1
        If ii = UBound(irr) Then
            bExit = True
        Else
            irr(ii + 1) = irr(ii + 1) + 1
            recu irr, ii + 1, bExit, crr
        End If
    Else
        If Not HasDupies(irr) Then
            ReDim Preserve crr(0 To UBound(crr) + 1)
            crr(UBound(crr)) = Join(irr, " ")
        End If
    End If
End Sub

Private Function HasDupies(arr As Variant) As Boolean
    Dim ii As Long
    Dim jj As Long
    For ii = LBound(arr) To UBound(arr) - 1
        For jj = ii + 1 To UBound(arr)
            If arr(jj) = arr(ii) Then
                HasDupies = True
                Exit Function
            End If
        Next
    Next
End Function

Private Function GetDrr(crr As Variant) As Variant
    Dim arr As Variant
    Dim brr As Variant
    ReDim arr(0 To 0)
    
    Dim yy As Long
    Dim xx As Long
    For yy = 1 To UBound(crr)
        brr = Split(crr(yy), " ")
        For xx = UBound(brr) To LBound(brr) + 1 Step -1
            brr(xx) = 0
            ReDim Preserve arr(0 To UBound(arr) + 1)
            arr(UBound(arr)) = Join(brr, " ")
        Next
        
        ReDim Preserve arr(0 To UBound(arr) + 1)
        arr(UBound(arr)) = crr(yy)
    Next
    GetDrr = arr
End Function

Private Function GetBrr(arr As Variant, aar As Variant) As Variant
    Dim crr As Variant
    Dim drr As Variant
    Dim brr As Variant
    ReDim brr(1 To UBound(arr))
    
    Dim xx As Long
    Dim yy As Long
    For yy = 1 To UBound(brr)
        crr = Split(arr(yy), " ")
        ReDim drr(LBound(crr) To UBound(crr))
        For xx = LBound(crr) To UBound(crr)
            If crr(xx) > 0 Then
                drr(xx) = aar(crr(xx))
            Else
                Exit For
            End If
        Next
        brr(yy) = Join(drr, ", ")
    Next
    
    GetBrr = brr
End Function
 
МатросНаЗебре, как-то очень долго будет, но не лишним. Спасибо!  :)
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Успел доделать  :)
    Описывать долго. Кто знает, тот поймёт. Может, потом, как-нибудь…
    Огромное спасибо MCH за помощь!  :idea:
Код. Тест — в самом конце модуля
Изменено: Jack Famous - 05.05.2023 18:47:09
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Страницы: 1
Наверх