53 votes

Comment puis-je partager de multiples rejoint mots?

J'ai un tableau de 1000 entrées, avec des exemples ci-dessous:

wickedweather
liquidweather
driveourtrucks
gocompact
slimprojector

Je voudrais être en mesure de séparer ces dans leurs mots, comme:

wicked weather
liquid weather
drive our trucks
go compact
slim projector

J'espérais une expression régulière ma faire le tour. Mais, car il n'y a pas de limite à arrêter, ni est-il une sorte de capitalisation que je pourrais peut-être la clé, je pense, une sorte de référence à un dictionnaire peut être nécessaire?

Je suppose que cela pourrait être fait à la main, mais pourquoi - quand il peut être fait avec code! =) Mais ce qui a déconcerté les moi. Des idées?

82voto

Darius Bacon Points 9741

L' algorithme de Viterbi est beaucoup plus rapide. Il calcule les mêmes scores que la recherche récursive dans Dmitry la réponse ci-dessus, mais en O(n) fois. (Dmitry la recherche prend du temps exponentiel; Viterbi t-il par la programmation dynamique.)

import re
from itertools import groupby

def viterbi_segment(text):
    probs, lasts = [1.0], [0]
    for i in range(1, len(text) + 1):
        prob_k, k = max((probs[j] * word_prob(text[j:i]), j)
                        for j in range(max(0, i - max_word_length), i))
        probs.append(prob_k)
        lasts.append(k)
    words = []
    i = len(text)
    while 0 < i:
        words.append(text[lasts[i]:i])
        i = lasts[i]
    words.reverse()
    return words, probs[-1]

def word_prob(word): return dictionary.get(word, 0) / total
def words(text): return re.findall('[a-z]+', text.lower()) 
dictionary = dict((w, len(list(ws)))
                  for w, ws in groupby(sorted(words(open('big.txt').read()))))
max_word_length = max(map(len, dictionary))
total = float(sum(dictionary.values()))

Tester:

>>> viterbi_segment('wickedweather')
(['wicked', 'weather'], 5.1518198982768158e-10)
>>> ' '.join(viterbi_segment('itseasyformetosplitlongruntogetherblocks')[0])
'its easy for me to split long run together blocks'

Pour être pratique, vous souhaiterez probablement quelques améliorations:

  • Ajouter journaux des probabilités, ne multipliez pas les probabilités. Cela évite à virgule flottante underflow.
  • Des apports en général utiliser des mots qui ne sont pas dans votre corpus. Ces sous-chaînes doit être affecté d'une probabilité non nulle que les mots, ou vous vous retrouvez avec pas de solution ou d'une mauvaise solution. (C'est aussi vrai pour les ci-dessus exponentielle de l'algorithme de recherche.) Cette probabilité doit être siphonné les corpus de mots probabilités et distribué de manière plausible, parmi toutes les autres propositions de mots: le thème général est connu comme le lissage statistique des modèles de langage. (Vous pouvez obtenir une assez rude hacks, cependant). C'est là que le O(n) algorithme de Viterbi souffle de l'algorithme de recherche, en raison de considérer le non-corpus de mots explose le facteur de branchement.

34voto

SquareCog Points 12947

Un homme de le faire?

farsidebag
loin sidebag
la face cachée sac
côté sac

Non seulement vous avez à utiliser un dictionnaire, vous pourriez avoir à utiliser une approche statistique pour comprendre ce qui est le plus probable (ou, à dieu ne plaise, un HMM pour votre choix de langue...)

Pour savoir comment faire des statistiques qui pourraient vous être utiles, je vous tourner vers le Dr Peter Norvig, qui s'attaque à un autre, mais le problème connexe de la vérification orthographique dans 21 lignes de code: http://norvig.com/spell-correct.html

(il ne triche un peu en pliant chaque boucle for en une seule ligne.. mais quand même).

