IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Participez au défi sur les palindromes

Le , par millie

0PARTAGES

0  0 
Bonjour,

Pour ce sixième défi proposé par SpiceGuid, l'équipe de developpez.com vous propose un challenge assez court qui peut laisser place à l'optimisation.

Challenge :

Il s'agit d'écrire une fonction (s: string) → string qui renvoie un palindrome p tel que:
  • s contient p
  • s ne contient aucun palindrome qui soit strictement plus long que p


On rappelle qu'un palindrome est une chaîne de caractère dont l'ordre de lettre est identique, selon que la lise par la gauche ou par la droite.
Formellement, une chaîne s est un palindrome si et seulement si :
Pour tout i de 0 à taille(s)-1, s[i] = s[taille(s)-1-i]

Les règles

Il n'y a pas de règle particulière (évidemment, il faut que la solution proposée fonctionne). Vous pouvez proposer des solutions aussi bien dans des langages fonctionnels (caml, haskell, scheme, lisp...) qu'impératif. Le public pourra ainsi juger du code suivant divers critères :
  • la maintenabilité
  • la simplicité
  • le fait qu'il soit optimisé


Le public pourra également juger les différences entre une solution fonctionnelle et une solution impérative. Il lui sera ainsi plus facile de voir, pour un même problème, les différences entre divers paradigmes.

Pour répondre, il vous suffit de poster à la suite.

A vos claviers
de votre participation.

__________________________
Sujet proposé par SpiceGuid

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de Jedai
Expert éminent https://www.developpez.com
Le 20/05/2009 à 13:21
J'avais une petite solution en Haskell, qui utilise un zipper de liste. Je me suis concocté mon propre zipper mais on peut aussi trouver des implémentations sur Hackage :
MyListZipper.hs :
Code Haskell : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module MyListZipper (LZ(), toZipper 
                    , forward, backward 
                    , (<|), (|>) 
                    , start, end 
                    , next, prev) where 
  
data LZ a = LZ [a] [a] 
  
toZipper xs = LZ [] xs 
  
forward, backward :: LZ a -> LZ a 
forward z@(LZ xs []) = z 
forward (LZ xs (y:ys)) = LZ (y:xs) ys 
backward z@(LZ [] ys) = z 
backward (LZ (x:xs) ys) = LZ xs (x:ys) 
  
(<|) :: a -> LZ a -> LZ a 
y <| (LZ xs ys) = LZ xs (y:ys) 
(|>) :: LZ a -> a -> LZ a 
(LZ xs ys) |> x = LZ (x:xs) ys 
  
start, end :: LZ a -> Bool 
start (LZ xs _) = null xs 
end (LZ _ ys) = null ys 
  
next, prev :: LZ a -> (a, LZ a) 
next (LZ xs (y:ys)) = (y, LZ xs ys) 
prev (LZ (x:xs) ys) = (x, LZ xs ys)

Et le code principal :
Code Haskell : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module Main () where 
import Data.List 
import Control.Arrow 
import MyListZipper 
  
data Pal a = Pal [a] [a] 
  
expand (Pal beg seed) = beg ++ seed ++ reverse beg 
  
