Changer la casse d'une chaine en se basant sur une autre

a marqué ce sujet comme résolu.

Est-qu'il y'a une methode prédéfinie pour realiser cette operation ?

À ma connaissance, non. Mais il y a str.islower, str.isupper, str.upper et str.lower.

Je ne sais pas si c'est efficace, mais avec des listes en intention, je ferai ça :

1
2
3
4
a="ZestE"
b="sVoIR"
c = "".join([a[i].upper() if elmt.isupper() else a[i].lower() for i,elmt in enumerate(b)])
# 'zEsTE'

Sans vérification de longueur ni rien. :-°

+0 -0

Salut,

Ça m'étonnerai qu'il y ai une méthode prédéfini pour ça parce que ce n'est pas très commun comme opération. Ce que je ferai si j'avais ce problème, c'est de faire une liste de la case de chaque lettre dans le second mot avec des booléens. Ensuite, tu as toutes tes infos pour modifier la chaîne de caractères n°1.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def use_case(a, b):
    cases = []
    res = ''

    for i in b:
        cases.append(True if i.isupper() else False)

    for string, case in zip(a, cases):
        res += string.upper() if case else string.lower()

    return res

la concatenation est plus efficace que join

Très étonné de cette affirmation, j'ai testé. Deux chaines de même longueur tapées au pif, ('edcrfvtgbyhnujikolpm' et 'GtHyDrrrGhhJUKKhgfDd'), un simple time(), un million de fois la méthode que je propose, on sort le temps de calcul intermédiaire pas d'affichage). Idem pour ta fonction.

Résultat, plus de 6 secondes pour ta méthode, moins de 5,8 pour join. Pour un million de chaine, donc on s'en fout en pratique. ^^ Mais de mémoire, la concaténation en python est une opération peu efficace.

+1 -0

Je viens de tester, la tienne est la plus rapide.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# pour 1000 runs
# pas de print
# avec ('ZestE', 'sVoIR')
gabbro: 3.3090016961473228
tleb: 3.4514151646754025
taoufik07: 2.573053158375427

# pour 1000 runs
# pas de print
# avec ('aOIREefpoieFiefpoefpIGGzregpioFZA', 'pmfzgpippFZAEPIOpoefofFFFdlmqFEE')
gabbro: 12.60344634769156
tleb: 15.760423850440814
taoufik07: 11.715182382434627

Je ne sais pas si c'est efficace, mais avec des listes en intention, je ferai ça :

1
2
3
4
a="ZestE"
b="sVoIR"
c = "".join([a[i].upper() if elmt.isupper() else a[i].lower() for i,elmt in enumerate(b)])
# 'zEsTE'

Gabbro

Légèrement mieux, et pas de souci de longueur :

1
c = "".join(x.upper() if y.isupper() else x.lower() for x, y in zip(a, b))

Résultat, plus de 6 secondes pour ta méthode, moins de 5,8 pour join. Pour un million de chaine, donc on s'en fout en pratique. ^^ Mais de mémoire, la concaténation en python est une opération peu efficace.

Gabbro

