Mathieu
Mathieu

Reputation: 290

Apply function to one element of a list in Python

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!

Upvotes: 5

Views: 4508

Answers (6)

Darius Bacon
Darius Bacon

Reputation: 15124

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[2]]

Upvotes: 2

wescpy
wescpy

Reputation: 11167

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.

Upvotes: 0

mjv
mjv

Reputation: 75125

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)

Upvotes: 2

inspectorG4dget
inspectorG4dget

Reputation: 113945

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)

Upvotes: 0

Dave Kirby
Dave Kirby

Reputation: 26552

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'

Upvotes: 7

hlfrk414
hlfrk414

Reputation: 151

>>> 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,)

Upvotes: 2

Related Questions