简体   繁体   中英

HOW TO “Arbitrary” format items in list/dict/etc. EX: change 4th character in every string in list

first of all i want to mention that there might not be any real life applications for this simple script i created, but i did it because I'm learning and I couldn't find anything similar here in SO. I wanted to know what could be done to "arbitrarily" change characters in an iterable like a list.

Sure tile() is a handy tool I learned relatively quick, but then I got to think what if, just for kicks, i wanted to format (upper case) the last character instead? or the third, the middle one,etc. What about lower case? Replacing specific characters with others?

Like I said this is surely not perfect but could give away some food for thought to other noobs like myself. Plus I think this can be modified in hundreds of ways to achieve all kinds of different formatting.

How about helping me improve what I just did? how about making it more lean and mean? checking for style, methods, efficiency, etc...

Here it goes:

words = ['house', 'flower', 'tree']  #string list

counter = 0                          #counter to iterate over the items in list
chars = 4                            #character position in string (0,1,2...)

for counter in range (0,len(words)): 
    while counter < len(words):
        z = list(words[counter])     # z is a temp list created to slice words
        if len(z) > chars:           # to compare char position and z length
            upper = [k.upper() for k in z[chars]] # string formatting EX: uppercase
            z[chars] = upper [0]     # replace formatted character with original
            words[counter] = ("".join(z)) # convert and replace temp list back into original word str list
            counter +=1
        else:
            break

print (words)

['housE', 'flowEr', 'tree']

There's much better Pythonistas than me, but here's one attempt:

[''.join(
      [a[x].upper() if x == chars else a[x]
          for x in xrange(0,len(a))]
    )
    for a in words]

Also, we're talking about the programmer's 4th, right? What everyone else calls 5th, yes?

I think the general case of what you're talking about is a method that, given a string and an index, returns that string, with the indexed character transformed according to some rule.

def transform_string(strng, index, transform):
    lst = list(strng)
    if index < len(lst):
        lst[index] = transform(lst[index])
    return ''.join(lst)


words = ['house', 'flower', 'tree']
output = [transform_string(word, 4, str.upper) for word in words]

To make it even more abstract, you could have a factory that returns a method, like so:

def transformation_factory(index, transform):
    def inner(word):
        lst = list(word)
        if index < len(lst):
            lst[index] = transform(lst[index])
    return inner
transform = transformation_factory(4, lambda x: x.upper())
output = map(transform, words)

This is somewhat of a combination of both (so +1 to both of them :) ). The main function accepts a list, an arbitrary function and the character to act on:

In [47]: def RandomAlter(l, func, char):
    return [''.join([func(w[x]) if x == char else w[x] for x in xrange(len(w))]) for w in l]
   ....:

In [48]: RandomAlter(words, str.upper, 4)
Out[48]: ['housE', 'flowEr', 'tree']

In [49]: RandomAlter([str.upper(w) for w in words], str.lower, 2)
Out[49]: ['HOuSE', 'FLoWER', 'TReE']

In [50]: RandomAlter(words, lambda x: '_', 4)
Out[50]: ['hous_', 'flow_r', 'tree']

The function RandomAlter can be rewritten as this, which may make it a bit more clear (it takes advantage of a feature called list comprehensions to reduce the lines of code needed).

def RandomAlter(l, func, char):
    # For each word in our list
    main_list = []
    for w in l:
        # Create a container that is going to hold our new 'word'
        new_word = []
        # Iterate over a range that is equal to the number of chars in the word
        # xrange is a more memory efficient 'range' - same behavior
        for x in xrange(len(w)):
            # If the current position is the character we want to modify
            if x == char:
                # Apply the function to the character and append to our 'word'
                # This is a cool Python feature - you can pass around functions
                # just like any other variable
                new_word.append(func(w[x]))
            else:
                # Just append the normal letter
                new_word.append(w[x])

        # Now we append the 'word' to our main_list. However since the 'word' is
        # a list of letters, we need to 'join' them together to form a string
        main_list.append(''.join(new_word))

    # Now just return the main_list, which will be a list of altered words
    return main_list

Some comments on your code:

for counter in range (0,len(words)):     
while counter < len(words):

This won't compile unless you indent the while loop under the for loop. And, if you do that, the inner loop will completely screw up the loop counter for the outer loop. And finally, you almost never want to maintain an explicit loop counter in Python. You probably want this:

for counter, word in enumerate(words):

Next:

z = list(words[counter])     # z is a temp list created to slice words

You can already slice strings, in exactly the same way you slice lists, so this is unnecessary.

Next:

    upper = [k.upper() for k in z[chars]] # string formatting EX: uppercase

This is a bad name for the variable, since there's a function with the exact same name—which you're calling on the same line.

Meanwhile, the way you defined things, z[chars] is a character, a copy of words[4] . You can iterate over a single character in Python, because each character is itself a string. but it's generally pointless— [k.upper() for k in z[chars]] is the same thing as [z[chars].upper()] .

    z[chars] = upper [0]     # replace formatted character with original

So you only wanted the list of 1 character to get the first character out of it… why make it a list in the first place? Just replace the last two lines with z[chars] = z[chars].upper() .

else:
    break

This is going to stop on the first string shorter than length 4, rather than just skip strings shorter than length 4, which is what it seems like you want. The way to say that is continue , not break . Or, better, just fall off the end of the list. In some cases, it's hard to write things without a continue , but in this case, it's easy—it's already at the end of the loop, and in fact it's inside an else: that has nothing else in it, so just remove both lines.

It's hard to tell with upper that your loops are wrong, because if you accidentally call upper twice, it looks the same as if you called it once. Change the upper to chr(ord(k)+1) , which replaces any letter with the next letter. Then try it with:

words = ['house', 'flower', 'tree', 'a', 'abcdefgh']

You'll notice that, eg, you get 'flowgr' instead of 'flowfr' .

You may also want to add a variable that counts up the number of times you run through the inner loop. It should only be len(words) times, but it's actually len(words) * len(words) if you have no short words, or len(words) * len(<up to the first short word>) if you have any. You're making the computer do a whole lot of extra work—if you have 1000 words, it has to do 1000000 loops instead of 1000. In technical terms, your algorithm is O(N^2), even though it only needs to be O(N).

Putting it all together:

words = ['house', 'flower', 'tree', 'a', 'abcdefgh']  #string list
chars = 4                            #character position in string (0,1,2...)

for counter, word in enumerate(words): 
    if len(word) > chars:           # to compare char position and z length
        z = list(word)
        z[chars] = chr(ord(z[chars]+1) # replace character with next character
        words[counter] = "".join(z)    # convert and replace temp list back into original word str list

print (words)

That does the same thing as your original code (except using "next character" instead of "uppercase character"), without the bugs, with much less work for the computer, and much easier to read.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM