pop/remove items out of a python tuple - python

I am not sure if I can make myself clear but will try.
I have a tuple in python which I go through as follows (see code below). While going through it, I maintain a counter (let's call it 'n') and 'pop' items that meet a certain condition.
Now of course once I pop the first item, the numbering all goes wrong, how can I do what I want to do more elegantly while removing only certain entries of a tuple on the fly?
for x in tupleX:
n=0
if (condition):
tupleX.pop(n)
n=n+1

As DSM mentions, tuple's are immutable, but even for lists, a more elegant solution is to use filter:
tupleX = filter(str.isdigit, tupleX)
or, if condition is not a function, use a comprehension:
tupleX = [x for x in tupleX if x > 5]
if you really need tupleX to be a tuple, use a generator expression and pass that to tuple:
tupleX = tuple(x for x in tupleX if condition)

Yes we can do it.
First convert the tuple into an list, then delete the element in the list after that again convert back into tuple.
Demo:
my_tuple = (10, 20, 30, 40, 50)
# converting the tuple to the list
my_list = list(my_tuple)
print my_list # output: [10, 20, 30, 40, 50]
# Here i wanna delete second element "20"
my_list.pop(1) # output: [10, 30, 40, 50]
# As you aware that pop(1) indicates second position
# Here i wanna remove the element "50"
my_list.remove(50) # output: [10, 30, 40]
# again converting the my_list back to my_tuple
my_tuple = tuple(my_list)
print my_tuple # output: (10, 30, 40)
Thanks

In Python 3 this is no longer an issue, and you really don't want to use list comprehension, coercion, filters, functions or lambdas for something like this.
Just use
popped = unpopped[:-1]
Remember that it's an immutable, so you will have to reassign the value if you want it to change
my_tuple = my_tuple[:-1]
Example
>>> foo= 3,5,2,4,78,2,1
>>> foo
(3, 5, 2, 4, 78, 2, 1)
foo[:-1]
(3, 5, 2, 4, 78, 2)
If you want to have the popped value,,
>>> foo= 3,5,2,4,78,2,1
>>> foo
(3, 5, 2, 4, 78, 2, 1)
>>> foo, bit = foo[:-1], foo[-1]
>>> bit
1
>>> foo
(3, 5, 2, 4, 78, 2)
Or, to work with each value of a tuple starting at the back...
foo = 3,5,2,4,78,2,1
for f in reversed(foo):
print(f) # 1; 2; 78; ...
Or, with the count...
foo = 3,5,2,4,78,2,1
for f, i in enumerate(reversed(foo)):
print(i, f) # 0 1; 1 2; 2 78; ...
Or, to coerce into a list..
bar = [*foo]
#or
bar = list(foo)

ok I figured out a crude way of doing it.
I store the "n" value in the for loop when condition is satisfied in a list (lets call it delList) then do the following:
for ii in sorted(delList, reverse=True):
tupleX.pop(ii)
Any other suggestions are welcome too.

Maybe you want dictionaries?
d = dict( (i,value) for i,value in enumerate(tple))
while d:
bla bla bla
del b[x]

There is a simple but practical solution.
As DSM said, tuples are immutable, but we know Lists are mutable.
So if you change a tuple to a list, it will be mutable. Then you can delete the items by the condition, then after changing the type to a tuple again. That’s it.
Please look at the codes below:
tuplex = list(tuplex)
for x in tuplex:
if (condition):
tuplex.pop(tuplex.index(x))
tuplex = tuple(tuplex)
print(tuplex)
For example, the following procedure will delete all even numbers from a given tuple.
tuplex = (1, 2, 3, 4, 5, 6, 7, 8, 9)
tuplex = list(tuplex)
for x in tuplex:
if (x % 2 == 0):
tuplex.pop(tuplex.index(x))
tuplex = tuple(tuplex)
print(tuplex)
if you test the type of the last tuplex, you will find it is a tuple.
Finally, if you want to define an index counter as you did (i.e., n), you should initialize it before the loop, not in the loop.

A shorter way perhaps:
tup = (0, 1, 2, 3)
new_tup = (*tup[:-2], tup[-1])
print(new_tup) # (0, 1, 3)

The best solution is the tuple applied to a list comprehension, but to extract one
item this could work:
def pop_tuple(tuple, n):
return tuple[:n]+tuple[n+1:], tuple[n]

say you have a dict with tuples as keys, e.g: labels = {(1,2,0): 'label_1'} you can modify the elements of the tuple keys as follows:
formatted_labels = {(elem[0],elem[1]):labels[elem] for elem in labels}
Here, we ignore the last elements.

One solution is to convert to set and bring back to tuple
tupleX = (
"ZAR",
"PAL",
"SEV",
"ALC",
"LPA",
"TFN",)
remove = (
"LPA",
"TFN",)
tuple(set(tupleX) - set(remove))
('ZAR', 'PAL', 'ALC', 'SEV')

Related

How to pass to a function that takes two arguments an element from one list together with each element of another list?

I am new to Python and I need help with following.
I do have a list a = [range(1, 50, 10)]
and another list b = [2, 4, 5, 8, 12, 34]
Now, I have a function that calculates something, let's call it "SomeFunction", that takes two arguments.
I want to pass to this SomeFunction for element from list a each element of list b. So, I want values for: SomeFunction(1, 2), SomeFunction(1, 4), SomeFunction(1, 5), ..... SomeFunction(50, 2), SomeFunction(50, 4), SomeFunction(50, 5), etc.
I think it should be done somehow with for loops, but I do not know how...
You'd need a nested for loop:
a = range(1, 50, 10)
b = [2, 4, 5, 8, 12, 34]
for aval in a:
for bval in b:
print(aval, bval) # or any other function call
This just goes through all values in b for each value in a. (Note that you don't want range inside square brackets as you have in your question, I removed those).
A more advanced version: "all pairs of values" is also known as the Cartesian product, which is provided by the itertools module. Thus you could write
from itertools import product
for aval, bval in product(a, b):
print(aval, bval)
Try this:
a = range(1, 50, 10)
b = [2, 4, 5, 8, 12, 34]
for i in a:
for n in b:
print(SomeFunction(i, n))

Why can I add tuples, but not subtract? [duplicate]

I am not sure if I can make myself clear but will try.
I have a tuple in python which I go through as follows (see code below). While going through it, I maintain a counter (let's call it 'n') and 'pop' items that meet a certain condition.
Now of course once I pop the first item, the numbering all goes wrong, how can I do what I want to do more elegantly while removing only certain entries of a tuple on the fly?
for x in tupleX:
n=0
if (condition):
tupleX.pop(n)
n=n+1
As DSM mentions, tuple's are immutable, but even for lists, a more elegant solution is to use filter:
tupleX = filter(str.isdigit, tupleX)
or, if condition is not a function, use a comprehension:
tupleX = [x for x in tupleX if x > 5]
if you really need tupleX to be a tuple, use a generator expression and pass that to tuple:
tupleX = tuple(x for x in tupleX if condition)
Yes we can do it.
First convert the tuple into an list, then delete the element in the list after that again convert back into tuple.
Demo:
my_tuple = (10, 20, 30, 40, 50)
# converting the tuple to the list
my_list = list(my_tuple)
print my_list # output: [10, 20, 30, 40, 50]
# Here i wanna delete second element "20"
my_list.pop(1) # output: [10, 30, 40, 50]
# As you aware that pop(1) indicates second position
# Here i wanna remove the element "50"
my_list.remove(50) # output: [10, 30, 40]
# again converting the my_list back to my_tuple
my_tuple = tuple(my_list)
print my_tuple # output: (10, 30, 40)
Thanks
In Python 3 this is no longer an issue, and you really don't want to use list comprehension, coercion, filters, functions or lambdas for something like this.
Just use
popped = unpopped[:-1]
Remember that it's an immutable, so you will have to reassign the value if you want it to change
my_tuple = my_tuple[:-1]
Example
>>> foo= 3,5,2,4,78,2,1
>>> foo
(3, 5, 2, 4, 78, 2, 1)
foo[:-1]
(3, 5, 2, 4, 78, 2)
If you want to have the popped value,,
>>> foo= 3,5,2,4,78,2,1
>>> foo
(3, 5, 2, 4, 78, 2, 1)
>>> foo, bit = foo[:-1], foo[-1]
>>> bit
1
>>> foo
(3, 5, 2, 4, 78, 2)
Or, to work with each value of a tuple starting at the back...
foo = 3,5,2,4,78,2,1
for f in reversed(foo):
print(f) # 1; 2; 78; ...
Or, with the count...
foo = 3,5,2,4,78,2,1
for f, i in enumerate(reversed(foo)):
print(i, f) # 0 1; 1 2; 2 78; ...
Or, to coerce into a list..
bar = [*foo]
#or
bar = list(foo)
ok I figured out a crude way of doing it.
I store the "n" value in the for loop when condition is satisfied in a list (lets call it delList) then do the following:
for ii in sorted(delList, reverse=True):
tupleX.pop(ii)
Any other suggestions are welcome too.
Maybe you want dictionaries?
d = dict( (i,value) for i,value in enumerate(tple))
while d:
bla bla bla
del b[x]
There is a simple but practical solution.
As DSM said, tuples are immutable, but we know Lists are mutable.
So if you change a tuple to a list, it will be mutable. Then you can delete the items by the condition, then after changing the type to a tuple again. That’s it.
Please look at the codes below:
tuplex = list(tuplex)
for x in tuplex:
if (condition):
tuplex.pop(tuplex.index(x))
tuplex = tuple(tuplex)
print(tuplex)
For example, the following procedure will delete all even numbers from a given tuple.
tuplex = (1, 2, 3, 4, 5, 6, 7, 8, 9)
tuplex = list(tuplex)
for x in tuplex:
if (x % 2 == 0):
tuplex.pop(tuplex.index(x))
tuplex = tuple(tuplex)
print(tuplex)
if you test the type of the last tuplex, you will find it is a tuple.
Finally, if you want to define an index counter as you did (i.e., n), you should initialize it before the loop, not in the loop.
A shorter way perhaps:
tup = (0, 1, 2, 3)
new_tup = (*tup[:-2], tup[-1])
print(new_tup) # (0, 1, 3)
The best solution is the tuple applied to a list comprehension, but to extract one
item this could work:
def pop_tuple(tuple, n):
return tuple[:n]+tuple[n+1:], tuple[n]
say you have a dict with tuples as keys, e.g: labels = {(1,2,0): 'label_1'} you can modify the elements of the tuple keys as follows:
formatted_labels = {(elem[0],elem[1]):labels[elem] for elem in labels}
Here, we ignore the last elements.
One solution is to convert to set and bring back to tuple
tupleX = (
"ZAR",
"PAL",
"SEV",
"ALC",
"LPA",
"TFN",)
remove = (
"LPA",
"TFN",)
tuple(set(tupleX) - set(remove))
('ZAR', 'PAL', 'ALC', 'SEV')

Adding values from loop to a tuple results in nested tuples instead of a flat tuple or list

I'm trying to perform the following:
tup1 = ()
for i in range(1, 10, 2):
tup1 = (tup1, i)
print tup1
I expect the output to be the sequence 1 to 10.
However, I end up with the following:
((((((), 0), 2), 4), 6), 8)
What would be a correct way to meet the requirement?
If you just want an iterable with the even numbers 1 to 10 then the simplest way to do it:
seq = range(2, 11, 2)
If you are doing this as a means of learning Python and you want to build up your own data structure, use a list:
l = []
for i in range(2, 11, 2):
l.append(i)
The above for loop can be rewritten as a list comprehension:
l = [i for i in range(2, 11, 2)]
or using an if clause in the loop comprehension:
l = [ i for i in range(1, 11) if i % 2 == 0]
You can append an item to a tuple using the += operator.
tup1=()
for i in range(1,10,2):
tup1+= (i,)
print tup1
This prints (1, 3, 5, 7, 9)
Tuples are immutable objects in Python. Thus means you can't modify them. What you're doing right now is creating a new tuple with the previous one inside
You could do:
lst = []
for i in range(1,10,2):
lst.append(i)
tup = tuple(lst) #If you really want a tuple
print tup
But lst = range(1,10,2) or tup = tuple(range(1,10,2)) is much better (Unless you want to use append for some reason)
Read about List Comprehension
tuple(i for i in range(1, 10, 2))
Or
tup1 = ()
for i in range(1, 10, 2):
tup1 += (i,)
print tup1
it's something like this:
print range(1, 11)
You are skipping by two by using for i in range(1,10,2): if you use for i in range(1,11): if will increment by 1. As for tup1=(tup1,i) you are constantly adding a tuple to each other which is creating the weird output. You could use a list if you want to store them. Otherwise using will do it just fine:
print(range(10))
List item
For appending into list or tuple you can use append() function or you can use += operator which does the same.
s=()
for sequence of numbers from 1 to 10
for i in range(1,11):
s+=(i,)
print(s) #(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
for sequence of numbers from 1 to 10 with step size 2
x=()
for i in range(1,11,2):
x+=(i,)
print(x) #odd nos from 1-9 (1, 3, 5, 7, 9)
x=()
for i in range(2,11,2):
x+=(i,)
print(x) #even nos from 2-10 (2, 4, 6, 8, 10)
List item
Storing values from loop in a list or tuple in Python by following ways -
-> By appending the value in the list (here new_data1) as join will not work here.
new_data1 = []
for line in all_words:
new_data=' '.join(lemmatize_sentence(line))
new_data1.append(new_data)
#print (new_data)
print (new_data1)
P.S. - This is just a snapshot of a code just for hint .
Hope this helps!!

Making a function only run for certain conditions in python

G'day!
So I have a function which is taking the elements from two lists, the first of which is in a standard list format, the second being a list of lists, the inner lists containing elements in the form of 3-tuples. My output is a new list in the format of the the second list, containing the same number of elements in the same number of inner lists, with some of the values slightly adjusted as a result of being passed through the function.
Here is an example code, and an example function, where chain is being imported from itertools. first is some list such as [0,1,2,3,1,5,6,7,1,2,3,5,1,1,2,3,5,6] whilst
second is some list such as [[(13,12,32),(11,444,25)],[(312,443,12),(123,4,123)],[(545,541,1),(561,112,560)]]
def add(x, y):
return x + y
foo = [add(x, y) for x, y in zip(first, chain(*(chain(*second))))]
bar = [foo[i:i+3] for i in range(0, len(foo), 3)]
second = [bar[i:i+2] for i in range(0, len(foo) / 3, 2)]
**Note: The Chain(chain()) part is for the following purpose: Because it's generally a bit harder to handle a list of list containing 3-tuples, The chain(chain()) is just flattening (into a traditional list of individual elements) that second list with the aforementioned 'odd format'. The rest of the code is just rebuilding the new list into the original format from the output of the function, which is already in flattened form.
The problems I'm having are as such:
I want the output to be of the exact same size and format as the original 'second' list. If both lists are empty, I want the empty list returned. If the first list is empty, I want the original second list returned. If the second list is empty, I want the empty list returned.
If the first list is shorter than the second list, I want the function to run for however elements can be matched between the two lists, then the 'excess' of the second list remaining unchanged.
If the second list is shorter than the first list, I want the function to run for however many elements there are in the second list, then just ignore the 'excess' elements from list 1, thus still outputting a new list that has the same dimensions and formatting as the original second list.
My problem is, that I have no idea how to implement these little nuances into my code. Any help would be appreciated.
Cheers,
James
Could you pad the first list with None where its not long enough and trim it where its too long.
then only carry out the function where x is not None otherwise return y
i've tried to code an example
from itertools import chain
first = [0, 1, 2, 3, 1, 5, 6, 7, 1, 2, 3, 5, 1, 1, 2, 3, 5, 6]
second = [
[(13, 12, 32), (11, 444, 25)],
[(312, 443, 12), (123, 4, 123)],
[(545, 541, 1), (561, 112, 560)],
[(13, 12, 32), (11, 444, 25)],
[(312, 443, 12), (123, 4, 123)],
[(545, 541, 1), (561, 112, 560)],
]
def add(x, y):
return x + y
def pad(list,length):
for i in range(length-len(list)):
list.append(None)
return list[0:length]
first = pad(first,len(list(chain(*(chain(*second))) )))
# There is probably a better way to achieve this
foo = [add(x, y) if x else y for x, y in zip(first, chain(*(chain(*second))))]
bar = [foo[i:i+3] for i in range(0, len(foo), 3)]
second = [bar[i:i+2] for i in range(0, len(foo) / 3, 2)]
print second
since zip only zips to the smaller of the two lists, it isn't too useful here. You could create your own algorithm that applies a function to two lists in the way you specify:
from itertools import *
def flatten(seq):
return list(chain(*(chain(*seq))))
def special_apply(a,b, func):
"""
applies a two argument function to the given flat lists.
The result will have the same size as the second list, even if the first list is shorter.
"""
result = []
for i in range(len(b)):
if i < len(a):
element = func(a[i], b[i])
#if a ran out of elements, just supply an unmodified element of b
else:
element = b[i]
result.append(element)
return result
def add(x,y):
return x+y
a = [1,1,1]
b = [[(13,12,32),(11,444,25)],[(312,443,12),(123,4,123)],[(545,541,1),(561,112,560)]]
foo = special_apply(a, flatten(b), add)
bar = [foo[i:i+3] for i in range(0, len(foo), 3)]
result = [bar[i:i+2] for i in range(0, len(foo) / 3, 2)]
print result
Result:
[[[14, 13, 33], [11, 444, 25]], [[312, 443, 12], [123, 4, 123]], [[545, 541, 1], [561, 112, 560]]]
I think this does everything you want, if I've understood all the requirements. The major difference from your code is that it also usesizip_longest() fromitertoolswith a customfillvalue, instead of plainzip(). It also just checks for the special cases involving empty input lists at the beginning, which seemed easier than trying to devise list comprehensions or whatever to handle them.
from itertools import chain, izip_longest
def add(x, y):
return x + y
def func(first, second):
if not first: return second
if not second: return []
second = chain(*(chain(*second))) # flatten
foo = [add(x, y) for x, y in izip_longest(first, second, fillvalue=0)]
bar = [tuple(foo[i:i+3]) for i in range(0, len(foo), 3)]
return [bar[i:i+2] for i in range(0, len(foo) / 3, 2)]
if __name__ == '__main__':
first = [
0, 1, 2, 3, 1, 5,
6, 7, 1, 2, 3, 5,
1, 1, 2, 3, 5, 6]
second = [
[(13, 12, 32), (11, 444, 25)], [(312, 443, 12), (123, 4, 123)],
[(545, 541, 1), (561, 112, 560)], [(13, 12, 32), (11, 444, 25)],
[(312, 443, 12), (123, 4, 123)], [(545, 541, 1), (561, 112, 560)],
]
print func(first, second)
print
print func(first[:-1], second) # 1st shorter, as many as poss, rest unchanged
print
print func(first, second[:-1]) # 2nd shorter, do only as many as in second

Summing Consecutive Ranges Pythonically

I have a sumranges() function, which sums all the ranges of consecutive numbers found in a tuple of tuples. To illustrate:
def sumranges(nums):
return sum([sum([1 for j in range(len(nums[i])) if
nums[i][j] == 0 or
nums[i][j - 1] + 1 != nums[i][j]]) for
i in range(len(nums))])
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> print sumranges(nums)
7
As you can see, it returns the number of ranges of consecutive digits within the tuple, that is: len((1, 2, 3, 4), (1), (5, 6), (19, 20), (24), (29), (400)) = 7. The tuples are always ordered.
My problem is that my sumranges() is terrible. I hate looking at it. I'm currently just iterating through the tuple and each subtuple, assigning a 1 if the number is not (1 + previous number), and summing the total. I feel like I am missing a much easier way to accomplish my stated objective. Does anyone know a more pythonic way to do this?
Edit: I have benchmarked all the answers given thus far. Thanks to all of you for your answers.
The benchmarking code is as follows, using a sample size of 100K:
from time import time
from random import randrange
nums = [sorted(list(set(randrange(1, 10) for i in range(10)))) for
j in range(100000)]
for func in sumranges, alex, matt, redglyph, ephemient, ferdinand:
start = time()
result = func(nums)
end = time()
print ', '.join([func.__name__, str(result), str(end - start) + ' s'])
Results are as follows. Actual answer shown to verify that all functions return the correct answer:
sumranges, 250281, 0.54171204567 s
alex, 250281, 0.531121015549 s
matt, 250281, 0.843333005905 s
redglyph, 250281, 0.366822004318 s
ephemient, 250281, 0.805964946747 s
ferdinand, 250281, 0.405596971512 s
RedGlyph does edge out in terms of speed, but the simplest answer is probably Ferdinand's, and probably wins for most pythonic.
My 2 cents:
>>> sum(len(set(x - i for i, x in enumerate(t))) for t in nums)
7
It's basically the same idea as descriped in Alex' post, but using a set instead of itertools.groupby, resulting in a shorter expression. Since sets are implemented in C and len() of a set runs in constant time, this should also be pretty fast.
Consider:
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> flat = [[(x - i) for i, x in enumerate(tu)] for tu in nums]
>>> print flat
[[1, 1, 1, 1], [1, 4, 4], [19, 19, 22, 26, 396]]
>>> import itertools
>>> print sum(1 for tu in flat for _ in itertools.groupby(tu))
7
>>>
we "flatten" the "increasing ramps" of interest by subtracting the index from the value, turning them into consecutive "runs" of identical values; then we identify and could the "runs" with the precious itertools.groupby. This seems to be a pretty elegant (and speedy) solution to your problem.
Just to show something closer to your original code:
def sumranges(nums):
return sum( (1 for i in nums
for j, v in enumerate(i)
if j == 0 or v != i[j-1] + 1) )
The idea here was to:
avoid building intermediate lists but use a generator instead, it will save some resources
avoid using indices when you already have selected a subelement (i and v above).
The remaining sum() is still necessary with my example though.
Here's my attempt:
def ranges(ls):
for l in ls:
consec = False
for (a,b) in zip(l, l[1:]+(None,)):
if b == a+1:
consec = True
if b is not None and b != a+1:
consec = False
if consec:
yield 1
'''
>>> nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
>>> print sum(ranges(nums))
7
'''
It looks at the numbers pairwise, checking if they are a consecutive pair (unless it's at the last element of the list). Each time there's a consecutive pair of numbers it yields 1.
This could probably be put together in a more compact form, but I think clarity would suffer:
def pairs(seq):
for i in range(1,len(seq)):
yield (seq[i-1], seq[i])
def isadjacent(pair):
return pair[0]+1 == pair[1]
def sumrange(seq):
return 1 + sum([1 for pair in pairs(seq) if not isadjacent(pair)])
def sumranges(nums):
return sum([sumrange(seq) for seq in nums])
nums = ((1, 2, 3, 4), (1, 5, 6), (19, 20, 24, 29, 400))
print sumranges(nums) # prints 7
You could probably do this better if you had an IntervalSet class because then you would scan through your ranges to build your IntervalSet, then just use the count of set members.
Some tasks don't always lend themselves to neat code, particularly if you need to write the code for performance.
There is a formula for this, the sum of the first n numbers, 1+ 2+ ... + n = n(n+1) / 2 . Then if you want to have the sum of i-j then it is (j(j+1)/2) - (i(i+1)/2) this I am sure simplifies but you can work that out. It might not be pythonic but it is what I would use.

Categories