Apply function to one element of a list in Python


Question: 

I'm looking for a concise and functional style way to apply a function to one element of a tuple and return the new tuple, in Python.

For example, for the following input:

inp = ("hello", "my", "friend")

I would like to be able to get the following output:

out = ("hello", "MY", "friend")

I came up with two solutions which I'm not satisfied with.

One uses a higher-order function.

def apply_at(arr, func, i):
    return arr[0:i] + [func(arr[i])] + arr[i+1:]

apply_at(inp, lambda x: x.upper(), 1)

One uses list comprehensions (this one assumes the length of the tuple is known).

[(a,b.upper(),c) for a,b,c in [inp]][0]

Is there a better way? Thanks!




6 Answers: 

I commented in support of your first snippet, but here are a couple other ways for the record:

(lambda (a,b,c): [a,b.upper(),c])(inp)

(Won't work in Python 3.x.) And:

[inp[0], inp[1].upper(), inp[1]]
 

Here is a version that works on any iterable and returns a generator:

>>> inp = ("hello", "my", "friend")
>>> def apply_nth(fn, n, iterable):
...    return (fn(x) if i==n else x for (i,x) in enumerate(iterable))
... 
>>> tuple(apply_nth(str.upper, 1, inp))
('hello', 'MY', 'friend')

You can extend this so that instead of one position you can give it a list of positions:

>>> def apply_at(fn, pos_lst, iterable):
...    pos_lst = set(pos_lst)
...    return (fn(x) if i in pos_lst else x for (i,x) in enumerate(iterable))
... 
>>> ''.join(apply_at(str.upper, [2,4,6,8], "abcdefghijklmno"))
'abCdEfGhIjklmno'
 
>>> inp = "hello", "my", "friend"
>>> index = 1
>>> inp[:index] + ( str.upper(inp[index]),) + inp[index + 1:]
('hello', 'MY', 'friend')

Seems simple, the only thing you may need to know is that to make a single element tuple, do (elt,)

 

Maybe some' like this?

>>>inp = ("hello", "my", "friend")
>>>out =  tuple([i == 1 and x.upper() or x for (x,i) in zip(t,range(len(t)))])

>>> out
('hello', 'MY', 'friend')

Note: rather than (x,i) in zip(t, range(len(t))) I should have thought of using the enumerate function : (i,x) in enumerate(t)

Making it a bit more general:
Rather than hard-coding the 1, we can place it in a variable.
Also, by using a tuple for that purpose, we can apply the function to elements at multiple indexes.

>>>inp = ("hello", "my", "friend")
>>>ix  = (0,2)
>>>out =  tuple([i in ix and x.upper() or x for (i, x) in enumerate(t)])

>>> out
('HELLO', 'my', 'FRIEND')

Also, we can "replace" the zip()/enumerate() by map(), in something like

out = tuple(map(lambda x,i : i == 1 and x.upper() or x, inp, range(len(inp)) ) )

Edit: (addressing comment about specifying the function to apply):
Could be something as simple as:

>>> f = str.upper  # or whatever function taking a single argument
>>> out = tuple(map(lambda x,i : i == 1 and f(x) or x, inp, range(len(inp)) ) )

Since we're talking about applying any function, we should mention the small caveat with the condition and if_true or if_false construct which is not exactly a substitute for the if/else ternary operator found in other languages. The limitation is that the function cannot return a value which is equivalent to False (None, 0, 0.0, '' for example). A suggestion to avoid this problem, is, with Python 2.5 and up, to use the true if-else ternary operator, as shown in Dave Kirby's answer (note the when_true if condition else when_false syntax of this operator)

 

I don't understand if you want to apply a certain function to every element in the tuple that passes some test, or if you would like it to apply the function to any element present at a certain index of the tuple. So I have coded both algorithms:

This is the algorithm (coded in Python) that I would use to solve this problem in a functional language like scheme:

This function will identify the element identifiable by id and apply func to it and return a list with that element changed to the output of func. It will do this for every element identifiable as id:

def doSomethingTo(tup, id):
    return tuple(doSomethingToHelper(list(tup), id))

def doSomethingToHelper(L, id):
    if len(L) == 0:
        return L
    elif L[0] == id:
        return [func(L[0])] + doSomethingToHelper(L[1:], id)
    else:
        return [L[0]] + doSomethingToHelper(L[1:], id)


This algorithm will find the element at the index of the tuple and apply func to it, and stick it back into its original index in the tuple

def doSomethingAt(tup, i): 
    return tuple(doSomethingAtHelper(list(tup), i, 0))

def doSomethingAtHelper(L, index, i):
if len(L) == 0: 
        return L
elif i == index: 
        return [func(L[0])] + L[1:]
else: 
        return [L[0]] + doSomethingAtHelper(L[1:], index, i+1)
 

i also like the answer that Dave Kirby gave. however, as a public service announcement, i'd like to say that this is not a typical use case for tuples -- these are data structures that originated in Python as a means to move data (parameters, arguments) to and from functions... they were not meant for the programmer to use as general array-like data structures in applications -- this is why lists exist. naturally, if you're needing the read-only/immutable feature of tuples, that is a fair argument, but given the OP question, this should've been done with lists instead -- note how there is extra code to either pull the tuple apart and put the resulting one together and/or the need to temporarily convert to a list and back.

 

More Articles


apache spark - Scala - How to iterate over tuples on RDD?

I have an RDD that contains tuples like this (A, List(2,5,6,7)) (B, List(2,8,9,10))and I would like to get the index of the first element where a specific condition between value and index holds. So far I have tried this on a single tuple test and it works fine:test._2.zipWithIndex.indexWhere

email - PHP mail function not working based on the body of the message

so on my server I tried running:mail('my@email.com', 'asdf', 'asklfdjksalfdsdaf I know I I know I');and PHP sent the mail perfectly....but then when I changed the message to mail('my@email.com', 'asdf', 'hahahahaa');it did NOT send the email to me....what can possibly cause this? the only thing that

Computer Networks ip addressing

So I have this exercise in one of my classes on network ip addressing. The problem is that i cant figure out how to complete the subnet bits and hosts bits..can anyone help me?the number of hosts is 414, 189, 135 and 90...


javascript - less.js: Use custom import function

less.js is using an internal xhr() function to load @imported .less files dynamically via ajax.I want to know if there is anything I can do the hand a custom function over to the less parser to get the imported files loaded through this function and NOT through the default loading function.As a wild

php - Laravel default selection on form when loading page

I have a select tag on a form, and I want to populate a default option, this default option will be the one that is searched. I can get it through something like $_GET but it does not work in my case, as I am looping through and getting all the values from the database. <select name="location"&

authentication - Sitecore - Prevent access to a page, but still show it in the navigation

In Sitecore I have denied access to a particular page for the anonymous user. This works correctly, but it also means that the page does not appear in the navigation menus and sitemap (both XSLT).What I would like is for the user to be able to see the link, but be redirected to a Register/Login page


scala - Should I use collectionAsScalaIterable({java collection}) or Seq({java collection}).flatten?

We're starting to use Scala Test to test our Java application, and I want to test the contents of a Java Collection. We came up with 2 possibilities:JavaConversions.collectionAsScalaIterable(getJavaCollection()) must contain(allOf(item1, item2).inOrder)orSeq(getJavaCollection()).flatten mustEqual Se

Revert from Python 2.7 to Python 2.6 in Mac OSX 10.6.8 doesn't work even though I've uninstalled Python 2.7

I have mac OSX 10.6.8. My system came with Python 2.6I recently installed Python 2.7 but experienced many problems with it so decided to revert back to Python 2.6 by executing $ sudo rm -rf /Library/Frameworks/Python.framework/Versions/2.7I also removed Python 2.7 from my Applications folder. Still

How do I access my website (IIS) from the internet?

I have installed IIS. I have browsed to my website on IIS using the 'Browse *:80 (http)' link in IIS. This navigates to http://localhost. That link is only going to work on my own machine.I don't care about my IP address changing (I'm going to show my website for a few hours max every now and the

plone - How can I get rid of spam users

The company I work for have a small Plone blog. But we have a problem with spam accounts. We use captcha on the site and Plone sends a mail to the users that they must confirm before they can edit the user profile. Still about 600 spam users are created every day. In the Plone user profile they past