web-dev-qa-db-fra.com

Comment diviser du texte sans espaces en liste de mots?

Entrée:"tableapplechairtablecupboard..." Plusieurs mots

Quel serait un algorithme efficace pour diviser un tel texte en liste de mots et obtenir:

Sortie:["table", "Apple", "chair", "table", ["cupboard", ["cup", "board"]], ...]

La première chose qui vient à l'esprit est de parcourir tous les mots possibles (en commençant par la première lettre) et de trouver le mot le plus long possible, continuer à partir de position=Word_position+len(Word)

P.S.
Nous avons une liste de tous les mots possibles.
Le mot "armoire" peut être "tasse" et "planche", sélectionnez la plus longue.
Langue: python, mais l'essentiel est l'algorithme lui-même.

89
Sergey

Un algorithme naïf ne donnera pas de bons résultats lorsqu'il est appliqué à des données réelles. Voici un algorithme de 20 lignes qui exploite la fréquence relative des mots pour donner des résultats précis pour du texte réel.

(Si vous voulez une réponse à votre question d'origine qui n'utilise pas la fréquence Word, vous devez affiner ce que l'on entend exactement par "mot le plus long": est-il préférable d'avoir un mot de 20 lettres et dix de 3 lettres mots, ou est-il préférable d'avoir cinq mots de 10 lettres? Une fois que vous vous êtes fixé une définition précise, il vous suffit de changer la ligne définissant wordcost pour refléter la signification voulue.)

L'idée

La meilleure façon de procéder est de modèle la distribution de la sortie. Une bonne première approximation consiste à supposer que tous les mots sont distribués indépendamment. Il vous suffit alors de connaître la fréquence relative de tous les mots. Il est raisonnable de supposer qu'ils suivent la loi de Zipf, c'est-à-dire que le mot de rang n dans la liste des mots a une probabilité d'environ 1/( n log = [~ # ~] n [~ # ~]) où [~ # ~] n [~ # ~] est le nombre de mots du dictionnaire.

Une fois que vous avez fixé le modèle, vous pouvez utiliser la programmation dynamique pour déduire la position des espaces. La phrase la plus probable est celle qui maximise le produit de la probabilité de chaque mot individuel, et il est facile de le calculer avec une programmation dynamique. Au lieu d'utiliser directement la probabilité, nous utilisons un coût défini comme le logarithme de l'inverse de la probabilité pour éviter les débordements.

Le code

from math import log

# Build a cost dictionary, assuming Zipf's law and cost = -math.log(probability).
words = open("words-by-frequency.txt").read().split()
wordcost = dict((k, log((i+1)*log(len(words)))) for i,k in enumerate(words))
maxword = max(len(x) for x in words)

def infer_spaces(s):
    """Uses dynamic programming to infer the location of spaces in a string
    without spaces."""

    # Find the best match for the i first characters, assuming cost has
    # been built for the i-1 first characters.
    # Returns a pair (match_cost, match_length).
    def best_match(i):
        candidates = enumerate(reversed(cost[max(0, i-maxword):i]))
        return min((c + wordcost.get(s[i-k-1:i], 9e999), k+1) for k,c in candidates)

    # Build the cost array.
    cost = [0]
    for i in range(1,len(s)+1):
        c,k = best_match(i)
        cost.append(c)

    # Backtrack to recover the minimal-cost string.
    out = []
    i = len(s)
    while i>0:
        c,k = best_match(i)
        assert c == cost[i]
        out.append(s[i-k:i])
        i -= k

    return " ".join(reversed(out))

que vous pouvez utiliser avec

s = 'thumbgreenappleactiveassignmentweeklymetaphor'
print(infer_spaces(s))

Les resultats

J'utilise ce dictionnaire 125k Word rapide et sale que j'ai mis en place à partir d'un petit sous-ensemble de Wikipedia.

Avant: thumbgreenappleactiveassignmentweeklymetaphor.
Après: thumb green Apple métaphore hebdomadaire d'affectation active.

Avant: il y adesinformations textuellesdespersonnes commentéesàpartirdehtmlmaisd'autrespersonnalisentdescaractèresdélimitéspeubledanslesexempleséchantillonnelappliqueractivationdel'affectationdepuisune semaineestapho-brusquementlorsqu'ilestréculierpourvecherestrastestherthestaresthefthestaresthefthedestarelestrastedestrustestareleconstaurer.

Après: il y a des masses d'informations textuelles des commentaires des gens qui sont analysées à partir de html mais il n'y a pas de caractères délimités en eux par exemple thumb green Apple métaphore hebdomadaire d'affectation active apparemment il y a le pouce vert Apple etc dans la chaîne j'ai également un grand dictionnaire pour demander si le mot est raisonnable donc quel est le moyen d'extraction le plus rapide Merci beaucoup.

Avant: il était obscur et ténèbrespourlesenvahirouexceptatàdesintervalles occasionnelsquand il a été contrôlépar une rafaleviolentequi a balayélesesthésiquespour lesfamillespourlesnouveauxcontenuscélébritésenflamantenflamantenflamantenflamantenflamantenfamille.

Après: ce fut une nuit sombre et orageuse la pluie tomba en torrents sauf à intervalles occasionnels quand elle fut contrôlée par une violente rafale de vent qui balaya la car c'est à Londres que notre scène traîne le long des toits et agite farouchement la faible flamme des lampes qui luttent contre l'obscurité.

Comme vous pouvez le voir, il est essentiellement parfait. La partie la plus importante est de vous assurer que votre liste de mots a été formée à un corpus similaire à ce que vous rencontrerez réellement, sinon les résultats seront très mauvais.


Optimisation

L'implémentation consomme une quantité linéaire de temps et de mémoire, elle est donc raisonnablement efficace. Si vous avez besoin d'accélérations supplémentaires, vous pouvez créer une arborescence de suffixes à partir de la liste Word pour réduire la taille de l'ensemble des candidats.

Si vous devez traiter une très grande chaîne consécutive, il serait raisonnable de diviser la chaîne pour éviter une utilisation excessive de la mémoire. Par exemple, vous pouvez traiter le texte en blocs de 10 000 caractères plus une marge de 1 000 caractères de chaque côté pour éviter les effets de limite. Cela réduira l'utilisation de la mémoire au minimum et n'aura presque certainement aucun effet sur la qualité.

158
Generic Human

Sur la base de l'excellent travail dans le top answer , j'ai créé un package pip pour une utilisation facile.

>>> import wordninja
>>> wordninja.split('derekanderson')
['derek', 'anderson']

Pour installer, exécutez pip install wordninja.

Les seules différences sont mineures. Cela renvoie un list plutôt qu'un str, cela fonctionne dans python3, il inclut la liste de mots et se divise correctement même s'il existe des caractères non alpha (comme les traits de soulignement, les tirets, etc.).

Merci encore à Generic Human!

https://github.com/keredson/wordninja

35
keredson

Voici une solution utilisant la recherche récursive:

def find_words(instring, prefix = '', words = None):
    if not instring:
        return []
    if words is None:
        words = set()
        with open('/usr/share/dict/words') as f:
            for line in f:
                words.add(line.strip())
    if (not prefix) and (instring in words):
        return [instring]
    prefix, suffix = prefix + instring[0], instring[1:]
    solutions = []
    # Case 1: prefix in solution
    if prefix in words:
        try:
            solutions.append([prefix] + find_words(suffix, '', words))
        except ValueError:
            pass
    # Case 2: prefix not in solution
    try:
        solutions.append(find_words(suffix, prefix, words))
    except ValueError:
        pass
    if solutions:
        return sorted(solutions,
                      key = lambda solution: [len(Word) for Word in solution],
                      reverse = True)[0]
    else:
        raise ValueError('no solution')

print(find_words('tableapplechairtablecupboard'))
print(find_words('tableprechaun', words = set(['tab', 'table', 'leprechaun'])))

les rendements

['table', 'Apple', 'chair', 'table', 'cupboard']
['tab', 'leprechaun']
16
unutbu

En utilisant un triestructure de données , qui contient la liste des mots possibles, il ne serait pas trop compliqué de faire ce qui suit:

  1. Pointeur avancé (dans la chaîne concaténée)
  2. Rechercher et stocker le nœud correspondant dans le trie
  3. Si le nœud du trie a des enfants (par exemple, il y a des mots plus longs), passez à 1.
  4. Si le nœud atteint n'a pas d'enfants, une correspondance Word la plus longue s'est produite; ajoutez le mot (stocké dans le nœud ou simplement concaténé pendant la traversée du trie) à la liste des résultats, réinitialisez le pointeur dans le trie (ou réinitialisez la référence) et recommencez
10
miku

La solution d'Unutbu était assez proche mais je trouve le code difficile à lire, et il n'a pas donné le résultat attendu. La solution de Generic Human présente l'inconvénient de nécessiter des fréquences Word. Ne convient pas à tous les cas d'utilisation.

Voici une solution simple utilisant un algorithme Divide and Conquer .

  1. Il essaie de minimiser le nombre de mots Par exemple. find_words('cupboard') renverra ['cupboard'] plutôt que ['cup', 'board'] (en supposant que cupboard, cup et board sont dans le dictionnaire )
  2. La solution optimale est non unique , l'implémentation ci-dessous renvoie une solution . find_words('charactersin') pourrait retourner ['characters', 'in'] ou peut-être retournera ['character', 'sin'] (comme vu ci-dessous). Vous pouvez assez facilement modifier l'algorithme pour retourner toutes les solutions optimales.
  3. Dans cette implémentation, les solutions sont mémorisées afin qu'elles s'exécutent dans un délai raisonnable.

Le code:

words = set()
with open('/usr/share/dict/words') as f:
    for line in f:
        words.add(line.strip())

solutions = {}
def find_words(instring):
    # First check if instring is in the dictionnary
    if instring in words:
        return [instring]
    # No... But maybe it's a result we already computed
    if instring in solutions:
        return solutions[instring]
    # Nope. Try to split the string at all position to recursively search for results
    best_solution = None
    for i in range(1, len(instring) - 1):
        part1 = find_words(instring[:i])
        part2 = find_words(instring[i:])
        # Both parts MUST have a solution
        if part1 is None or part2 is None:
            continue
        solution = part1 + part2
        # Is the solution found "better" than the previous one?
        if best_solution is None or len(solution) < len(best_solution):
            best_solution = solution
    # Remember (memoize) this solution to avoid having to recompute it
    solutions[instring] = best_solution
    return best_solution

Cela prendra environ 5 secondes sur ma machine 3GHz:

result = find_words("thereismassesoftextinformationofpeoplescommentswhichisparsedfromhtmlbuttherearenodelimitedcharactersinthemforexamplethumbgreenappleactiveassignmentweeklymetaphorapparentlytherearethumbgreenappleetcinthestringialsohavealargedictionarytoquerywhetherthewordisreasonablesowhatsthefastestwayofextractionthxalot")
assert(result is not None)
print ' '.join(result)

les masses de reis d'informations textuelles des commentaires des peuples qui sont analysées à partir de html mais il n'y a pas de caractère délimité les pécher par exemple pouce vert Apple métaphore hebdomadaire d'affectation active apparemment il y a pouce vert Apple etc dans la chaîne j'ai également un grand dictionnaire pour demander si le mot est raisonnable, donc quel est le moyen d'extraction le plus rapide

9
Rems

La réponse de https://stackoverflow.com/users/1515832/generic-human est excellente. Mais la meilleure mise en œuvre de ce que j'ai jamais vue a été écrite par Peter Norvig lui-même dans son livre "Beautiful Data".

Avant de coller son code, permettez-moi d'expliquer pourquoi la méthode de Norvig est plus précise (bien qu'un peu plus lente et plus longue en termes de code).

1) Les données sont un peu meilleures - à la fois en termes de taille et de précision (il utilise un nombre de mots plutôt qu'un simple classement) 2) Plus important encore, c'est la logique derrière les n-grammes qui rend vraiment l'approche si précise .

L'exemple qu'il donne dans son livre est le problème de la division d'une chaîne "sitdown". Maintenant, une méthode de non-bigramme de partage de chaîne considérerait p ('sit') * p ('down'), et si cela est inférieur au p ('sitdown') - ce qui sera le cas assez souvent - il ne sera PAS divisé mais nous le voudrions (la plupart du temps).

Cependant, lorsque vous avez le modèle bigram, vous pouvez évaluer p ("asseoir") comme un bigram vs p ("asseoir") et l'ancien gagne. Fondamentalement, si vous n'utilisez pas de bigrammes, cela traite la probabilité des mots que vous divisez comme indépendants, ce qui n'est pas le cas, certains mots sont plus susceptibles d'apparaître l'un après l'autre. Malheureusement, ce sont aussi les mots qui sont souvent collés ensemble dans de nombreux cas et confond le séparateur.

Voici le lien vers les données (ce sont des données pour 3 problèmes distincts et la segmentation n'est qu'un. Veuillez lire le chapitre pour plus de détails): http://norvig.com/ngrams/

et voici le lien vers le code: http://norvig.com/ngrams/ngrams.py

Ces liens sont en place depuis un certain temps, mais je vais quand même copier-coller la partie segmentation du code ici

import re, string, random, glob, operator, heapq
from collections import defaultdict
from math import log10

def memo(f):
    "Memoize function f."
    table = {}
    def fmemo(*args):
        if args not in table:
            table[args] = f(*args)
        return table[args]
    fmemo.memo = table
    return fmemo

def test(verbose=None):
    """Run some tests, taken from the chapter.
    Since the hillclimbing algorithm is randomized, some tests may fail."""
    import doctest
    print 'Running tests...'
    doctest.testfile('ngrams-test.txt', verbose=verbose)

################ Word Segmentation (p. 223)

@memo
def segment(text):
    "Return a list of words that is the best segmentation of text."
    if not text: return []
    candidates = ([first]+segment(rem) for first,rem in splits(text))
    return max(candidates, key=Pwords)

def splits(text, L=20):
    "Return a list of all possible (first, rem) pairs, len(first)<=L."
    return [(text[:i+1], text[i+1:]) 
            for i in range(min(len(text), L))]

def Pwords(words): 
    "The Naive Bayes probability of a sequence of words."
    return product(Pw(w) for w in words)

#### Support functions (p. 224)

def product(nums):
    "Return the product of a sequence of numbers."
    return reduce(operator.mul, nums, 1)

class Pdist(dict):
    "A probability distribution estimated from counts in datafile."
    def __init__(self, data=[], N=None, missingfn=None):
        for key,count in data:
            self[key] = self.get(key, 0) + int(count)
        self.N = float(N or sum(self.itervalues()))
        self.missingfn = missingfn or (lambda k, N: 1./N)
    def __call__(self, key): 
        if key in self: return self[key]/self.N  
        else: return self.missingfn(key, self.N)

def datafile(name, sep='\t'):
    "Read key,value pairs from file."
    for line in file(name):
        yield line.split(sep)

def avoid_long_words(key, N):
    "Estimate the probability of an unknown Word."
    return 10./(N * 10**len(key))

N = 1024908267229 ## Number of tokens

Pw  = Pdist(datafile('count_1w.txt'), N, avoid_long_words)

#### segment2: second version, with bigram counts, (p. 226-227)

def cPw(Word, prev):
    "Conditional probability of Word, given previous Word."
    try:
        return P2w[prev + ' ' + Word]/float(Pw[prev])
    except KeyError:
        return Pw(Word)

P2w = Pdist(datafile('count_2w.txt'), N)

@memo 
def segment2(text, prev='<S>'): 
    "Return (log P(words), words), where words is the best segmentation." 
    if not text: return 0.0, [] 
    candidates = [combine(log10(cPw(first, prev)), first, segment2(rem, first)) 
                  for first,rem in splits(text)] 
    return max(candidates) 

def combine(Pfirst, first, (Prem, rem)): 
    "Combine first and rem results into one (probability, words) pair." 
    return Pfirst+Prem, [first]+rem 

Si vous précompilez la liste de mots dans un DFA (qui sera très lent), le temps nécessaire pour faire correspondre une entrée sera proportionnel à la longueur du chaîne (en fait, un peu plus lentement que de simplement parcourir la chaîne).

Il s'agit en fait d'une version plus générale de l'algorithme trie mentionné précédemment. Je ne le mentionne que pour completeless - pour l'instant, il n'y a pas d'implémentation DFA que vous pouvez simplement utiliser. RE2 fonctionnerait, mais je ne sais pas si les liaisons Python vous permettent de régler la taille que vous autorisez un DFA avant de simplement jeter les données DFA compilées et recherche NFA.

2
Devin Jeanpierre

Voici la réponse acceptée traduite en JavaScript (nécessite node.js et le fichier "wordninja_words.txt" de https://github.com/keredson/wordninja ):

var fs = require("fs");

var splitRegex = new RegExp("[^a-zA-Z0-9']+", "g");
var maxWordLen = 0;
var wordCost = {};

fs.readFile("./wordninja_words.txt", 'utf8', function(err, data) {
    if (err) {
        throw err;
    }
    var words = data.split('\n');
    words.forEach(function(Word, index) {
        wordCost[Word] = Math.log((index + 1) * Math.log(words.length));
    })
    words.forEach(function(Word) {
        if (Word.length > maxWordLen)
            maxWordLen = Word.length;
    });
    console.log(maxWordLen)
    splitRegex = new RegExp("[^a-zA-Z0-9']+", "g");
    console.log(split(process.argv[2]));
});


function split(s) {
    var list = [];
    s.split(splitRegex).forEach(function(sub) {
        _split(sub).forEach(function(Word) {
            list.Push(Word);
        })
    })
    return list;
}
module.exports = split;


function _split(s) {
    var cost = [0];

    function best_match(i) {
        var candidates = cost.slice(Math.max(0, i - maxWordLen), i).reverse();
        var minPair = [Number.MAX_SAFE_INTEGER, 0];
        candidates.forEach(function(c, k) {
            if (wordCost[s.substring(i - k - 1, i).toLowerCase()]) {
                var ccost = c + wordCost[s.substring(i - k - 1, i).toLowerCase()];
            } else {
                var ccost = Number.MAX_SAFE_INTEGER;
            }
            if (ccost < minPair[0]) {
                minPair = [ccost, k + 1];
            }
        })
        return minPair;
    }

    for (var i = 1; i < s.length + 1; i++) {
        cost.Push(best_match(i)[0]);
    }

    var out = [];
    i = s.length;
    while (i > 0) {
        var c = best_match(i)[0];
        var k = best_match(i)[1];
        if (c == cost[i])
            console.log("Alert: " + c);

        var newToken = true;
        if (s.slice(i - k, i) != "'") {
            if (out.length > 0) {
                if (out[-1] == "'s" || (Number.isInteger(s[i - 1]) && Number.isInteger(out[-1][0]))) {
                    out[-1] = s.slice(i - k, i) + out[-1];
                    newToken = false;
                }
            }
        }

        if (newToken) {
            out.Push(s.slice(i - k, i))
        }

        i -= k

    }
    return out.reverse();
}
1
Christian Ayscue

Si vous avez une liste exhaustive des mots contenus dans la chaîne:

Word_list = ["table", "Apple", "chair", "cupboard"]

Utiliser la compréhension de la liste pour parcourir la liste pour localiser le mot et combien de fois il apparaît.

string = "tableapplechairtablecupboard"

def split_string(string, Word_list):

    return ("".join([(item + " ")*string.count(item.lower()) for item in Word_list if item.lower() in string])).strip()


La fonction renvoie une sortie string de mots dans l'ordre de la liste table table Apple chair cupboard

0
zainsharif1

Développer sur @ miku's suggestion d'utiliser un Trie, un ajout uniquement Trie est relativement simple à implémenter dans python:

class Node:
    def __init__(self, is_Word=False):
        self.children = {}
        self.is_Word = is_Word

class TrieDictionary:
    def __init__(self, words=Tuple()):
        self.root = Node()
        for Word in words:
            self.add(Word)

    def add(self, Word):
        node = self.root
        for c in Word:
            node = node.children.setdefault(c, Node())
        node.is_Word = True

    def lookup(self, Word, from_node=None):
        node = self.root if from_node is None else from_node
        for c in Word:
            try:
                node = node.children[c]
            except KeyError:
                return None

        return node

Nous pouvons alors construire un dictionnaire basé sur Trie à partir d'un ensemble de mots:

dictionary = {"a", "pea", "nut", "peanut", "but", "butt", "butte", "butter"}
trie_dictionary = TrieDictionary(words=dictionary)

Ce qui produira un arbre qui ressemble à ceci (* indique le début ou la fin d'un mot):

* -> a*
 \\\ 
  \\\-> p -> e -> a*
   \\              \-> n -> u -> t*
    \\
     \\-> b -> u -> t*
      \\             \-> t*
       \\                 \-> e*
        \\                     \-> r*
         \
          \-> n -> u -> t*

Nous pouvons l'incorporer dans une solution en la combinant avec une heuristique sur la façon de choisir les mots. Par exemple, nous pouvons préférer des mots plus longs à des mots plus courts:

def using_trie_longest_Word_heuristic(s):
    node = None
    possible_indexes = []

    # O(1) short-circuit if whole string is a Word, doesn't go against longest-Word wins
    if s in dictionary:
        return [ s ]

    for i in range(len(s)):
        # traverse the trie, char-wise to determine intermediate words
        node = trie_dictionary.lookup(s[i], from_node=node)

        # no more words start this way
        if node is None:
            # iterate words we have encountered from biggest to smallest
            for possible in possible_indexes[::-1]:
                # recurse to attempt to solve the remaining sub-string
                end_of_phrase = using_trie_longest_Word_heuristic(s[possible+1:])

                # if we have a solution, return this Word + our solution
                if end_of_phrase:
                    return [ s[:possible+1] ] + end_of_phrase

            # unsolvable
            break

        # if this is a leaf, append the index to the possible words list
        Elif node.is_Word:
            possible_indexes.append(i)

    # empty string OR unsolvable case 
    return []

Nous pouvons utiliser cette fonction comme ceci:

>>> using_trie_longest_Word_heuristic("peanutbutter")
[ "peanut", "butter" ]

Comme nous conservons notre position dans le Trie lorsque nous recherchons des mots de plus en plus longs, nous parcourons le trie au plus une fois par solution possible (plutôt que 2 fois pour peanut: pea, peanut). Le dernier court-circuit nous évite de marcher dans le pire des cas dans les cordes.

Le résultat final n'est qu'une poignée d'inspections:

'peanutbutter' - not a Word, go charwise
'p' - in trie, use this node
'e' - in trie, use this node
'a' - in trie and Edge, store potential Word and use this node
'n' - in trie, use this node
'u' - in trie, use this node
't' - in trie and Edge, store potential Word and use this node
'b' - not in trie from `peanut` vector
'butter' - remainder of longest is a Word

Un avantage de cette solution réside dans le fait que vous savez très rapidement si des mots plus longs existent avec un préfixe donné, ce qui évite d'avoir à tester de manière exhaustive les combinaisons de séquences par rapport à un dictionnaire. Cela rend également l'accès à une réponse unsolvable relativement bon marché par rapport aux autres implémentations.

Les inconvénients de cette solution sont une grande empreinte mémoire pour le trie et le coût de construction du trie à l'avance.

0
Matthew Story

Pour la langue allemande, il y a CharSplit qui utilise l'apprentissage automatique et fonctionne assez bien pour les chaînes de quelques mots.

https://github.com/dtuggener/CharSplit

0
Karl Adler

Basé sur la solution d'unutbu, j'ai implémenté une version Java:

private static List<String> splitWordWithoutSpaces(String instring, String suffix) {
    if(isAWord(instring)) {
        if(suffix.length() > 0) {
            List<String> rest = splitWordWithoutSpaces(suffix, "");
            if(rest.size() > 0) {
                List<String> solutions = new LinkedList<>();
                solutions.add(instring);
                solutions.addAll(rest);
                return solutions;
            }
        } else {
            List<String> solutions = new LinkedList<>();
            solutions.add(instring);
            return solutions;
        }

    }
    if(instring.length() > 1) {
        String newString = instring.substring(0, instring.length()-1);
        suffix = instring.charAt(instring.length()-1) + suffix;
        List<String> rest = splitWordWithoutSpaces(newString, suffix);
        return rest;
    }
    return Collections.EMPTY_LIST;
}

Entrée:"tableapplechairtablecupboard"

Sortie:[table, Apple, chair, table, cupboard]

Entrée:"tableprechaun"

Sortie:[tab, leprechaun]

0

Il semble qu'un retour en arrière assez banal suffira. Commencez au début de la chaîne. Scannez à droite jusqu'à ce que vous ayez un mot. Ensuite, appelez la fonction sur le reste de la chaîne. La fonction renvoie "faux" si elle balaye complètement vers la droite sans reconnaître un mot. Sinon, retourne le mot trouvé et la liste des mots retournés par l'appel récursif.

Exemple: "ananas". Trouve "tab", puis "bond", mais pas de mot en "ple". Aucun autre mot dans "leapple". Trouve "table", puis "app". "le" n'est pas un mot, essaie donc Apple, reconnaît, revient.

Pour obtenir le plus de temps possible, continuez, n'émettant (plutôt que renvoyant) des solutions correctes; ensuite, choisissez l'optimal selon le critère que vous choisissez (maxmax, minmax, average, etc.)

0
Patrick87