Related
I was resolving a HackerRank quiz, which consisted in emulating a pagination request.
The function received an array of items, the sort parameter, the sort order, the page number, and the items per page, and returns an array of names corresponding to the given page.
The items had the shape [name: string, relevance: int, price: int].
The sort parameter was 0 for name, 1 for relevance, and 2 for price.
The sort order was 0 for ascending and 1 for descending.
I tried the function in JavaScript and in Python, in case the default sorting function worked different and altered the results. This is my implementation in Python3.
def fetchPaginated(items, sortParameter, sortOrder, pageNum, itemsPerPage):
sortedItems = sorted(items, key=lambda item: item[sortParameter], reverse=False if sortOrder == 0 else True)
paginatedIdx = pageNum * itemsPerPage
slicedItems = sortedItems[paginatedIdx:(paginatedIdx + itemsPerPage)]
return map(lambda item: item[0], slicedItems)
Since HackerRank has hidden test cases, I don't know the inputs for the failing tests. I remember that the size of the array was in the order of the 100s and 1000s, the page number was between 0 and 2, the items per page between 1 and 20. There wasn't a pattern for the sort parameter and order (it wasn't like all the failing tests were for the sort parameter 1 or similar).
Could someone indicate me if my code, or the algorithm behind it has a flaw I don't detect? Maybe a flaw that makes it fail on edge cases?
EDIT:
Link to the HackerRank question. I don't know if it's available to everyone: https://www.hackerrank.com/test/78113p6eaqn
I also had this question on an assessment. The problem is that the input for items is a 2D list of strings, not a list of [name: string, relevance: int, price: int]. Thus when you called sort you were sorting strings of numbers instead of actual numbers, which led to unexpected results. For example:
>>> nums = list(range(1, 6)) + list(range(10, 60, 10))
>>> nums = list(map(str, nums))
>>> nums
['1', '2', '3', '4', '5', '10', '20', '30', '40', '50']
>>> nums.sort()
>>> nums
['1', '10', '2', '20', '3', '30', '4', '40', '5', '50']
A small fix would be to change the key function so that it converts relevance and price (columns 1 and 2) to ints while leaving name (column 0) alone.
sortedItems = sorted(items, key=lambda item: int(item[sortParameter]) if sortParameter else item[sortParameter], reverse=sortOrder)
Given a list which contains both strings and None values, in which some of the strings have embedded newlines, I wish to split the strings with newlines into multiple strings and return a flattened list.
I've written code to do this using a generator function, but the code is rather bulky and I'm wondering if it's possible to do it more concisely using a list comprehension or a function from the itertools module. itertools.chain doesn't seem to be able to decline to iterate any non-iterable elements.
def expand_newlines(lines):
r"""Split strings with newlines into multiple strings.
>>> l = ["1\n2\n3", None, "4\n5\n6"]
>>> list(expand_newlines(l))
['1', '2', '3', None, '4', '5', '6']
"""
for line in lines:
if line is None:
yield line
else:
for l in line.split('\n'):
yield l
You can use yield from.
def expand(lines):
for line in lines:
if isinstance(line,str):
yield from line.split('\n')
elif line is None:
yield line
list(expand(l))
#['1', '2', '3', None, '4', '5', '6']
Here's a single line, but I think #Ch3steR's solution is more readable.
from itertools import chain
list(chain.from_iterable(i.splitlines() if i is not None and '\n' in i else [i]
for i in lines))
You could use itertools.chain if you did the following
import itertools
def expand_newlines(lines):
return itertools.chain.from_iterable(x.split("\n") if x else [None]
for x in lines)
Using more_itertools.collapse to flatten nested lists:
Given
import more_itertools as mit
lst = ["1\n2\n3", None, "7\n8\n9"]
Demo
list(mit.collapse([x.split("\n") if x else x for x in lst ]))
# ['1', '2', '3', None, '7', '8', '9']
more_itertools is a third-party package. Install via > pip install more_itertools.
If you might modify list inplace then you might do:
lst = ["1\n2\n3", None, "4\n5\n6"]
for i in range(len(lst))[::-1]:
if isinstance(lst[i], str):
lst[i:i+1] = lst[i].split("\n")
print(lst) # ['1', '2', '3', None, '4', '5', '6']
this solution utilize fact that you might not only get python's list slices, but also assign to them. It moves from right to left, as otherwise I would need to keep count of additional items, which would make it harder.
Similar to #blueteeth's answer but more concise by way of inverting the logic:
import itertools
chainfi = itertools.chain.from_iterable
def expand_newlines(lines):
r"""Split strings with newlines into multiple strings.
>>> l = ["1\n2\n3", None, "4\n5\n6"]
>>> list(expand_newlines(l))
['1', '2', '3', None, '4', '5', '6']
"""
return chainfi([None] if l is None else l.split('\n') for l in lines)
None is the special case so that's what we should be checking for.
This is concise enough that I wouldn't even bother writing a function for it—I just kept it in the function to confirm it works via doctest.
Assume I'd like to convert a list of strings to integer, but it cannot be done for all elements.
I know this works:
a = ['2.0','3.0','4.0','5.0','Cherry']
b = []
for k in a:
try:
int(k)
b.append(int(k))
except:
pass
print b
> [2, 3, 4, 5]
But is there also a shorter way of doing this? I thought about something like:
b = [try int(k) for k in a]
This may sound like a silly question since I do have a working solution, but I have often been shown shorter ways of doing the same thing and always appreciated this kind of help. I am using Python 2.7
Thanks!
Edit: sorry, I was also talking about floating point. I just changed my example data
There is no way to use try/except clauses inside List Comprehensions but this could help:
a = ['2','3','4','5','Cherry']
print [int(x) for x in a if x.isdigit()]
Output:
['2', '3', '4', '5']
Update (as the question was updated):
This could help but I don't know how good/accurate is to use it:
a = ['2.0','3.0','4.0','5.0', '44545.45', 'Cherry']
[float(x) for x in a if x.split('.')[0].isdigit() and x.split('.')[1].isdigit()]
Output:
[2.0, 3.0, 4.0, 5.0, 44545.45]
Try this one.
def is_number(k):
try:
float(k)
except ValueError:
return False
return True
[int(float(k)) for k in a if is_number(k)]
If you want to compress try-except into one line then i think answer is NO and it is answered at here.
I would go with mix of regex and isinstance check-It captures all number types e.g. float, long, int and complex-
>>>a=['2', '3', '4', '5', 'Cherry', '23.3', '-3']
>>>filter(bool,[i if isinstance(eval(i),(int, long, float, complex)) else None for i in filter(lambda x: re.findall(r'[-+]?\d+[\.]?\d*',x),a)])
>>>['2', '3', '4', '5', '23.3', '-3']
If you want to capture only floats-
>>>filter (bool,[i if isinstance(eval(i),float) else None for i in filter(lambda x: re.findall(r'[-+]?\d+[\.]?\d*',x),a)])
>>>['23.3']
If you want to capture only int-
>>>filter (bool,[i if isinstance(eval(i),int) else None for i in filter(lambda x: re.findall(r'[-+]?\d+[\.]?\d*',x),a)])
>>>['2', '3', '4', '5', '-3']
I'm trying to sort data from a text file and show it in python.
So far i have:
text_file = open ("Class1.txt", "r")
data = text_file.read().splitlines()
namelist, scorelist = [],[]
for li in data:
namelist.append(li.split(":")[0])
scorelist.append(li.split(":")[1])
scorelist.sort()
print (scorelist)
text_file.close()
It sorts the the data, however it only reads the first number:
['0', '0', '10', '3', '3', '5']
It reads 10 as "1"
This is what my text file looks like:
Harry:3
Jarrod:10
Jacob:0
Harold:5
Charlie:3
Jj:0
It's lexographically sorting, if you need integer sorting, append the split as an int
scorelist.append(int(li.split(":")[1]))
Since scorelist is a list of strings, "10" shows up before "3" because the first character in "10" is less than the first character in "3" (lexicographic sorting -- like words in a dictionary). The trick here is to tell python to sort integers. You can do that as the other answers point out by sorting a list of integers rather than a list of strings, OR you could use a key function to sort:
scorelist.sort(key=int)
This tells python to sort the items as integers rather than as strings. The nice thing here is that you don't need to change the data at all. You still end up with a list of strings rather than a list of integers -- you just tell python to change how it compares the strings. Neat.
demo:
>>> scorelist = ['3', '10', '0', '5', '3', '0']
>>> scorelist_int = [int(s) for s in scorelist]
>>>
>>> scorelist.sort(key=int)
>>> scorelist
['0', '0', '3', '3', '5', '10']
>>>
>>> scorelist_int.sort()
>>> scorelist_int
[0, 0, 3, 3, 5, 10]
The data are actually strings. The sort is done like in a dictionary.
You should convert scores into int:
scorelist.append(int(li.split(":")[1]))
I'm working on a script where I have a list of tuples like ('1','2','3','4'). e.g.:
list = [('1','2','3','4'),
('2','3','4','5'),
('3','4','5','6'),
('4','5','6','7')]
Now I need to add '1234', '2345','3456' and '4567' respectively at the end of each tuple. e.g:
list = [('1','2','3','4','1234'),
('2','3','4','5','2345'),
('3','4','5','6','3456'),
('4','5','6','7','4567')]
Is it possible in any way?
Tuples are immutable and not supposed to be changed - that is what the list type is for.
However, you can replace each tuple using originalTuple + (newElement,), thus creating a new tuple. For example:
t = (1,2,3)
t = t + (1,)
print(t)
(1,2,3,1)
But I'd rather suggest to go with lists from the beginning, because they are faster for inserting items.
And another hint: Do not overwrite the built-in name list in your program, rather call the variable l or some other name. If you overwrite the built-in name, you can't use it anymore in the current scope.
Based on the syntax, I'm guessing this is Python. The point of a tuple is that it is immutable, so you need to replace each element with a new tuple:
list = [l + (''.join(l),) for l in list]
# output:
[('1', '2', '3', '4', '1234'),
('2', '3', '4', '5', '2345'),
('3', '4', '5', '6', '3456'),
('4', '5', '6', '7', '4567')]
As mentioned in other answers, tuples are immutable once created, and a list might serve your purposes better.
That said, another option for creating a new tuple with extra items is to use the splat operator:
new_tuple = (*old_tuple, 'new', 'items')
I like this syntax because it looks like a new tuple, so it clearly communicates what you're trying to do.
Using splat, a potential solution is:
list = [(*i, ''.join(i)) for i in list]
In Python, you can't. Tuples are immutable.
On the containing list, you could replace tuple ('1', '2', '3', '4') with a different ('1', '2', '3', '4', '1234') tuple though.
As other people have answered, tuples in python are immutable and the only way to 'modify' one is to create a new one with the appended elements included.
But the best solution is a list. When whatever function or method that requires a tuple needs to be called, create a tuple by using tuple(list).
I was going through some details related to tuple and list, and what I understood is:
Tuples are Heterogeneous collection data type
Tuple has Fixed length (per tuple type)
Tuple are Always finite
So for appending new item to a tuple, need to cast it to list, and do append() operation on it, then again cast it back to tuple.
But personally what I felt about the Question is, if Tuples are supposed to be finite, fixed length items and if we are using those data types in our application logics then there should not be a scenario to appending new items OR updating an item value in it.
So instead of list of tuples it should be list of list itself, Am I right on this?
list_of_tuples = [('1', '2', '3', '4'),
('2', '3', '4', '5'),
('3', '4', '5', '6'),
('4', '5', '6', '7')]
def mod_tuples(list_of_tuples):
for i in range(0, len(list_of_tuples)):
addition = ''
for x in list_of_tuples[i]:
addition = addition + x
list_of_tuples[i] = list_of_tuples[i] + (addition,)
return list_of_tuples
# check:
print mod_tuples(list_of_tuples)
A lot of people is writing tuples are immutables... and that's right! But there is this code:
tuple = ()
for i in range(10):
tuple += (i,)
print(tuple)
And It works! Why? Well.. that's because in the code there is a "=". Every time you use the operator "=" in Python you assign a new value to an object: you create a new variable!
So you are not modifying the old tuple: you are creating a new one, with the same name (tuple), with value the old one plus something more.
OUTPUTS = []
for number in range(len(list_of_tuples))):
tup_ = list_of_tuples[number]
list_ = list(tup_)
item_ = list_[0] + list_[1] + list_[2] + list_[3]
list_.append(item_)
OUTPUTS.append(tuple(list_))
OUTPUTS is what you desire
In case of tuple and list, a very simple way can be (suppose) :
tpl = ( 3, 6, 9)
lst = [ 12, 15, 18]
my_tuple = tpl + tuple(lst)
print(my_tuple)