Je cherchais un moyen élégant et efficace de découper une chaîne en sous-chaînes d'une longueur donnée en Ruby.
Jusqu'à présent, le mieux que j'ai pu trouver est celui-ci:
def chunk(string, size)
(0..(string.length-1)/size).map{|i|string[i*size,size]}
end
>> chunk("abcdef",3)
=> ["abc", "def"]
>> chunk("abcde",3)
=> ["abc", "de"]
>> chunk("abc",3)
=> ["abc"]
>> chunk("ab",3)
=> ["ab"]
>> chunk("",3)
=> []
Vous voudrez peut- chunk("", n)
être revenir [""]
au lieu de []
. Si tel est le cas, ajoutez simplement ceci comme première ligne de la méthode:
return [""] if string.empty?
Recommanderiez-vous une meilleure solution?
Éditer
Merci à Jeremy Ruten pour cette solution élégante et efficace: [edit: PAS efficace!]
def chunk(string, size)
string.scan(/.{1,#{size}}/)
end
Éditer
La solution string.scan prend environ 60 secondes pour découper 512k en morceaux de 1k 10000 fois, par rapport à la solution d'origine basée sur les tranches qui ne prend que 2,4 secondes.
Réponses:
Utilisez
String#scan
:>> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{4}/) => ["abcd", "efgh", "ijkl", "mnop", "qrst", "uvwx"] >> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{1,4}/) => ["abcd", "efgh", "ijkl", "mnop", "qrst", "uvwx", "yz"] >> 'abcdefghijklmnopqrstuvwxyz'.scan(/.{1,3}/) => ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]
la source
/.
bit de celui-ci signifie qu'elle inclura tous les caractères SAUF les retours à la ligne\n
. Si vous souhaitez inclure des nouvelles lignes, utilisezstring.scan(/.{4}/m)
Voici une autre façon de procéder:
"abcdefghijklmnopqrstuvwxyz".chars.to_a.each_slice(3).to_a.map {|s| s.to_s }
=> ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]
la source
"abcdefghijklmnopqrstuvwxyz".chars.each_slice(3).map(&:join)
Je pense que c'est la solution la plus efficace si vous savez que votre chaîne est un multiple de la taille du morceau
def chunk(string, size) (string.length / size).times.collect { |i| string[i * size, size] } end
et pour les pièces
def parts(string, count) size = string.length / count count.times.collect { |i| string[i * size, size] } end
la source
string.length / size
par(string.length + size - 1) / size
- ce modèle est courant dans le code C qui doit gérer la troncature d'entiers.Voici une autre solution pour un cas légèrement différent, lors du traitement de grandes chaînes et il n'est pas nécessaire de stocker tous les blocs à la fois. De cette façon, il stocke un seul morceau à la fois et fonctionne beaucoup plus rapidement que le découpage de chaînes:
io = StringIO.new(string) until io.eof? chunk = io.read(chunk_size) do_something(chunk) end
la source
Errno::EINVAL
erreurs commeInvalid argument @ io_fread
etInvalid argument @ io_write
.J'ai fait un petit test qui découpe environ 593 Mo de données en 18991 morceaux de 32 Ko. Votre version slice + map a fonctionné pendant au moins 15 minutes en utilisant 100% du processeur avant d'appuyer sur ctrl + C. Cette version utilisant String # unpack s'est terminée en 3,6 secondes:
def chunk(string, size) string.unpack("a#{size}" * (string.size/size.to_f).ceil) end
la source
test.split(/(...)/).reject {|v| v.empty?}
Le rejet est nécessaire car il inclut sinon l'espace vide entre les ensembles. Mon regex-fu n'est pas tout à fait à la hauteur de voir comment résoudre ce problème du haut de ma tête.
la source
Une meilleure solution qui prend en compte la dernière partie de la chaîne qui pourrait être inférieure à la taille du morceau:
def chunk(inStr, sz) return [inStr] if inStr.length < sz m = inStr.length % sz # this is the last part of the string partial = (inStr.length / sz).times.collect { |i| inStr[i * sz, sz] } partial << inStr[-m..-1] if (m % sz != 0) # add the last part partial end
la source
Avez-vous d'autres contraintes en tête? Sinon, je serais terriblement tenté de faire quelque chose de simple comme
[0..10].each { str[(i*w),w] }
la source
text.scan(/.{1,4}/m)
Résout juste le problèmela source