grow :: (Eq a) => Pal a -> LZ a -> Pal a 
grow pal@(Pal beg seed) z  
    | start z || end z || p /= n = pal 
    | otherwise                  = grow (Pal (p:beg) seed) z' 
    where (p, temp) = prev z 
          (n, z')   = next temp 
  
seeds :: (Eq a) => [a] -> [(Pal a, LZ a)] 
seeds = zSeeds . toZipper  
  
zSeeds :: (Eq a) => LZ a -> [(Pal a, LZ a)] 
zSeeds z | end z     = [] 
         | otherwise = (pal, z') : zSeeds z'' 
         where 
           (n, temp)      = next z 
           (pal, z', z'') = findSeed [n] temp (forward z) 
  
findSeed :: (Eq a) => [a] -> LZ a -> LZ a -> (Pal a, LZ a, LZ a) 
findSeed s@(x:_) z w 
    | end z || n /= x  = (Pal [] s, z, w) 
    | otherwise        = findSeed (n:s) z' (forward w) 
    where (n, z') = next z 
  
longest = foldl' max (0,"") . map ((length &&& id) . expand . uncurry grow) . seeds 
  
main = print . longest =<< getContents

L'idée est simple : on commence par isoler les séquences de lettres identiques dans s, puis on essaie de faire "grossir" ces séquences par les deux côtés tant que cela reste un palindrome, enfin on récupère le palindrome le plus long.

--
Jedaï
0  0 
Avatar de djo.mos
Expert éminent https://www.developpez.com
Le 20/05/2009 à 15:20
Salut,
Une solution impérative (code en Java) :
L'idée est parcourir les lettres de la chaine, en recherchant la lettre courante dans le reste de la chaine. Si on en trouve, on teste si la sous-chaine qui commence à la lettre courante jusqu'à la lettre trouvée est palindrome.

Code java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package defi6; 
  
public class Defi6 { 
  
	private boolean isPalindrome(char[] a, int i, int j) { 
		int l = j - i; 
		for (int x = i; x <= i + l / 2; x++) { 
			if (a[x] != a[j - (x - i)]) { 
				return false; 
			} 
		} 
		return true; 
	} 
  
	private String extractPalyndrome(String s) { 
		char[] a = s.toCharArray(); 
		String candidate = null; 
		for (int i = 0; i < a.length; i++) { 
			for (int j = i; j < a.length; j++) { 
				if (a[j] == a[i] && isPalindrome(a, i, j)) { 
					String pal = s.substring(i, j + 1); 
					if (candidate == null 
							|| pal.length() > candidate.length()) { 
						candidate = pal; 
					} 
				} 
			} 
		} 
		return candidate; 
	} 
  
	public static void main(String[] args) { 
		System.out.println(new Defi6().extractPalyndrome("abbbccacc")); 
	} 
  
}
0  0 
Avatar de Philou67430
Expert confirmé https://www.developpez.com
Le 20/05/2009 à 16:15
J'espère avoir bien compris qu'il faut réaliser une fonction qui retourne le plus long palindrome d'une chaine de caractère.

La fonction est réalisée en perl, et est basée sur les expressions régulières :
Code : Sélectionner tout
1
2
3
sub palindrome {
  return [sort { length $b <=> length $a } $_[0] =~ /((.+).?(??{ reverse "$2" }))/gx]->[0];
}
Exemple d'utilisation en uniligne :
Code : Sélectionner tout
1
2
$ perl -e '$aa = "tooiuertreuioot toot azertyuioppoiuytreza";sub palindrome { return [sort { length $b <=> length $a } $aa =~ /((.+
).?(??{ reverse "$2" }))/gx]->[0] } print palindrome($aa)'
Attention cependant, l'opérateur (??{ ... }) des expressions régulières de perl est considéré comme expérimental (version 5.10), mais il est diablement efficace ici.
L'extraction des palindromes est réalisée grâce à la regexp :
/((.+).?(??{ reverse "$2" }))/gx
à savoir un certain nombre de caractère (le plus possible), suivi éventuellement d'un caractère quelconque, suivi de la première partie déjà trouvée, mais à l'envers.
On extrait de cet expression deux chaines : le palindrome et la première partie du palindrome. La palindrome est toujours plus long que la première partie.
Ensuite, on trie le résultat par longue décroissante, et on prends le premier élément de cette liste.
0  0 
Avatar de Jedai
Expert éminent https://www.developpez.com
Le 20/05/2009 à 16:40
La solution que j'ai donné en premier lieu a une très bonne complexité/efficacité, mais ce n'est évidemment pas le plus simple qu'on puisse faire en Haskell, dans la même optique que la solution Perl, on peut avoir :
Code Haskell : Sélectionner tout
1
2
3
isPalindrome s = s == reverse s 
allSubstrings = concatMap (init . tails) . inits 
longest = maximumBy (comparing length) . filter isPalindrome . allSubstrings

Complexité absolument horrible bien sûr... (ça reste plus rapide que la solution Perl, même en interprété)
La solution de djo.mos est meilleure de ce point de vue mais tout de même plus complexe que ma première solution Haskell.

--
Jedaï
0  0 
Avatar de Philou67430
Expert confirmé https://www.developpez.com
Le 20/05/2009 à 16:58
Sauf erreur, Jedaï, je crois que ta fonction isPalindrome ne récupère pas les palindromes de taille impaire.
0  0 
Avatar de Jedai
Expert éminent https://www.developpez.com
Le 20/05/2009 à 17:02
Citation Envoyé par Philou67430 Voir le message
Sauf erreur, Jedaï, je crois que ta fonction isPalindrome ne récupère pas les palindromes de taille impaire.
Je vois mal comment ce serait possible : en effet ma fonction est une traduction directe de la définition "un palindrome se lit identiquement dans un sens ou dans l'autre"... Peux-tu m'expliquer pourquoi tu croyais cela ?
En fait dans ce second code, j'ai favorisé à fond la lisibilité et la simplicité du code, il n'y a aucune astuce, la fonction finale :
Code : Sélectionner tout
longest = maximumBy (comparing length) . filter isPalindrome . allSubstrings
dit exactement ce qu'elle fait : faire une liste de toutes les sous-chaînes, filtrer celle qui sont des palindromes et récupérer le palindrome de longueur maximale parmi ceux-ci.

--
Jedaï
0  0 
Avatar de Philou67430
Expert confirmé https://www.developpez.com
Le 20/05/2009 à 17:11
Parce que j'ai mal lu le code
0  0 
Avatar de Jedai
Expert éminent https://www.developpez.com
Le 20/05/2009 à 17:38
Une solution identique à ma première mais sur un type de donnée différent, spécifiquement sur une chaîne de caractère disposant d'un accès aléatoire en O(1) (String en Haskell est un synonyme pour [Char] autrement dit une simple liste chaînée de caractères) :
Code Haskell : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
module Main () where 
import Data.List 
import Data.Ord 
import Data.ByteString.Char8 hiding (map) 
import qualified Data.ByteString.Char8 as B 
  
