Assistance with Python 'sort(key=None)' - python

I'm having a hard time understanding why my function is not returning the reversed version of my list. I've spent a long time trying to understand why and i hit a wall: ---it only returns my list in ascending order.
letters = 'abcdefghijk'
numbers = '123456'
dict1 = {}
def reverseOrder(listing):
lst2 = []
lst2.append(listing)
lst2.sort(reverse=True)
return lst2
for l, n in zip(letters, numbers):
dict1.update({l:n})
lst1 = list(dict1) + list(dict1.values())
lst1.sort(key=reverseOrder)
print(lst1)

The key function passed to list.sort has a very specific purpose:
key specifies a function of one argument that is used to extract a comparison key from each list element (for example, key=str.lower). The key corresponding to each item in the list is calculated once and then used for the entire sorting process. The default value of None means that list items are sorted directly without calculating a separate key value.
So the function is supposed to take in a single list element, and then return a key that determines its sorting compared to the other elements.
For example, if you wanted to sort a list by the length of their contents, you could do it like this:
def lengthOfItem (item):
return len(item)
lst.sort(key=lengthOfItem)
Since the function only takes a single item, it makes it unsuitable for sorting behaviors where you actually need to compare two elements in order to make a relation. But those sortings are very inefficient, so you should avoid them.
In your case, it seems like you want to reverse your list. In that case you can just use list.reverse().

You are using sort function in an invalid way.
Here is the definition of sort function (from builtins.py):
def sort(self, key=None, reverse=False): # real signature unknown; restored from __doc__
""" L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE* """
pass
key argument has to be used if there is 'ambiguity' on how items have to be sorted e.g. items are tuples, dictionaries, etc.
Example:
lst = [(1, 2), (2, 1)]
lst.sort(key=lambda x: x[0]) # lst = [(1, 2), (2, 1)]
lst.sort(key=lambda x: x[1]) # lst = [(2, 1), (1, 2)]
Not quite sure what you want with this part though:
for l, n in zip(letters, numbers):
dict1.update({l:n})
lst1 = list(dict1) + list(dict1.values())
Seems like you want a list of all numbers and letters but you are doing it an odd way.
Edit: I have updated answer.

Related

How do I implement a Schwartzian Transform in Python?

In Perl I sometimes use the Schwartzian Transform to efficiently sort complex arrays:
#sorted = map { $_->[0] } # sort by word length
sort { $a->[1] <=> $b->[1] } # use numeric comparison
map { [$_, length($_)] } # calculate the length of the string
#unsorted;
How to implement this transform in Python?
You don't need to. Python has this feature built in, and in fact Python 3 removed C-style custom comparisons because this is so much better in the vast majority of cases.
To sort by word length:
unsorted.sort(key=lambda item: len(item))
Or, because len is already an unary function:
unsorted.sort(key=len)
This also works with the built-in sorted function.
If you want to sort on multiple criteria, you can take advantage of the fact that tuples sort lexicographically:
# sort by word length, then alphabetically in case of a tie
unsorted.sort(key=lambda item: (len(item), item)))
While there should normally be no reason not to use the key argument for the sorted function or list.sort method, you can of course do without it, by creating a list of pairs (called tmp below) where the first item is the sort key and the second item is the original item.
Due to lexicographical sorting, sorting this list will sort by the key first. Then you can take the items in the desired order from the sorted tmp list of pairs.
example = ["hello", "spam", "foo"]
tmp = []
for item in example:
sort_key = len(item)
tmp.append((sort_key, item))
# tmp: [(5, "hello"), (4, "spam"), (3, "foo")]
tmp.sort()
# tmp: [(3, "foo"), (4, "spam"), (5, "hello")]
result = []
for _, item in tmp:
result.append(item)
# result: ["foo", "spam", "hello"]
Note that usually this would be written with list comprehensions instead of calling .append in a loop, but the purpose of this answer is to illustrate the underlying algorithm in a way most likely to be understood by beginners.

How can I compare two lists in python and return matches by email

I want to compare email into both list and put into new list,
a = [('abc#gmail.com',5),('xyz#gmail.com',6),('pqr#gmail.com',8)]
b = [('ABC','abc#gmail.com'),('XYZ','xyz#gmail.com'),('PQR','pqr#gmail.com')]
would return [('ABC',5),('XYZ',6),('PQR',8)], for instance.
Lookup in a list for each item if not already ordered is O(n) complexity and is not an ideal data structure for the process
It would be beneficial if you would convert the list you would be using for lookup converted to a dictionary
d_a = dict(a)
subsequent to which the lookup is both efficient and elegant
>>> [(key, d_a[value]) for key, value in b if value in d_a]
[('ABC', 5), ('XYZ', 6), ('PQR', 8)]
You should also take into consideration for negative case when the lookup key may not match or is present in the lookup list
Sorting both lists and using list comprehension:
a = [('abc#gmail.com',5),('xyz#gmail.com',6),('pqr#gmail.com',8)]
b = [('ABC','abc#gmail.com'),('XYZ','xyz#gmail.com'),('PQR','pqr#gmail.com')]
result = [(y[0],x[1]) for x,y in zip(sorted(a,key=lambda s:s[0])), sorted(b,key=lambda s:s[1])) if x[0]==y[1]]
list a is sorted based on first element(s[0]) of each tuple.
list b is sorted based on second element(s[1]) of each tuple.

