Wszystkie wariacje string[]

0

Witam.

Potrzebuję napisać funkcję zwracającą wszystkie wariacje elementów tablicy stringow.

Mam naprzykład string
string tekst = "To jest jakis tam string";
który splituję:
string[] tekstTablica = tekst.Split(' ');
dostaję więc tablicę
tekstTablica = {"To", "jest", "jakis", "tam", "string"}
Teraz potrzebuję dostać wszystkie wariacje tej tablicy.
Czyli np.:
Jest jakis tam string To
jakis Jest tam To string
tam To jest jakis string
itd..

Początkowe wyrażenie jest maksymalnie 5 wyrazowe, ale równie dobrze może być 1, 2, 3 i 4 wyrazowe.

Jak rozwiązać ten problem? Bardzo zależy mi na wydajności algorytmu, bo będzie to w bardzo dużej pętli.

0
rychu22 napisał(a)

Witam.

Potrzebuję napisać funkcję zwracającą wszystkie wariacje elementów tablicy stringow.

Mam naprzykład string
string tekst = "To jest jakis tam string";
który splituję:
string[] tekstTablica = tekst.Split(' ');
dostaję więc tablicę
tekstTablica = {"To", "jest", "jakis", "tam", "string"}
Teraz potrzebuję dostać wszystkie wariacje tej tablicy.
Czyli np.:
Jest jakis tam string To
jakis Jest tam To string
tam To jest jakis string
itd..

Początkowe wyrażenie jest maksymalnie 5 wyrazowe, ale równie dobrze może być 1, 2, 3 i 4 wyrazowe.

Jak rozwiązać ten problem? Bardzo zależy mi na wydajności algorytmu, bo będzie to w bardzo dużej pętli.

To jest spory problem bo nie ma wydajnego algorytmu na to.
Ja bym skorzystał z backtrackingu bo w sumie nadaje się do tego typu problemów i jest chyba jednym z najwydajniejszych z tego typu rozwiązań i dosyć łatwe w implementacji :)

0

Zrobiłem tak jak mówisz. Działa ok, tylko bardzo długo. Dla tablicy 6 wyrazowej (pokusiłem się dla testów na taką) są to 23 milisekundy u mnie. Nie chce myśleć co będzie jak zamknę to w pętli :O
Musze popracować nad optymalizacja. Może uda mi się coś poprawić.

Jak by ktoś znał jakiś lepszy sposób, z chęcią wysłucham :)

0
rychu22 napisał(a)

Zrobiłem tak jak mówisz. Działa ok, tylko bardzo długo. Dla tablicy 6 wyrazowej (pokusiłem się dla testów na taką) są to 23 milisekundy u mnie. Nie chce myśleć co będzie jak zamknę to w pętli :O
Musze popracować nad optymalizacja. Może uda mi się coś poprawić.

Jak by ktoś znał jakiś lepszy sposób, z chęcią wysłucham :)

Mam nadzieję, że korzystasz ze StringBuildera, bo widzisz lepiej moze byc ciezko wiec pozostaje pÓÓÓÓki co optymalizacja tego co jest :P

0

Wrzuce kod. Na pewno masz więcej doświadczenie ode mnie to może coś mi podpowiesz z optymalizacja, za co bede bardzo wdzieczny :) Każda milisekunda jest ważna.

zmienna slownik to List<string>

private void StworzWariacje(string wyrazenie)
        {
            string[] podzielone = wyrazenie.Split(' ');

            for (int i = 0; i < podzielone.Length - 1; i++)
            {
                StringBuilder noweWyrazenie = new StringBuilder(string.Empty);

                for (int j = 0; j < i; j++)
                {
                    noweWyrazenie.Append(podzielone[j]);
                    noweWyrazenie.Append(' ');
                }

                noweWyrazenie.Append(podzielone[i + 1]);
                noweWyrazenie.Append(' ');
                noweWyrazenie.Append(podzielone[i]);
                noweWyrazenie.Append(' ');

                for (int j = i + 2; j < podzielone.Length; j++)
                {
                    noweWyrazenie.Append(podzielone[j]);
                    noweWyrazenie.Append(' ');
                }

                wyrazenie = noweWyrazenie.ToString();
                wyrazenie = wyrazenie.Remove(wyrazenie.Length - 1);

                if (!(lista.Contains(wyrazenie)))
                {
                    lista.Add(wyrazenie);
                    StworzWariacje(wyrazenie);
                }
            }
        }

Aha, jeszcze dodam. Różne warianty próbowałem i czy używam StringBuilder'a, czy używam zwyklego stringa, czas wykonywania jest taki sam :|
Może kod sie przyda komus w przyszlosci :)

0

Jeszcze może wyjaśnie jak to działa. W tym celu posłużmy się cyferkami, ktore odpowiadaja indeksom w tablicy (numeruje od 1). Mamy więc:
1 2 3 4 5
W każdej rekurencji następuje zamiana 1 z 2, 2 z 3, 3 z 4, 4 z 5. Czyli z jednej rekurencji otrzymujemy 5 (w tym przypadku).
2 1 3 4 5, 1 3 2 4 5, 1 2 4 3 5, 1 2 3 5 4
No i rekurencja sie pogłębia od kazdego z tych przypadków.
Ilustracja do tego :P

http://img190.imageshack.us/img190/1439/rekurancja.jpg

0

Proponowałbym zrobić to tak, że uruchamiasz dla każdego "indeksu" w danej tablicy osobny wątek z ThreadPool, a on sobie już robi resztę (skracasz działanie średnio o X razy gdzie X to ilość stringów, minus czas na uruchamianie wątków),
czyli np. masz slowa: a b c d e indeksowane 0, 1, 2, 3, 4
niech wątek A uruchamia się, i szuka wszystkich możliwości pięcioelementowych z "a" jako pierwszym elementem, potem niech wrzuca to do jakiegoś magazynu, po zakończeniu działania wątków magazyn powinien być pełny.

0

abc - nie ma? eee.. albo ja cos przegapilem, Ty nie sprawdziles jak na lezy. to, ze liczba permutacji rosnie wykladniczo z dlugoscia zbioru nie sprawia ze nie mozna ich wydajnie generowac

proponuje uzyc/napisac odpowiednik next_permutation, ktorego przyklad zaprezentowano tutaj: http://marknelson.us/2002/03/01/next-permutation
w 100% iteracyjny, wiec jesli cos u siebie skopales z rekursja, powinny byc duzo szybsze [info: Bidirectionaliterator u Ciebie w C# moze byc indeksem w tablicy polamanych slow, zas funckja reverse odwraca ciag elementow: abc -> cba]
poza tym - zapomnij o łamaniu i sklejaniu stringa za kazdym wywolaniem funkcji. w polaczeniu z rekursja, za pewne to jest to co Ci zzera tyle czasu, pomijajac ewentualne bledy w rekursji. rozbij stringa raz i tylko raz przed wywolaniem, podaj do swojej funkcji juz polamana tablice i nigdy w srodku nic nie sklejaj, tylko caly czas operuj na postaci 'połamanej' -- zaoszczedzisz naprawde mase czasu. jesli uda Ci sie jeszcze pracowac 'in place', bez alokacji nowych tablic - to juz szybciej nie bedzie..

programmer_: na procesorze 1 rdzeniowym, na podziale tego na watki autor tylko czas straci, na procesorze N rdzeniowym ma sens tworzyc do N takich watkow, wieksza ilosc bedzie wolniejsza niz wykonenie sekwencyjne. obcenie w domu miewa sie N=1..2, rzadziej 4 jak ktos jest maniakiem albo ma serwer sieci osiedlowej w sypialni.

0

Nie chce mi się odpowiadać w kontekście pojedynczych wpisów. Ten problem potraktowałem jako swoiste wyzwanie optymalizacyjne.
W pierwszej kolejności sprawdziłem jak się miewa backtracking przedstawiony wyżej.
Wypadł dosyć tak sobie, wysypuje się już przy 7 wyrazach.

Zaciekawił mnie też pomysł z wykorzystaniem kilku rdzeni - nie wiadomo może dla autora pomysł przypadłby do gustu, bo 2x to już standard itd. itd. (dodatkowo jest coś jak HT - testowany osobiście, daje troche kopa :) )

Wiadomo, że algorytm iteracyjny będzie znacznie szybszy i wydajniejszy pod każdym względem, ale autor zaznaczył, że wyrazów będzie maksymalnie 5 (dobrze zrozumiałem?) takze można to zaniedbać :P

Jako, że w C# nie mam dużego stażu więc wymodziłem klasę spełniającą wymagane czynności:
generuje ona listę takich permutacji z wykorzystaniem maksymalnie 5 rdzeni (gdyż tyle maksymalnie jest wyrazów według autora)

Zrobiłem testy (niestety dla 6 słów, gdyż algorytm autora tematu się wysypuje przy większej ilości) i otrzymałem następujące wyniki:

Lista ma 720 zdan //algorytm autora
Wygenerowanie listy zajelo: 41 ms
Nacisnij ENTER by rozpoczac drugi test porownawczy
Lista ma 720 zdan //z podzialem na watki (+ inne podejscie do sprawy)
Wygenerowanie listy zajelo: 15 ms
Nacisnij ENTER by zakonczyc ocenianie

Zamieszczam cały kod programu i mam nadzieję, że przyda się jakoś autorowi :)
Oczywiście zachęcam do skorzystania z iteracyjnego pomysłu jeżeli zależy Ci na jeszcze większej wydajności

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        private void StworzWariacje(string wyrazenie)
        {
            string[] podzielone = wyrazenie.Split(' ');

            for (int i = 0; i < podzielone.Length - 1; i++)
            {
                StringBuilder noweWyrazenie = new StringBuilder(string.Empty);

                for (int j = 0; j < i; j++)
                {
                    noweWyrazenie.Append(podzielone[j]);
                    noweWyrazenie.Append(' ');
                }

                noweWyrazenie.Append(podzielone[i + 1]);
                noweWyrazenie.Append(' ');
                noweWyrazenie.Append(podzielone[i]);
                noweWyrazenie.Append(' ');

                for (int j = i + 2; j < podzielone.Length; j++)
                {
                    noweWyrazenie.Append(podzielone[j]);
                    noweWyrazenie.Append(' ');
                }

                wyrazenie = noweWyrazenie.ToString();
                wyrazenie = wyrazenie.Remove(wyrazenie.Length - 1);

                if (!(lista.Contains(wyrazenie)))
                {
                    lista.Add(wyrazenie);
                    StworzWariacje(wyrazenie);
                }
            }
        }
        public List<String> lista = new List<string>();
        static void Main(string[] args)
        {
            
            DateTime start = DateTime.Now;
            Program main = new Program();
            main.StworzWariacje("wyraz1 wyraz2 wyraz3 wyraz4 wyraz5 wyraz6");
            
            System.Console.WriteLine("Lista ma {0} zdan",main.lista.Count);
            DateTime stop = DateTime.Now;
            System.Console.WriteLine("Wygenerowanie listy zajelo: {0:0} ms", stop.TimeOfDay.TotalMilliseconds-start.TimeOfDay.TotalMilliseconds);
            System.Console.WriteLine("Nacisnij ENTER by rozpoczac drugi test porownawczy");
            System.Console.ReadKey();
            start = DateTime.Now;
            BackTrackMTH test = new BackTrackMTH("wyraz1 wyraz2 wyraz3 wyraz4 wyraz5 wyraz6");
           
            
            System.Console.WriteLine("Lista ma {0} zdan",test.generateList().Count);
            stop = DateTime.Now;
            System.Console.WriteLine("Wygenerowanie listy zajelo: {0:0} ms", stop.TimeOfDay.TotalMilliseconds - start.TimeOfDay.TotalMilliseconds);
            System.Console.WriteLine("Nacisnij ENTER by zakonczyc ocenianie");
            System.Console.ReadKey();
        }
    }

    //-----------------

    class BackTrackMTH
    {
        String[] tab;
        Thread[] pool;
        Semaphore sem = new Semaphore(1, 1); 
        List<String> str;
        private void generate(Object nr)
        {
            int i = (int)nr;
            bool[] uzyte=new bool[tab.Length];
            for (int k = 0; k < tab.Length; k++)
                uzyte[k] = false;
            uzyte[i] = true;
            backtracking(1, new StringBuilder(tab[i]), uzyte);
        }
        private void backtracking(int slowo, StringBuilder strb, bool[] uzyte)
        {
            if (slowo == tab.Length)
            {
                    sem.WaitOne();
                    str.Add(strb.ToString());
                  
                   sem.Release();
        
                return;
            }
            for (int i = 0; i < tab.Length; i++)
            {
                if (uzyte[i] == false)
                {
                    uzyte[i] = true;
                    strb.Append(' ');
                    strb.Append(tab[i]);
                    backtracking(slowo + 1, strb, uzyte);
                    strb.Remove(strb.Length - tab[i].Length - 1, tab[i].Length + 1);
                    uzyte[i] = false;
                }
            }
        }
       
        public BackTrackMTH(String wyrazenie)
        {
            tab = wyrazenie.Split(' ');
            pool = new Thread[tab.Length];
            ParameterizedThreadStart th = new ParameterizedThreadStart(generate);
            for (int i = 0; i < tab.Length; i++)
            {
                pool[i] = new Thread(th);
            }
            str=new List<string>();
        }
        public List<String> generateList()
        {
            for (int i=0;i<tab.Length;i++)
                pool[i].Start(i);
            for (int i = 0; i < tab.Length; i++)
                pool[i].Join();
            return this.str;
        }

    }
}