Mise à jour de Ce est resté coincé dans ma tête, j'ai donc eu à la naissance il aujourd'hui. Ce code n'similaire split à celui décrit par Robert Pari, mais ensuite il ordonne les résultats basés sur la fréquence des mots dans le dictionnaire fichier (qui est maintenant prévu pour être certains de texte représentant de votre domaine ou de l'anglais en général. J'ai utilisé big.txt de Norvig, ci-dessus, et catted un dictionnaire, pour couvrir des mots manquants).

Une combinaison de deux mots sera la plupart du temps battre une combinaison de 3 mots, à moins que la différence de fréquence est énorme.


J'ai posté ce code avec quelques modifications mineures sur mon blog

http://squarecog.wordpress.com/2008/10/19/splitting-words-joined-into-a-single-string/ et a également écrit un peu plus sur l'underflow bug dans ce code.. j'ai été tenté de juste tranquillement le corriger, mais pensé que cela peut aider certaines personnes qui n'ont pas vu le journal de truc avant: http://squarecog.wordpress.com/2009/01/10/dealing-with-underflow-in-joint-probability-calculations/


Sortie sur vos mots, plus un peu de mes propres -- remarquez ce qui se passe avec "orcore":

perl splitwords.pl big.txt mots
answerveal: 2 possibilités
 - réponse de veau
 - réponse ve al

wickedweather: 4 possibilités
 - intempéries
 - wicked nous à son
 - mèche ed météo
 - mèche ed nous à son

liquidweather: 6 possibilités
 - liquide de la météo
 - liquide de nous à son
 - li quid de la météo
 - li quid nous à son
 - li qu id météo
 - li qu id, nous sommes à son

driveourtrucks: 1 possibilités
 nos camions

gocompact: 1 possibilités
 - aller compact

slimprojector: 2 possibilités
 - projecteur slim
 - mince, projet ou

orcore: 3 possibilités
 - ou core
 - ou co re
 - orc de minerai de

Code:

#!/usr/bin/env perl

use strict;
use warnings;

sub find_matches($);
sub find_matches_rec($\@\@);
sub find_word_seq_score(@);
sub get_word_stats($);
sub print_results($@);
sub Usage();

our(%DICT,$TOTAL);
{
  my( $dict_file, $word_file ) = @ARGV;
  ($dict_file && $word_file) or die(Usage);

  {
    my $DICT;
    ($DICT, $TOTAL) = get_word_stats($dict_file);
    %DICT = %$DICT;
  }

  {
    open( my $WORDS, '<', $word_file ) or die "unable to open $word_file\n";

    foreach my $word (<$WORDS>) {
      chomp $word;
      my $arr = find_matches($word);


      local $_;
      # Schwartzian Transform
      my @sorted_arr =
        map  { $_->[0] }
        sort { $b->[1] <=> $a->[1] }
        map  {
          [ $_, find_word_seq_score(@$_) ]
        }
        @$arr;


      print_results( $word, @sorted_arr );
    }

    close $WORDS;
  }
}


sub find_matches($){
    my( $string ) = @_;

    my @found_parses;
    my @words;
    find_matches_rec( $string, @words, @found_parses );

    return  @found_parses if wantarray;
    return \@found_parses;
}

sub find_matches_rec($\@\@){
    my( $string, $words_sofar, $found_parses ) = @_;
    my $length = length $string;

    unless( $length ){
      push @$found_parses, $words_sofar;

      return @$found_parses if wantarray;
      return  $found_parses;
    }

    foreach my $i ( 2..$length ){
      my $prefix = substr($string, 0, $i);
      my $suffix = substr($string, $i, $length-$i);

      if( exists $DICT{$prefix} ){
        my @words = ( @$words_sofar, $prefix );
        find_matches_rec( $suffix, @words, @$found_parses );
      }
    }

    return @$found_parses if wantarray;
    return  $found_parses;
}


## Just a simple joint probability
## assumes independence between words, which is obviously untrue
## that's why this is broken out -- feel free to add better brains
sub find_word_seq_score(@){
    my( @words ) = @_;
    local $_;

    my $score = 1;
    foreach ( @words ){
        $score = $score * $DICT{$_} / $TOTAL;
    }

    return $score;
}