Vous avez raison quand on depasse quelques centaines de milliers la complexité de la concatination devient O(n^2) (mais moi j'ai a peine 100 caractere)

Voici un lien qui va surement vous intéresser : https://waymoot.org/home/python_string/

+0 -0

@tleb : tes benchmarks sont très peu représentatifs, et il est notoire que join doit être préféré, même si la concaténation est optimisée dans certains cas (source).

EDIT: au passage, si la performance est vraiment importante, on peut légèrement améliorer mon code ci-dessus en remplaçant le générateur par une liste :

1
"".join([x.upper() if y.isupper() else x.lower() for x, y in zip(a, b)])
+3 -0

Vous mesurez la concaténation de deux chaines, essayez avec 1000 et le résultat non seulement en temps, mais également en utilisation de mémoire, sera sans appel.

Et pour expliquer la différence, a + b c'est :

  • une allocation dynamique pour le resultat,
  • 1 copie de a,
  • 1 copie de b

Prenons maintenant a + b + c = (a + b) + c (sans optimisation particulière) :

  • 1 allocation,
  • 1 copie de a,
  • 1 copie de b,
  • 1 allocation,
  • 1 copie de a + b, soit :
    • 1 copie de a,
    • 1 copie de b,
  • 1 copie de c
  • 1 suppression du resultat intermédiaire a + b

Soit deux allocations et 5 copies.

Prenons maintenant ''.join((a, b, c)) :

  • 1 allocation,
  • copie de a,
  • copie de b,
  • copie de c

Soit 1 allocation et 3 copies.

Extrapolez ça à la concaténation de N chaînes :

  • + : N-1 allocations de mémoire, (N(N+1)/2)+1 (soit du O(N^2)) copies, N-2 résultats intermédiaires supprimés par le GC.
  • join: 1 allocation de mémoire, N copies et 0 résultat intermédiaire.

Maintenant il y a moyen que Python optimise les longues sommes de chaînes, sauf que dans la pratique ces longues concaténations sont faites sur plusieurs lignes avec des étapes intermédiaires (des suites de res +=...), et que Python n'optimise certainement pas ce cas.

Moralité, réalisez toujours la concaténation de N > 2 chaînes avec join et interdisez-vous l'opérateur +. Votre RAM et le garbage collector de Python vous diront merci.

+5 -0

Voici un lien qui va surement vous intéresser : https://waymoot.org/home/python_string/

taoufik07

Ce lien est obsolète, et ses conclusions sont exactement l'inverse des tiennes.

Il est obsolète parce qu'il utilise une version antédiluvienne de Python dans laquelle les strings sont des chaînes d'octets (donc avec des éléments à taille fixe) et non des chaînes unicode, donc il se permet l'utilisation d'arrays ou pire, d'une syntaxe qui n'existe plus depuis une éternité, et ses conclusions sont l'inverse des tiennes puisqu'il mesure le nombre de concaténations qu'il a réussi à effectuer en un temps fixe (ce qui est une méthode beaucoup plus adaptée pour profiler des opérations de cet ordre de grandeur), plutôt que le temps passé à effectuer un nombre fixe de concaténations.

+0 -0

C'est connu que la concaténation est notoirement inefficace pour un grand nombre de chaînes. Ce n'est pas seulement vrai pour python, mais aussi pour Java, JavaScript, PHP, C#, C++ avec std::string. Avec l'opérateur + ou += c'est fondamentalement toujours la même chose, ce que décrit Nohar.

Dans tous ces langages on recommande fortement soit Array/list.join, soit StringBuffer/StringBuilder selon ce qui existe :

  • Python: join comme montré ci-dessus
  • Java: StringBuilder (ou anciennement StringBuffer)
  • C#: De mémoire la classe s'appelle StringBuffer
  • JavaScript: Array.join
  • PHP: implode
  • C++: boost::join ou std::ostringstream

En Java si je me rappelle bien StringBuilder est plus de 10 fois plus rapide que du += à gogo dans des boucles, en plus de nécéssiter beaucoup moins de mémoire.

IL doit probablement exister un équivalent à StringBuilder/StringBuffer en python.

+0 -0

D'accord, j'ai compris maintenant pourquoi join est plus rapide qu'une concaténation. Cependant, j'ai testé chez moi et ce que j'ai trouvé montre le contraire. Je suis sûr qu'il y a une explication logique et que mon exemple n'est pas représentatif.

 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
import time


class Timer:
    def __init__(self, msg=''):
        self.msg = msg

    def __enter__(self):
        self.start = time.perf_counter()

    def __exit__(self, *args):
        end = time.perf_counter()
        diff = (end - self.start) * 1000

        print('{}: {}'.format(self.msg, diff))


ls = list(range(100000))

with Timer('concat'):
    for i in ls:
        'foo' + 'bar' + 'baz' + 'nurf' + 'urf'

with Timer('join'):
    for i in ls:
        'foo'.join(('bar', 'baz', 'nurf', 'urf',))

with Timer('format'):
    for i in ls:
        '{}{}{}{}{}'.format('foo', 'bar', 'baz', 'nurf', 'urf')

"""
concat: 8.812092999960441
join: 47.03898500019932
format: 165.92073899983006
"""

Que-ce qui n'est pas représentatif dans mon test ?

Je comprends plus rien là. J'ai réessayé une dizaine de fois, à chaque fois les mêmes résultats. Il y a une sorte de cache qui s'applique ?

 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
import time

# class Timer...

ls = list(range(100000))

with Timer('concat'):
    for i in ls:
        'foo' + 'bar' + 'baz' + 'nurf' + 'urf'

with Timer('build tuple'):
    for i in ls:
        ('bar', 'baz', 'nurf', 'urf',)

words = ('bar', 'baz', 'nurf', 'urf',)
with Timer('join without building'):
    for i in ls:
        'foo'.join(words)

with Timer('join with building'):
    for i in ls:
        'foo'.join(('bar', 'baz', 'nurf', 'urf',))

with Timer('format'):
    for i in ls:
        '{}{}{}{}{}'.format('foo', 'bar', 'baz', 'nurf', 'urf')

"""
concat: 10.9051580002415
build tuple: 9.084231000088039
join without building: 52.41280000154802
join with building: 47.69759500049986
format: 166.3222829993174
"""

@tleb : Il y a le module timeit pour faire ce que tu fais. Et puis construire une liste énorme juste pour itérer, WTF…

Sinon, ce serait bien de lire certains messages

J'ai déjà indiqué plus haut que certains cas de concaténation sont optimisés, mais ce n'est pas une raison pour ne pas utiliser join. Tu peux jouer avec le module dis pour t'en apercevoir :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
>>> dis.dis("for i in ls: 'foo' + 'bar' + 'baz' + 'nurf' + 'urf'")
  1           0 SETUP_LOOP              18 (to 21)
              3 LOAD_NAME                0 (ls)
              6 GET_ITER
        >>    7 FOR_ITER                10 (to 20)
             10 STORE_NAME               1 (i)
             13 LOAD_CONST               9 ('foobarbaznurfurf')
             16 POP_TOP
             17 JUMP_ABSOLUTE            7
        >>   20 POP_BLOCK
        >>   21 LOAD_CONST               5 (None)
             24 RETURN_VALUE

J'ai aussi mentionné que join est un cas particulier ou il vaut mieux construire une liste et la lui passer que d'utiliser un générateur. (source)

+1 -0

Attention également, c'est ''.join et pas 'foo'.join.

 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
import time

class Timer:
    def __init__(self, msg=''):
        self.msg = msg

    def __enter__(self):
        self.start = time.perf_counter()

    def __exit__(self, *args):
        end = time.perf_counter()
        diff = (end - self.start) * 1000

        print('{}: {}'.format(self.msg, diff))


words = ['foo', 'bar', 'baz', 'qux', 'fizz', 'buzz', 'spam', 'eggs'] * 100

with Timer("sum"):
    for _ in range(1000):
        s = ''
        for wd in words:
            s += wd

with Timer("join"):
    for _ in range(1000):
        s = ''.join(words)


"""
sum: 114.31186700065155
join: 5.419948996859603
"""
+0 -0

mais moi j'ai a peine 100 caractere

Donc quel est l'intérêt de l'affirmation (fausse) ci-dessous ?

la concatenation est plus efficace que join

Je ne vois pas la cohérence de ces deux phrases, il faut s'adapter au problème, et dans ton cas, quel que soit le cas, les deux solutions sont bonnes. Ce n'est pas une centaine de caractères qui va titiller python et d'ailleurs n'importe quel langage.

Dans le cas d'un choix d'optimisation, si tu vois que les temps ne répondent pas aux critères, tu peux revoir le code pour le rendre plus performant, ce qui ne semble pas être le cas du problème actuel.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte