Get max number out of strings - python

I have the following 'numbers', which are actually strings from a regex:
nums = ['1', '4', '9', '10']
I would like to get the max, which would be 10. Is there a cleaner way to do this than to do a list comprehension, such as:
>>> max(nums)
'9'
>>> max([int(item) for item in nums])
10
Otherwise, it would give me 9.

max has a keyword argument key that takes a callable which transforms the values in some way before comparing them.
>>> max(nums, key=int)
This is essentially the same as your list comprehension max(int(item) for item in nums), except that it's important to note that the original values are returned, not the transformed values. This means that:
>>> a = max(nums, key=int)
>>> b = max(int(item) for item in nums)
>>> repr(a), repr(b)
('10', 10)

Use the map function:
>>> max(map(int, nums))

Related

Find index of an element in list using wildcards

I have a list like this:
a = [['1','2','3','a','b'],
['4','5','6','c','d'],
['7','8','9','e','f']]
I want to identify the index of an element in the list a based on first three sub-elements of the element. For example, the index of the element which contains ['4','5','6'] as its first three sub-elements is 1.
I have tried to do this using list comprehension follows:
ind = [i for i in range(0,len(a)) if (a[i][0] == '4' and a[i][1] == '5' and a[i][2] == '6')]
But this is computationally expensive as I have to implement this code several times in a for loop. So, I am looking for a less computationally expensive method. SO far I have tried 'list.index' as follows:
ind = a.index(['4','5','6','*','*'])
where '*' is used as wildcard string. But is does not work as it outputs:
['4', '5', '6', '.*', '.*'] is not in list
I think there is something wrong with the way I am using wildcard. Can you please tell me what is it? Or is there another fast way to identify the element of a list based on its sub-elements?
Solution 1: True wildcard
You can simply use a.index(...) if you use a true wildcard:
class Wildcard:
def __eq__(self, anything):
return True
a = [['1','2','3','a','b'],
['4','5','6','c','d'],
['7','8','9','e','f']]
wc = Wildcard()
print(a.index(['4', '5', '6', wc, wc]))
Outputs 1. Try it online!
This might be fast because 1) it does the searching in C code and 2) it does minimal work, as it for example doesn't create a list slice for every row and might often rule out a row simply by looking at just the first value.
Solution 2: operator.indexOf
Or using operator.indexOf, which finds the index in C rather than in Python like an enumerate solution would. Here are two versions of that, I suspect the one mapping a ready-to-go slice is faster:
from operator import indexOf, itemgetter
a = [['1','2','3','a','b'],
['4','5','6','c','d'],
['7','8','9','e','f']]
print(indexOf((r[:3] for r in a), ['4', '5', '6']))
print(indexOf(map(itemgetter(slice(3)), a), ['4', '5', '6']))
Try it online!
Well you could transpose and slice and transpose back and finally index, like this:
>>> list(zip(*list(zip(*a))[:3])).index(('4', '5', '6'))
1
>>>
But what's wrong with?
>>> [x[:3] for x in a].index(['4', '5', '6'])
1
>>>
It may be more efficient to implement it as a generator. In this way, if e.g. you know that you can get at most one match you can stop once it is found:
a = [['1','2','3','a','b'],
['4','5','6','c','d'],
['7','8','9','e','f']]
pattern = ['4','5','6']
def find_index(data, pattern):
for n, elt in enumerate(a):
if elt[:3] == pattern:
yield n
indices = find_index(a, pattern)
next(indices)
It gives:
1
Here's a fairly simple and straightforward way of accomplishing this:
def findListMatch(lists, match):
# Loop over lists with indices
for k, sublist in enumerate(lists):
# Check if the beginning of the list matches the desired start pattern
if sublist[0:len(match)] == match:
# If it's a match, return the index
return k
# If none of the lists match, return a placeholder value of "None"
return None
a = [['1','2','3','a','b'],
['4','5','6','c','d'],
['7','8','9','e','f']]
matchIndex = findListMatch(a, ['4', '5', '6'])
# Result:
# matchIndex = 1
#U12-Forward's answer works but unnecessarily builds a temporary list of the same size as the input list before applying the index method, which can be quite an overhead if the list is long. A more efficient approach would be to use enumerate to generate the indices while comparing the first 3 items to the desired list:
next(i for i, l in enumerate(a) if l[:3] == ['4', '5', '6'])

Reverse a list of string giving me none [duplicate]

