Problem sorting list of strings - Python - python

I have a list of strings:
cards = ['2S', '8D', '8C', '4C', 'TS', '9S', '9D', '9C', 'AC', '3D']
and the order in which I want to display the cards:
CARD_ORDER = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A']
This is how I'm trying to order the list:
sorted(cards, lambda x,y: CARD_ORDER.index(x[0]) >= CARD_ORDER.index(y[0]) )
Unfortunately this does not seem to work....
or more precisely the list stays exactly the same, sorted(cards) works fine instead.
Any ideas?

it's
sorted(cards, key=lambda x: CARD_ORDER.index(x[0]))
key parameter accepts a single value, by which to sort the main iterable. You're probably trying to use cmp parameter which is not recommended for quite some time.

Try
sorted(cards, key = lambda x: CARD_ORDER.index(x[0]) )

Related

Dict of list of list into dict of tuples of tuples

I have this dictionary:
a = {'Jimmy': [['5', '7', '5'], ['S', 'F', 'R']],
'Limerick': [['8', '8', '5', '5', '8'], ['A', 'A', 'B', 'B', 'A']]}
I want the end product to be a dictionary with tuples of tuples, but I think the second loop is not working.
What I want:
a = {'Jimmy': (('5', '7', '5'), ('S', 'F', 'R')),
'Limerick': (('8', '8', '5', '5', '8'), ('A', 'A', 'B', 'B', 'A'))}
Can anyone help me to see what I'm doing wrong?
I tried:
a = {'Jimmy': [['5', '7', '5'], ['S', 'F', 'R']],
'Limerick': [['8', '8', '5', '5', '8'], ['A', 'A', 'B', 'B', 'A']]}
for key in a:
a[key] = tuple(a[key])
for value in a[key]:
value = tuple(value)
print(a)
but it didn't work.
value refers to a fresh variable -- reassigning it does not modify the dictionary.
You should use map() to transform each list into a tuple, and then call tuple() once more to transform the resulting map object into a tuple:
for key in a:
a[key] = tuple(map(tuple, a[key]))
You are almost there. What you needed is this:
for key in a:
a[key] = tuple(tuple(item) for item in a[key])
Your statement value = tuple(value) re-assigns the local variable value to a new tuple, but it doesn't change the contents of a[key] at all.
In fact, since tuples are immutable, your statement a[key] = tuple(a[key]) prevents the contents of a[key] from changing, unless you reassign a[key] = something_else. Something like a[key] = tuple(a[key]) then a[key][0] = "A" will fail because tuples are immutable.
The other answers give nice concise solutions, so you may want to go with those, but here is one that mirrors your original attempt:
a = {'Jimmy': [['5', '7', '5'], ['S', 'F', 'R']],
'Limerick': [['8', '8', '5', '5', '8'], ['A', 'A', 'B', 'B', 'A']]}
for key in a:
new_values = []
for value in a[key]:
new_values.append(tuple(value))
a[key] = tuple(new_values)
print(a)
Here, to get around the fact that tuples are immutable, you can make a list [] (which is mutable), build it up over the loop, then convert the list to a tuple, and then finally assign that tuple to a[key].

How do I sort a list by another list's index number in Python?

When we sort a dictionary by value in Python, we use lambda func. But i tried this way in my lists, instead of values, using another list index number. This may be a short str function/method for my question.
Here is an example:
cardsorder = ['2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A']
colors = ['C', 'D', 'S', 'H']
handEx = ['8C','TS','KC','9H','4S']
I want to sort my handEx list elements by using cardsorder index numbers.
Firstly i scan every element and i find values for sorting:
for card in handEx:
value = cardsorder.index(card[0])
In this way by using these values, i can sort elements by comparing the others but i think it is a very long way to find the solution.
Is there a simpler solution, for example using a lambda function?
You're very close. Just make a key function that gets the equivalent of value:
>>> sorted(handEx, key=lambda card: cardsorder.index(card[0]))
['4S', '8C', '9H', 'TS', 'KC']
Or in-place:
handEx.sort(key=lambda card: cardsorder.index(card[0]))
By the way, I posted a similar answer here.

