Je souhaite rechercher des sous-chaînes dans une chaîne donnée. Chaque fois que la sous-chaîne est incluse dans la chaîne saisie, je l'ajoute à un tableau. En fin de compte, je veux compter
ce tableau pour obtenir le nombre de fois où chaque sous-chaîne apparaît.
Le problème est que la sous-chaîne du dictionnaire dans mon code n'est ajoutée qu'une seule fois à new_array
.
Par exemple:
def substrings(word, array) new_array = [] array.each do |index| if word.downcase.include? (index) new_array << index end end puts new_array.tally end dictionary = ["below", "down","go","going","horn","how","howdy","it","i","low","own","part","partner","sit"] substrings("go going", dictionary)
Devrait afficher:
{"go"=>1, "going"=>1, "i"=>1}
mais j'obtiens
{"go"=>2, "going"=>1, "i"=>1}
Voici mon code:
dictionary = ["below", "down","go","going","horn","how","howdy","it","i","low","own","part","partner","sit"] substrings("go going", dictionary)
7 Réponses :
Vous pouvez utiliser scan pour compter combien de fois chaque sous-chaîne apparaît.
def substrings(word, array) output = {} array.each do |index| count_substring_appears = word.scan(index).size if count_substring_appears > 0 output[index] = count_substring_appears end end output end
Seuls les mots "go", "going" et "i" de votre dictionnaire sont des sous-chaînes de votre phrase. Chacun de ces mots n'apparaît qu'une seule fois dans le dictionnaire.
Donc new_array
contient ["go", "going", "i"]
qui exactement {"go" => 1, "going" => 1, "i" => 1}
.
Je suppose que vous vous attendiez à ce que go
soit deux fois parce que c'est deux fois dans votre phrase. Dans ce cas, vous pouvez changer votre méthode en
def substrings(word, array) new_array = [] array.each do |index| word.scan(/#{index}/).each { new_array << index } end puts new_array.tally end
word.scan (/ # {index} /)
renvoie chaque occurrence de sous-chaîne dans votre phrase .
La solution de @byakugie est encore plus propre
C'est plus propre, mais je me retrouve à aimer le vôtre. J'apprends juste le rubis et le vôtre est le plus facile à digérer pour moi en ce moment. Merci!
@Maniaci Je suis content si vous le trouvez utile. Je voulais remplacer le moins possible, juste pour mettre en évidence le problème dans la méthode d'origine.
Vous devez compter le nombre de fois qu'une chaîne apparaît dans l'index, utilisez donc scan
:
def substrings(word, array) hash = {} array.each do |index| if word.downcase.include? (index) new_hash = {index => word.scan(/#{index}/).length}; hash.merge!(new_hash) end end puts hash end
Selon la taille de votre dictionnaire.
Vous pouvez simplement mapper tous les éléments avec leur nombre d'occurrences lorsque la sous-chaîne existe dans le mot.
dictionary.map {|w| [w,word.scan(w).size] if word.include?(w)}.compact.to_h
Je l'aime mieux que le mien. "Élégant!"
@theTinMan merci et honnêtement si vous regardez mes modifications, mon original était similaire mais pire que le vôtre.
Si je comprends bien qu'on nous donne un tableau dictionnaire
de mots ne contenant aucun espace, et une chaîne str
, et que nous devons construire un hachage dont les clés sont des éléments de dictionary
et dont les valeurs sont égales au nombre de sous-chaînes 1 non superposées de str
pour lesquelles la clé est une sous-chaîne. Le hachage retourné doit exclure les clés ayant des valeurs de zéro.
Cette réponse résout le cas où, dans:
substr_counts(str) #=> {"l"=>3, "o"=>2, "w"=>2, "n"=>3, "e"=>3, "r"=>3, "lo"=>2, # ... # "wnliest"=>1, "lownlies"=>1, "ownliest"=>1, "lownliest"=>1} substr_counts(str).size #=> 109
dictionary
est grand, str n'est pas trop volumineux (la signification que j'élaborerai plus tard) et l'efficacité est importante.
Nous commençons par définir une méthode d'aide, dont le but deviendra clair. p >
str = "lowner partnership lownliest" cover_count(str, dictionary) #=> {"i"=>2, "low"=>2, "own"=>2, "part"=>1, "partner"=>1}
Pour l'exemple donné dans la question,
cover_count("go going", dictionary) #=> {"go"=>2, "going"=>1, "i"=>1}
Comme on l'a vu, cette méthode décompose str
en words, calcule chaque sous-chaîne de chaque mot et renvoie un hachage dont les clés sont les sous-chaînes et dont les valeurs sont le nombre total de sous-chaînes non superposées dans tous les mots qui contiennent cette sous-chaîne.
Le hachage souhaité peut maintenant être construit rapidement.
dictionary = ["below", "down", "go", "going", "horn", "how", "howdy", "it", "i", "low", "own", "part", "partner", "sit"]
def cover_count(str, dictionary) h = substr_counts(str) dictionary.each_with_object({}) do |word,g| g[word] = h[word] if h.key?(word) end end
p >
substr_counts("go going") #=> {"g"=>3, "o"=>2, "go"=>2, "i"=>1, "n"=>1, "oi"=>1, "in"=>1, "ng"=>1, # "goi"=>1, "oin"=>1, "ing"=>1, "goin"=>1, "oing"=>1, "going"=>1}
Un autre exemple:
def substr_counts(str) str.split.each_with_object(Hash.new(0)) do |word,h| (1..word.size).each do |sub_len| (0..word.size-sub_len).each do |start_idx| h[word[start_idx,sub_len]] += 1 end end end end
Ici,
substrings(str, dictionary)
Il y a un compromis évident ici. Si str
est long, et surtout s'il contient des mots longs 2 , il faudra trop de temps pour construire h
pour justifier les économies de ne pas avoir pour déterminer, pour chaque mot du dictionnaire
, si ce mot est contenu dans chaque mot de str
. Si, cependant, cela vaut la peine de construire h
, le gain de temps global pourrait être substantiel.
1. Par "sans chevauchement", j'entends que si str
est égal à 'bobobo'
, il contient une, et non deux, sous-chaînes 'bobo'
. sup>
2. substr_counts ("antidisestablishmentarianism"). size # => 385
, pas si mal.
Une autre option consiste à utiliser Array # product après la séparation du mot, vous pouvez donc utiliser Enumerable # Tally comme vous le souhaitez:
word = "go going" word.split.product(dictionary).select { |a, b| a.include? b }.map(&:last).tally #=> {"go"=>2, "going"=>1, "i"=>1}
Il ne produit pas la même chose lorsque word = "gogoing"
, car il est divisé en un tableau d'éléments. Donc, je ne peux pas dire si c'est le comportement que vous recherchez.
Je commencerais par ceci:
dictionary = %w[down go going it i] target = 'go going' dictionary.flat_map { |w| target.scan(Regexp.new(w, Regexp::IGNORECASE)) }.reject(&:empty?).tally # => {"go"=>2, "going"=>1, "i"=>1}
Astuce: Pour les longues listes de mots individuels, utilisez
% w [a b c ...]
qui vous donne le même résultat sans tous les guillemets et virgules supplémentaires.