No i oczywiście przyznam się, że nie wpadłbym na pomysł z iteracją zaproponowaną przez @quetzalcoatl, gdyż ostatnio większość problemów z którymi się stykam to backtracking więc już tylko o tym myślę :)
Także potraktuj już ten kod jako taką sobie ciekawostkę i implementuj najlepsze dostępne rozwiązanie :)

0

Bardzo się cieszę, że powstała mini-dyskusja w temacie :)
U mnie na komputerze, mój algorytm działa jeszcze dla 7 wyrazów i czas działania to 1,4 sekundy. Dla 8 juz mam przepełnienie stosu. Biorąc pod uwagę wyniki dla 6 wyrazów (u mnie ~25ms u Ciebie ~41ms) to na Twoim komputerze 7 wyrazów mój algorytm powinien obliczyć w około 2,5 sekundy. Możesz porównać z swoim :)

Tak. Wyrazów będzie maksymalnie 5. Nie wiem na jakim komputerze będzie stał program, bo robię na zamówienie, ale myślę że dwa jajka minimum powinien mieć procesor :) Zobaczę jak wątki będą działać u siebie. Siadam teraz i będę implementował i porównywał wszystkie wersje. Spróbuję jeszcze u siebie zgodnie z radą Quetzalcoatl'a operować tylko i wyłączenie na tablicach. Dam znać z którego rozwiązania skorzystałem i dlaczego :) Nie ukrywam, że spore nadzieje wiążę z algorytmem iteracyjnym.

Pozdr i dzięki bardzo za odpowiedzi. Dyskusję proszę kontynuować, jest bardzo ciekawa :)

0

A proszę:

static class Simplest
{
	static string[] output = null;

	public static IEnumerable<string> Run(string sentence)
	{
		string[] words =
			sentence.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
		if (words.Length > 10)
			throw new ArgumentOutOfRangeException("sentence");
		output = new string[factorial(words.Length)];
		buildStrings(string.Empty, words, 0);
		return output;
	}

	private static int factorial(int n)
	{
		return n <= 1 ? 1 : n * factorial(n - 1);
	}

	private static void buildStrings(string prefix, string[] wordsLeft, int index)
	{
		if (prefix.Length > 0)
			prefix += " ";

		int inputLength = wordsLeft.Length;
		if (inputLength == 1)
		{
			output[index] = prefix + wordsLeft[0];
		}
		else
		{
			int section = inputLength * index;
			int new_inputLength = inputLength - 1;
			for (int idx = 0; idx < inputLength; idx++)
			{
				string new_prefix = prefix + wordsLeft[idx];
				string[] new_input = new string[new_inputLength];
				for (int new_idx = 0; new_idx < new_inputLength; new_idx++)
					new_input[new_idx] = wordsLeft[new_idx >= idx ? new_idx + 1 : new_idx];
				buildStrings(new_prefix, new_input, section + idx);
			}
		}
	}
}

U mnie na 2 x 1.86GHz 8 wyrazów miele w niecałe 200ms, 10 wyrazów najkrócej w 11s. Wykonałem też wersję wielowątkową, lecz wzrost wydajności był zaledwie kilka %, więc darowałem sobie tą wersję, tak jest prościej. Jak widać, żadnych dynamicznych kolekcji, same tablice. Tak jest najszybciej. Tak samo nie ma tu żadnych zbędnych iteracji, w rekurencji wykonywane tylko to co trzeba. No i prostota...
Pewnie dałoby się jeszcze nieco przyspieszyć gdyby operować na tablicach int-ów numerujących kolejne wyrazy i dopiero na koniec budować tablicę stringów, ale to zbędne komplikowanie sprawy. Jak widać, operacje na stringach również są całkiem wydajne.

0

Hej. Prędzej był tutaj post z prośbą o pomoc, ale dałem już sobie rade. Powiem tylko o co mi chodziło, a raczej co rozwiązało mój problem - do next_permutation trzeba wrzucić posortowaną tablicę. Inaczej to tak jak by się weszło w środek iteracji, w wyniku czego dostaje się niekompletny zbiór permutacji.

10 wyrazów ten algorytm przemielił mi w 3 sekundy. Dzięki wielkie wszystkim za odpowiedzi oraz udzieloną pomoc.

Pozdrawiam.
Rychu!

0

PS. mam nadzieje ze kwestia 'permutacji 5 wyrazow' to tylko przypadkowa zbieznosc i to nie jest pewien program przypomiajacy filtr antyspamowy dla pewnej fimy na polnocy Polski. Jesli zas zbieznosc jest nieprzypadkowa: w ogole nie potrzebujesz permutowac

1 użytkowników online, w tym zalogowanych: 0, gości: 1