How to write complex sort in python?

Is there a concise way to sort a list by first sorting numbers in ascending order and then sort the characters in descending order?
How would you sort the following:
['2', '4', '1', '6', '7', '4', '2', 'K', 'A', 'Z', 'B', 'W']
To:
['1', '2', '2', '4', '4', '6', '7', 'Z', 'W', 'K', 'B', 'A']
One way (there might be better ones) is to separate digits and letters beforehand, sort them appropriately and glue them again together in the end:
lst = ['2', '4', '1', '6', '7', '4', '2', 'K', 'A', 'Z', 'B', 'W']
numbers = sorted([number for number in lst if number.isdigit()])
letters = sorted([letter for letter in lst if not letter.isdigit()], reverse=True)
combined = numbers + letters
print(combined)
Another way makes use of ord(...) and the ability to sort by tuples. Here we use zero for numbers and one for letters:
def sorter(item):
if item.isdigit():
return 0, int(item)
else:
return 1, -ord(item)
print(sorted(lst, key=sorter))
Both will yield
['1', '2', '2', '4', '4', '6', '7', 'Z', 'W', 'K', 'B', 'A']
As for timing:
def different_lists():
global my_list
numbers = sorted([number for number in my_list if number.isdigit()])
letters = sorted([letter for letter in my_list if not letter.isdigit()], reverse=True)
return numbers + letters
def key_function():
global my_list
def sorter(item):
if item.isdigit():
return 0, int(item)
else:
return 1, -ord(item)
return sorted(my_list, key=sorter)
from timeit import timeit
print(timeit(different_lists, number=10**6))
print(timeit(key_function, number=10**6))
This yields (running it a million times on my MacBook):
2.9208732349999997
4.54283629
So the approach with list comprehensions is faster here.
To elaborate on the custom-comparison approach: in Python the built-in sort does key comparison.
How to think about the problem: to group values and then sort each group by a different quality, we can think of "which group is a given value in?" as a quality - so now we are sorting by multiple qualities, which we do with a key that gives us a tuple of the value for each quality.
Since we want to sort the letters in descending order, and we can't "negate" them (in the arithmetic sense), it will be easiest to apply reverse=True to the entire sort, so we keep that in mind.
We encode: True for digits and False for non-digits (since numerically, these are equivalent to 1 and 0 respectively, and we are sorting in descending order overall). Then for the second value, we'll use the symbol directly for non-digits; for digits, we need the negation of the numeric value, to re-reverse the sort.
This gives us:
def custom_key(value):
numeric = value.isdigit()
return (numeric, -int(value) if numeric else value)
And now we can do:
my_list.sort(key=custom_key, reverse=True)
which works for me (and also handles multi-digit numbers):
>>> my_list
['1', '2', '2', '4', '4', '6', '7', 'Z', 'W', 'K', 'B', 'A']
You will have to implement your own comparison function and pass it as the key argument for the sorted function. What you are seeking is not a trivial comparison as you "assign" custom values to fields so you will have to let Python know how you value each one of them

Extend/append python join list