data Pal = P !Int !Int deriving (Show) 
  
expand :: ByteString -> Pal -> ByteString 
expand bs (P start end) = fst . B.splitAt (end - start) . snd . B.splitAt start $ bs 
  
grow :: ByteString -> Pal -> Pal 
grow bs p@(P start end) 
    | start == 0  
      || end == B.length bs 
      || bs `index` (start-1) /= bs `index` end = p  
    | otherwise                                 = grow bs (P (start - 1) (end + 1)) 
  
seeds :: ByteString -> [Pal] 
seeds bs | B.null bs = [] 
         | otherwise = go (B.head bs) 0 1 
    where 
      go c start end  
          | end == B.length bs = [P start end] 
          | c == nextChar      = go c start (end + 1) 
          | otherwise          = P start end : go nextChar end (end + 1) 
          where nextChar = bs `index` end 
  
longest :: ByteString -> ByteString 
longest bs = expand bs . maximumBy (comparing lengthPal) . map (grow bs) . seeds $ bs 
    where lengthPal (P s e) = e - s 
  
main :: IO () 
main = print . longest =<< B.getContents

Cette solution reste complètement fonctionnelle : il n'y a pas le moindre soupçon de mutation ou de code impur dans le tas, simplement il utilise un tableau fonctionnel (immutable) à la place d'une liste chaînée.

Je doute qu'on fasse beaucoup mieux que ce code par la suite (du point de vue rapidité).

--
Jedaï
0  0 
Avatar de Jedai
Expert éminent https://www.developpez.com
Le 20/05/2009 à 20:08
Il est intéressant de noter qu'encore une fois les critères algorithmiques priment sur la question du langage ou de l'efficacité de la structure de donnée choisie : sur un fichier de taille raisonnable (1,5 Mo, un dictionnaire des mots français), ma première version mettait 0.6s environ et ma troisième 0.06s... Je ne sais pas combien de temps mettent les versions Perl et Java : je les ai arrêté après 3/4 d'heure d'exécution !

La différence tient simplement à la complexité : mes versions 1 et 3 sont en O(np) où p est la longueur du plus grand palindrome), la version Perl est en O(n²) et la version Java également, bien que chacune ait une petite optimisation par rapport à ma version 2 (en O(n²p) pur jus).

--
Jedaï
0  0 
Avatar de pseudocode
Rédacteur https://www.developpez.com
Le 20/05/2009 à 20:35
Une version impérative (java que j'ai essayé de faire ressemble a du C). Le principe est basé sur l'aspect "miroir" des palindromes: pour chaque caractère de la chaine, on explore simultanément a gauche + a droite jusqu'a rencontrer une différence.

Code java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Palindrome { 
  
	private int palindromeLength,palindromeStart; 
  
	private void explore(char[] s, int length, int start) { 
		for(int i=0,j=0;j<=1;j++) { 
			for(i=1;(start - i + j)>=0 && (start + i)<length;i++) 
				if (s[start - i + j] != s[start + i]) break; 
			int plen=1+2*(i-1)-j; 
			if (plen>palindromeLength) { 
				palindromeLength=plen; 
				palindromeStart=start-i+1+j; 
			} 
		} 
	} 
  
	public String getLongestPalindrom(char[] s) { 
		palindromeStart=0; 
		palindromeLength=0; 
		for(int i=0;i<s.length;i++) 
			explore(s,s.length,i); 
		return new String(s,palindromeStart,palindromeLength); 
	} 
}


EDIT (jeudi à 14h00): Une réecriture de l'algo ci-dessus dans une seule fonction pour avoir une meilleure "maintenabilité" et "simplicité" comme demandé dans l'énoncé. Vive les commentaires.

Code java : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
String getLongestPalindrom(char[] s, int length) { 
	// pointeurs sur le meilleur palindrome trouvé 
	int palindromeStart=0,palindromeLength=0; 
	// pointeurs gauche/droite sur les caractères 
	int left,right; 
	// pour chaque caractère de la chaine 
	for(int i=0;i<length;i++) { 
		// 1. exploration miroir par rapport a un caractère central 
		for(left=i-1,right=i+1;left>=0 && right<length;left--,right++) 
			if (s[left] != s[right]) break; 
		// sauvegarde du meilleur palindrome 
		if (right-left-1>palindromeLength) { 
			palindromeLength=right-left-1; 
			palindromeStart=left+1; 
		} 
		// 2. exploration miroir par rapport a un axe central 
		for(left=i,right=i+1;left>=0 && right<length;left--,right++) 
			if (s[left] != s[right]) break; 
		// sauvegarde du meilleur palindrome 
		if (right-left-1>palindromeLength) { 
			palindromeLength=right-left-1; 
			palindromeStart=left+1; 
		} 
	} 
	// retourne une copie du meilleur palindrome trouvé 
	return new String(s,palindromeStart,palindromeLength); 
}
0  0