This question already has answers here:
How can I get a reversed copy of a list (avoid a separate statement when chaining a method after .reverse)?
(11 answers)
Closed 5 years ago.
s is a string and following code is giving me "None" while executing
n=list(s)
l = n.reverse()
print(l)
Try this:
n=list(s)
n.reverse()
print(n)
reverse
You got to copy the list and then reverse it with l.reverse(), not l = n.reverse().
copy
You have to use copy, not just l = n, because if you do so you will have two reference to the same data, so that when you reverse l you reverse also n. If you do not want to reverse also the original n list, you have to make a brand new copy with l = n. copy, then you reverse l, without modifying the original data stored with n label. You can copy a list also with l = n[:].
>>> s = "12345"
>>> n = list(s)
>>> l = n.copy() # or l = n[:]
>>> l.reverse()
>>> n
['1', '2', '3', '4', '5']
>>> l
['5', '4', '3', '2', '1']
>>>
Thake a list and get the items in a string
If you want a string back
>>> k = "".join(l)
>>> k
'54321'
Problem:
reverse() function is doing in place reversal of list and it is not returning anything that's the reason l gets assigned None
As a solution after calling reverse() print variable n itself as shown
s = "abc"
n=list(s)
n.reverse()
print(n)
For your code, it should be
s='string'
n=list(s)
n.reverse()
print(''.join(n))
Output:
gnirts
You can use [::-1] for reverse.Slice notation is easy to reverse string
s = "reverse"
s = s[::-1]
print(s)
output
esrever
reverse() function returns None but reverse the list.
n=list(s)
l = n.reverse()
print(l)
print(n) #Reversed list
You can use reversed() function as well.
l = list(reversed(n))
print(l)

Comparing all elements of 2 lists with Python 2

I have 2 lists: a = ['5', '2', '3', '4'], and b = ['1', '6', '7', '5']. Using Python 2, how can I compare each list element in a to each element in b? (i.e. is a[0] == b[0], is a[0] == b[1], etc).
I know that I could just write out numerous if statements, but I hope that there is a more elegant way to do this.
After checking each list element, I want to know how many times a shared value was found (in my example lists above, it would be one time, '5').
EDIT: This is not a duplicate, b/c i am comparing two different lists to each other, while the possible duplicate dealt with only 1 list.
The count() method of list may help:
>>> a = ['5', '2', '3', '4']
>>> b = ['1', '6', '7', '5']
>>> for item in a:
... print item, b.count(item)
...
5 1
2 0
3 0
4 0
Probably faster for big inputs than eugene y's, as it only needs to iterate over b once,
instead of len(a) times:
from collections import Counter
counts = Counter(b)
for i in a:
print(i, counts[i])
If you are only concerned with shared values, and not with their positions or counts, convert them to set and use intersection:
>>> a = ['5','2','3','4']
>>> b = ['1','6','7','5']
>>> set(a).intersection(b)
{'5'}
If you want to retain how often the elements appear in the intersection, you can also do an intersection of collections.Counter using &
>>> a = ['5','2','3','4','1','1','6','5']
>>> b = ['1','6','7','5','5']
>>> collections.Counter(a) & collections.Counter(b)
Counter({'5': 2, '1': 1, '6': 1})
Note: This is different from the solution by #GingerPlusPlus in that it is symmetric, i.e. if 5 is present once in list a and twice in list b, then the shared count will be 1, not 2.
def cmp(*lists):
lists_len_min = list(map(lambda x: len(x), lists))
if min(lists_len_min) != max(lists_len_min):
raise Exception("Lists must have equal length")
iterator = iter(lists)
last = next(iterator)
for element in iterator:
for i, each in enumerate(element):
#print(i, last[i], each)
if last[i] != each:
return False
else:
return True
This function can compare as many lists you want with equal length. Just call cmp(list1, list2, list3)
This code will produce list of elements which is consist in both a and b list
a = [1,2,3,4]
b = [2,3,1,7]
c = [e for e in a if e in b]
It might be complex by memory in case if you use big arrays but if you plan to use this data than why not

Python - too many values to unpack