I have an example:
li = [['b', 'b', 'c', '3.2', 'text', '3', '5', '5'], ['a', 'w', '3', '4'], ['a', 'x', '3', '4'],['a','b'],['312','4']]
a = 0
b = []
c = []
count = []
for x in range(len(li)):
for a in range(len(li[x])):
if li[x][a].isalpha():
a += 1
elif not li[x][a].isalpha() and li[x][a + 1].isalpha():
a += 1
else:
break
i = (len(li[x]) - a)
b.extend([' '.join(li[x][0:a])])
b.extend(li[x][a::])
count.append(i)
for x in range(len(count)):
a = count[x] + 1
z = (sum(count[:x]))
if x == 0:
c.append(b[:a])
else:
c.append(b[a+1::z])
print(c)
I have various items in the li list and the length of the list itself is not constant.
If any element in the array is a string or if there is some other symbol between the two strings, it combines everything into one element - this join works as I wanted.
I would like to preserve the existing structure. For example, output now looks like this:
[['b b c 3.2 text', '3', '5', '5'], ['a w', 'a x', 'a b', '4'], ['a w', '4'], ['5', '4'], ['a w', '']]
but it should look like this:
[['b b c 3.2 text', '3', '5', '5'],['aw','3','4'],['ax','3','4'],['ab'],['312','4']
Of course, the code I sent did not work properly - I think of a solution but I still have some problems with it - I do not know how to add ranges to this list c - I try to pull the length of the elements of the list as count but it also doesn't work for me - maybe this is a bad solution? Maybe this extend b is not the best solution? Maybe there is no point in using so many 'transformations' and creating new lists?
Let me some tips.
The definition is a bit unclear to me, but I think this will do it. Code is not very verbose, though. If it does what you intended, I can try to explain / make it simpler.
li = [['b', 'b', 'c', '3.2', 'text', '3', '5', '5'], ['a', 'w', '3', '4'], ['a', 'x', '3', '4'],['a','b'],['312','4']]
def join_to_last_text(lst: list, min_join: int = 1) -> list:
last_text = max((i for i,s in enumerate(lst) if s.isalpha()), default=min_join - 1)
return [' '.join(lst[:last_text + 1])] + lst[last_text + 1:]
output = [join_to_last_text(lst) for lst in li]
print(output)
# You can join a minimum of first items by setting a higher max default.
# If max does not find isalpha, it will use this value.
output_min_2 = [join_to_last_text(lst, min_join=2) for lst in li]
print(output_min_2)
#Johan Schiff's code works as expected but leaves a corner case - when the first element of the list is not a text. I have made a small change in his code to take care of that situation:
li = [['b', 'b', 'c', '3.2', 'text', '3', '5', '5'], ['a', 'w', '3', '4'], ['a', 'x', '3', '4'],['a','b'],['312','4']]
def join_to_last_text(lst: list) -> list:
first_text = min((i for i,s in enumerate(lst) if s.isalpha()), default=0)
last_text = max((i for i,s in enumerate(lst) if s.isalpha()), default=0)
return lst[:first_text] + [''.join(lst[first_text:last_text + 1])] + lst[last_text + 1:]
output = [join_to_last_text(lst) for lst in li]
print(output)
Where would this give a different output(a correct one)? Check out the following test case:
li = [['4','b', 'b', 'c', '3.2', 'text', '3', '5', '5'], ['a', 'w', '3', '4']]
#Johan's code would output -
[['5bbc3.2text', '3', '5', '5'], ['aw', '3', '4']]
whereas based on the following phrase in the question
If any element in the array is a string or if there is some other symbol between the two strings, it combines everything into one element
the output should be-
[['5', 'bbc3.2text', '3', '5', '5'], ['aw', '3', '4']]

using 'or' condition in re.split

I have a list of strings each one of those needs to be split when an 'y' or 'm' is found:
mylist = ['3m10y','10y20y','18m2y']
in the following items:
splitlist = [['3m','10y'],['10y','20y'],['18m','2y']]
i was thinking of using re.split() but I cannot use the 'or' condition in order to tell the function to split either when it finds an 'm' or an 'y'.
any help appreciated!
thanks
Try findall instead of split:
>>> re.findall(r'\d+[ym]', '3m10y')
['3m', '10y']
[my] is m or y.
>>> items = re.split(r'(m|y)', '10m2y4m55y55y53m')
>>> items
['10', 'm', '2', 'y', '4', 'm', '55', 'y', '55', 'y', '53', 'm', '']
>>> [''.join(p) for p in zip(items[::2], items[1::2])]
['10m', '2y', '4m', '55y', '55y', '53m']

Categories