sub get_word_stats($){
    my ($filename) = @_;

    open(my $DICT, '<', $filename) or die "unable to open $filename\n";

    local $/= undef;
    local $_;
    my %dict;
    my $total = 0;

    while ( <$DICT> ){
      foreach ( split(/\b/, $_) ) {
        $dict{$_} += 1;
        $total++;
      }
    }

    close $DICT;

    return (\%dict, $total);
}

sub print_results($@){
    #( 'word', [qw'test one'], [qw'test two'], ... )
    my ($word,  @combos) = @_;
    local $_;
    my $possible = scalar @combos;

    print "$word: $possible possibilities\n";
    foreach (@combos) {
      print ' -  ', join(' ', @$_), "\n";
    }
    print "\n";
}

sub Usage(){
    return "$0 /path/to/dictionary /path/to/your_words";
}

8voto

Robert Gamble Points 41984

Le meilleur outil pour le travail ici est la récursivité, pas des expressions régulières. L'idée de base est de commencer à partir du début de la chaîne à la recherche pour un mot, puis prendre le reste de la chaîne et de chercher un autre mot, et ainsi de suite jusqu'à la fin de la chaîne est atteint. Une solution récursive est naturel, puisque les retours en arrière qui doit arriver lors du reste de la chaîne ne peut pas être décomposé en un ensemble de mots. La solution ci-dessous utilise un dictionnaire pour déterminer ce qu'est un mot et imprime les solutions qu'il trouve (certaines chaînes peut être décomposée en de multiples jeux de mots, par exemple wickedweather pourrait être analysée comme "méchants nous à son"). Si vous voulez juste un jeu de mots, vous aurez besoin de déterminer les règles pour la sélection du meilleur jeu, peut-être en sélectionnant la solution avec le moins grand nombre de mots ou par la fixation d'un minimum de longueur de mot.

#!/usr/bin/perl

use strict;

my $WORD_FILE = '/usr/share/dict/words'; #Change as needed
my %words; # Hash of words in dictionary

# Open dictionary, load words into hash
open(WORDS, $WORD_FILE) or die "Failed to open dictionary: $!\n";
while (<WORDS>) {
  chomp;
  $words{lc($_)} = 1;
}
close(WORDS);

# Read one line at a time from stdin, break into words
while (<>) {
  chomp;
  my @words;
  find_words(lc($_));
}

sub find_words {
  # Print every way $string can be parsed into whole words
  my $string = shift;
  my @words = @_;
  my $length = length $string;

  foreach my $i ( 1 .. $length ) {
    my $word = substr $string, 0, $i;
    my $remainder = substr $string, $i, $length - $i;
    # Some dictionaries contain each letter as a word
    next if ($i == 1 && ($word ne "a" && $word ne "i"));

    if (defined($words{$word})) {
      push @words, $word;
      if ($remainder eq "") {
        print join(' ', @words), "\n";
        return;
      } else {
        find_words($remainder, @words);
      }
      pop @words;
    }
  }

  return;
}

4voto

Andy Lester Points 34051

Certaines personnes, lorsqu'ils sont confrontés à un problème, pense que "je sais, je vais utiliser des expressions régulières." Maintenant, ils ont deux problèmes.

-- JWZ

4voto

Greg Hewgill Points 356191

Je pense que vous êtes en droit de penser que ce n'est pas vraiment un travail pour une expression régulière. Je l'approche de cette utilisation du dictionnaire idée look pour le plus long préfixe d'un mot dans le dictionnaire. Lorsque vous trouvez que, hacher l'enlever et faire de même avec le reste de la chaîne.

La méthode ci-dessus est soumis à l'ambiguïté, par exemple "drivereallyfast" faudrait d'abord trouver "pilote", puis ont de la difficulté avec "eallyfast". Donc, vous devez également avoir à faire un retour en arrière si vous avez couru dans cette situation. Ou, puisque vous n'avez pas que beaucoup de cordes à split, il suffit de faire à la main ceux qui échouent le système automatisé de split.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X