sorting a list numerically that has string and integar value

I am looking for a code that can sort a list say for example list x, which contains integers and string. the code would then sort the list x so that the integer value is sorted corresponding to the string. so far I have tried this code however it does not work.
x =["a" 2,"c" 10, "b" 5]
x.sort()
print (x)
I want the result to be
["a" 2 "b" 5 "C" 10]
so the list is sorted numerically in acceding order and the string is also printed.
Use List of Tuples and then sort them according to what you want, example:
x = [('b',5),('a',2),('c',10)]
x.sort() # This will sort them based on item[0] of each tuple
x.sort(key=lambda s: s[1]) # This will sort them based on item[1] of each tuple
Another approach is to use dictionary instead of list of tuples, example:
x = {'b':5,'a':2,'c':10}#This will be automatically sorted based on the key of each element
if you print x, you will get:
{'a': 2, 'c': 10, 'b': 5}
if you want to sort them based on the value of each element, then:
x = sorted(x.items(), key=lambda s:s[1])
This will create a new list of tuples, since sorted() returns "new" sorted list, hence the result will be:
[('a', 2), ('b', 5), ('c', 10)]
If I deducted correctly you also want the resulting list to have an integer where the original list has an integer (and the same for characters).
I don't think there is an out-of-the-box way to do that. One possible approach is to separate your list into two others: one with integer, one with chars. Then, after sorting each list, you can merge them respecting the desired positions of integers and chars.
Use a nested iterable to pair the letters to numbers, then sort the items by the second elements:
# just pairs.sort(key = lambda x: x[1])
pairs = [('a', 2), ('c', 10), ('b', 5)]
I considered the elements are separate. The following code might help, you can fill or remove the print statement in the except block, as you wish.
x =["a", 2,"c", 10, "b", 5]
numbers = []
letters = []
for element in x:
try:
numbers.append(int(element))
except:
letters.append(str(element))
numbers.sort()
letters.sort()
numbers.reverse()
letters.reverse()
for index,item in enumerate(x):
try:
print int(item),
x[index] = numbers.pop()
except ValueError:
x[index] = letters.pop()
print "\n"+ str(x)

What does enumerate() mean?