I have a tuple containing string and a list of string as below:
test = ('str1',['1', '2'])
for a,b in test:
print(a,b)
I want to unpack in a way that I can get [('str1','1'),('str1','2')].
However I am getting "ValueError: too many values to unpack (expected 2)".
If I print length of test, it comes as 2. So not sure what is wrong here.
Although test has two elements, you're attempting to iterate over tuples which won't work because test has no tuples (it's a tuple itself).
So this works:
test = [('str1',['1', '2'])]
for a,b in test:
print(a,b)
Or, to get what you want, as a list:
print([(test[0], item) for item in test[1]])
You can also iterate in that way:
test = ('str1',['1', '2'])
for item in test[1]:
print(test[0], item)
You can use zip function to implement that:
>>> seq = ['1', '2']
>>> print(zip(['str']*len(seq), seq))
[('str', '1'), ('str', '2')]
"Too many values to unpack" means exactly that. Let's look at the elements in test:
test = ('str1',['1', '2'])
for a,b in test:
print(a,b)
Each element in test will be unpacked into two variables. The first element is 'str1', and the second one is ['1', '2']. 'str1' is a string with four characters, so, unpacked, it would need four variables. However, you only provide two, a and b. That's the error.
To get the output you want, I recommend unpacking as follows:
a,b = test
Now a is 'str1', and b is ['1', '2']. You can then loop through the values in b:
for item in b:
print(a, item)
Result:
str1 1
str1 2
Your code iterates over each item in test; first 'str1', then ['1', '2']. The problem is when you try to do a, b = 'str1' (this is what the for loop is doing). There are 4 values in 'str1', but only two variables to which you're trying to assign them.
Here's one way to do what you actually want:
test = ('str1',['1', '2'])
test_str, test_list = test
for b in test_list:
print(test_str, b)
In this code, test_str is 'str1' and test_list is ['1', '2']. Then you iterate over test_list, and just reference test_str to get 'str1'.

How to insert a character after every 2 characters in a string

Is there a pythonic way to insert an element into every 2nd element in a string?
I have a string: 'aabbccdd' and I want the end result to be 'aa-bb-cc-dd'.
I am not sure how I would go about doing that.
>>> s = 'aabbccdd'
>>> '-'.join(s[i:i+2] for i in range(0, len(s), 2))
'aa-bb-cc-dd'
Assume the string's length is always an even number,
>>> s = '12345678'
>>> t = iter(s)
>>> '-'.join(a+b for a,b in zip(t, t))
'12-34-56-78'
The t can also be eliminated with
>>> '-'.join(a+b for a,b in zip(s[::2], s[1::2]))
'12-34-56-78'
The algorithm is to group the string into pairs, then join them with the - character.
The code is written like this. Firstly, it is split into odd digits and even digits.
>>> s[::2], s[1::2]
('1357', '2468')
Then the zip function is used to combine them into an iterable of tuples.
>>> list( zip(s[::2], s[1::2]) )
[('1', '2'), ('3', '4'), ('5', '6'), ('7', '8')]
But tuples aren't what we want. This should be a list of strings. This is the purpose of the list comprehension
>>> [a+b for a,b in zip(s[::2], s[1::2])]
['12', '34', '56', '78']
Finally we use str.join() to combine the list.
>>> '-'.join(a+b for a,b in zip(s[::2], s[1::2]))
'12-34-56-78'
The first piece of code is the same idea, but consumes less memory if the string is long.
If you want to preserve the last character if the string has an odd length, then you can modify KennyTM's answer to use itertools.izip_longest:
>>> s = "aabbccd"
>>> from itertools import izip_longest
>>> '-'.join(a+b for a,b in izip_longest(s[::2], s[1::2], fillvalue=""))
'aa-bb-cc-d'
or
>>> t = iter(s)
>>> '-'.join(a+b for a,b in izip_longest(t, t, fillvalue=""))
'aa-bb-cc-d'
I tend to rely on a regular expression for this, as it seems less verbose and is usually faster than all the alternatives. Aside from having to face down the conventional wisdom regarding regular expressions, I'm not sure there's a drawback.
>>> s = 'aabbccdd'
>>> '-'.join(re.findall('..', s))
'aa-bb-cc-dd'
This version is strict about actual pairs though:
>>> t = s + 'e'
>>> '-'.join(re.findall('..', t))
'aa-bb-cc-dd'
... so with a tweak you can be tolerant of odd-length strings:
>>> '-'.join(re.findall('..?', t))
'aa-bb-cc-dd-e'
Usually you're doing this more than once, so maybe get a head start by creating a shortcut ahead of time:
PAIRS = re.compile('..').findall
out = '-'.join(PAIRS(in))
Or what I would use in real code:
def rejoined(src, sep='-', _split=re.compile('..').findall):
return sep.join(_split(src))
>>> rejoined('aabbccdd', sep=':')
'aa:bb:cc:dd'
I use something like this from time to time to create MAC address representations from 6-byte binary input:
>>> addr = b'\xdc\xf7\x09\x11\xa0\x49'
>>> rejoined(addr[::-1].hex(), sep=':')
'49:a0:11:09:f7:dc'
Here is one list comprehension way with conditional value depending of modulus of enumeration, odd last character will be in group alone:
for s in ['aabbccdd','aabbccdde']:
print(''.join([ char if not ind or ind % 2 else '-' + char
for ind,char in enumerate(s)
]
)
)
""" Output:
aa-bb-cc-dd
aa-bb-cc-dd-e
"""
This one-liner does the trick. It will drop the last character if your string has an odd number of characters.
"-".join([''.join(item) for item in zip(mystring1[::2],mystring1[1::2])])
As PEP8 states:
Do not rely on CPython's efficient implementation of in-place string concatenation for statements in the form a += b or a = a + b. This optimization is fragile even in CPython (it only works for some types) and isn't present at all in implementations.
A pythonic way of doing this that avoids this kind of concatenation, and allows you to join iterables other than strings could be:
':'.join(f'{s[i:i+2]}' for i in range(0, len(s), 2))
And another more functional-like way could be:
':'.join(map('{}{}'.format, *(s[::2], s[1::2])))
This second approach has a particular feature (or bug) of only joining pairs of letters. So:
>>> s = 'abcdefghij'
'ab:cd:ef:gh:ij'
and:
>>> s = 'abcdefghi'
'ab:cd:ef:gh'

Categories

Resources