What does for row_number, row in enumerate(cursor): do in Python?
What does enumerate mean in this context?
The enumerate() function adds a counter to an iterable.
So for each element in cursor, a tuple is produced with (counter, element); the for loop binds that to row_number and row, respectively.
Demo:
>>> elements = ('foo', 'bar', 'baz')
>>> for elem in elements:
... print elem
...
foo
bar
baz
>>> for count, elem in enumerate(elements):
... print count, elem
...
0 foo
1 bar
2 baz
By default, enumerate() starts counting at 0 but if you give it a second integer argument, it'll start from that number instead:
>>> for count, elem in enumerate(elements, 42):
... print count, elem
...
42 foo
43 bar
44 baz
If you were to re-implement enumerate() in Python, here are two ways of achieving that; one using itertools.count() to do the counting, the other manually counting in a generator function:
from itertools import count
def enumerate(it, start=0):
# return an iterator that adds a counter to each element of it
return zip(count(start), it)
and
def enumerate(it, start=0):
count = start
for elem in it:
yield (count, elem)
count += 1
The actual implementation in C is closer to the latter, with optimisations to reuse a single tuple object for the common for i, ... unpacking case and using a standard C integer value for the counter until the counter becomes too large to avoid using a Python integer object (which is unbounded).
It's a builtin function that returns an object that can be iterated over. See the documentation.
In short, it loops over the elements of an iterable (like a list), as well as an index number, combined in a tuple:
for item in enumerate(["a", "b", "c"]):
print item
prints
(0, "a")
(1, "b")
(2, "c")
It's helpful if you want to loop over a sequence (or other iterable thing), and also want to have an index counter available. If you want the counter to start from some other value (usually 1), you can give that as second argument to enumerate.
I am reading a book (Effective Python) by Brett Slatkin and he shows another way to iterate over a list and also know the index of the current item in the list but he suggests that it is better not to use it and to use enumerate instead.
I know you asked what enumerate means, but when I understood the following, I also understood how enumerate makes iterating over a list while knowing the index of the current item easier (and more readable).
list_of_letters = ['a', 'b', 'c']
for i in range(len(list_of_letters)):
letter = list_of_letters[i]
print (i, letter)
The output is:
0 a
1 b
2 c
I also used to do something, even sillier before I read about the enumerate function.
i = 0
for n in list_of_letters:
print (i, n)
i += 1
It produces the same output.
But with enumerate I just have to write:
list_of_letters = ['a', 'b', 'c']
for i, letter in enumerate(list_of_letters):
print (i, letter)
As other users have mentioned, enumerate is a generator that adds an incremental index next to each item of an iterable.
So if you have a list say l = ["test_1", "test_2", "test_3"], the list(enumerate(l)) will give you something like this: [(0, 'test_1'), (1, 'test_2'), (2, 'test_3')].
Now, when this is useful? A possible use case is when you want to iterate over items, and you want to skip a specific item that you only know its index in the list but not its value (because its value is not known at the time).
for index, value in enumerate(joint_values):
if index == 3:
continue
# Do something with the other `value`
So your code reads better because you could also do a regular for loop with range but then to access the items you need to index them (i.e., joint_values[i]).
Although another user mentioned an implementation of enumerate using zip, I think a more pure (but slightly more complex) way without using itertools is the following:
def enumerate(l, start=0):
return zip(range(start, len(l) + start), l)
Example:
l = ["test_1", "test_2", "test_3"]
enumerate(l)
enumerate(l, 10)
Output:
[(0, 'test_1'), (1, 'test_2'), (2, 'test_3')]
[(10, 'test_1'), (11, 'test_2'), (12, 'test_3')]
As mentioned in the comments, this approach with range will not work with arbitrary iterables as the original enumerate function does.
The enumerate function works as follows:
doc = """I like movie. But I don't like the cast. The story is very nice"""
doc1 = doc.split('.')
for i in enumerate(doc1):
print(i)
The output is
(0, 'I like movie')
(1, " But I don't like the cast")
(2, ' The story is very nice')
I am assuming that you know how to iterate over elements in some list:
for el in my_list:
# do something
Now sometimes not only you need to iterate over the elements, but also you need the index for each iteration. One way to do it is:
i = 0
for el in my_list:
# do somethings, and use value of "i" somehow
i += 1
However, a nicer way is to user the function "enumerate". What enumerate does is that it receives a list, and it returns a list-like object (an iterable that you can iterate over) but each element of this new list itself contains 2 elements: the index and the value from that original input list:
So if you have
arr = ['a', 'b', 'c']
Then the command
enumerate(arr)
returns something like:
[(0,'a'), (1,'b'), (2,'c')]
Now If you iterate over a list (or an iterable) where each element itself has 2 sub-elements, you can capture both of those sub-elements in the for loop like below:
for index, value in enumerate(arr):
print(index,value)
which would print out the sub-elements of the output of enumerate.
And in general you can basically "unpack" multiple items from list into multiple variables like below:
idx,value = (2,'c')
print(idx)
print(value)
which would print
2
c
This is the kind of assignment happening in each iteration of that loop with enumerate(arr) as iterable.
the enumerate function calculates an elements index and the elements value at the same time. i believe the following code will help explain what is going on.
for i,item in enumerate(initial_config):
print(f'index{i} value{item}')

Python:reduce list but keep details

say i have a list of items which some of them are similiar up to a point
but then differ by a number after a dot
['abc.1',
'abc.2',
'abc.3',
'abc.7',
'xyz.1',
'xyz.3',
'xyz.11',
'ghj.1',
'thj.1']
i want to to produce from this list a new list which collapses multiples but preserves some of their data, namely the numbers suffixes
so the above list should produce a new list
[('abc',('1','2','3','7'))
('xyz',('1','3','11'))
('ghj',('1'))
('thj',('1'))]
what I have thought, is the first list can be split by the dot into pairs
but then how i group the pairs by the first part without losing the second
I'm sorry if this question is noobish, and thanks in advance
...
wow, I didnt expect so many great answers so fast, thanks
from collections import defaultdict
d = defaultdict(list)
for el in elements:
key, nr = el.split(".")
d[key].append(nr)
#revert dict to list
newlist = d.items()
Map the list with a separator function, use itertools.groupby with a key that takes the first element, and collect the second element into the result.
from itertools import groupby, imap
list1 = ["abc.1", "abc.2", "abc.3", "abc.7", "xyz.1", "xyz.3", "xyz.11", "ghj.1", "thj.1"]
def break_up(s):
a, b = s.split(".")
return a, int(b)
def prefix(broken_up): return broken_up[0]
def suffix(broken_up): return broken_up[1]
result = []
for key, sub in groupby(imap(break_up, list1), prefix):
result.append((key, tuple(imap(suffix, sub))))
print result
Output:
[('abc', (1, 2, 3, 7)), ('xyz', (1, 3, 11)), ('ghj', (1,)), ('thj', (1,))]

Categories