How to avoid code duplication in unit tests - python

Suppose I have a function called "factorial" and I want to test this function. I often find myself rewriting unit tests like the one shown below where I define some test cases, possibly including some edge cases, running tests for all of those. This common pattern, defining the test values and expected output and running tests on them leaves me with the following boilerplate code. Essentially I would like to have one function to which I pass the list of test values with the list of expected values and the function to test it on and let the framework handle the rest for me. Does something like that exist and what would speaks against such a simplified approach?
import unittest
class TestRecursionAlgorithms(unittest.TestCase):
def test_factorial(self):
input_values = [1, 2, 3, 4, 5]
solutions = [1, 2, 6, 24, 120]
for idx, (input_value, expected_solution) in enumerate(zip(input_values, solutions)):
with self.subTest(test_case=idx):
self.assertEqual(expected_solution, factorial(input_value))
Cheers

You could use a variation of this.
input_values = [1, 2, 3, 4, 5]
solutions = [1, 2, 6, 24, 120]
result = dict(zip(input_values, solutions)) # Key:Value
print(result)
match = {i: k for i, k in result.items() if i == k} # Key Value comparison
print(match)
result:
{1: 1, 2: 2, 3: 6, 4: 24, 5: 120}
{1: 1, 2: 2}

Related

Updating key values in dictionaries

I am trying to write code for the following:
The idea is to have a storage/inventory dictionary and then have the key values be reduced by certain household tasks. E.g. cleaning, cooking etc.
This would be the storage dictionary:
cupboard= {"cookies":30,
"coffee":3,
"washingpowder": 5,
"cleaningspray": 5,
'Pasta': 0.5,
'Tomato': 4,
'Beef': 2,
'Potato': 2,
'Flour': 0.2,
'Milk': 1,
"Burger buns": 6}
now this is the code that I wrote to try and reduce one single key's value (idea is that the action "cleaning" reduces the key "cleaning spray" by one cleaning unit = 0.5
cleaning_amount = 0.5
def cleaning(room):
while cupboard["cleaningspray"] <0.5:
cleaned = {key: cupboard.get(key) - cleaning_amount for key in cupboard}
return cupboard
livingroom = 1*cleaning_amount
cleaning(livingroom)
print(cupboard)
but it returns this, which is the same dictionary as before, with no updated values
{'cookies': 30, 'coffee': 3, 'washingpowder': 5, 'cleaningspray': 5, 'Pasta': 0.5, 'Tomato': 4, 'Beef': 2, 'Potato': 2, 'Flour': 0.2, 'Milk': 1, 'Burger buns': 6}
Can anybody help?
Thank you!!
picture attached to see indents etc.
I guess you want to decrease the "cleaningspray" amount depending on the room size (or other factors). I would do it like this:
cleaning_amount = 0.5
def cleaning(cleaning_factor):
if cupboard["cleaningspray"] > 0.5:
# reduce the amount of cleaning spray depending on the cleaning_factor and the global cleaning_amount
cupboard["cleaningspray"] -= cleaning_factor * cleaning_amount
livingroom_cleaning_factor = 1
cleaning(livingroom_cleaning_factor)
print(cupboard)
Output:
{'cookies': 30, 'coffee': 3, 'washingpowder': 5, 'cleaningspray': 4.5, 'Pasta': 0.5, 'Tomato': 4, 'Beef': 2, 'Potato': 2, 'Flour': 0.2, 'Milk': 1, 'Burger buns': 6}
So I believe the reason that the values don't change is because it is being done in a for loop.
e.g.
list_values = [1, 2, 3, 4, 5]
new_variable = [num + 1 for num in list_values]
print("list_values", list_values) # The original list_values variable doesn't change
print("new_variable", new_variable) # This new variable holds the required value
This returns:
list_values [1, 2, 3, 4, 5]
new_variable [2, 3, 4, 5, 6]
So to fix the problem, you can use the 'new_variable'
So, now that the concept is clear (I hope), in your case it would be something like this
def cleaning():
while cupboard["cleaningspray"] > 0.5: # Also here, i beleive you intend to have `>`
#and not `<` in the original code
cleaned = {key: cupboard.get(key) - cleaning_amount for key in cupboard}
return cleaned
We return the 'new_variable' that is cleaned
so it can be assigned to the original dictionary variable as follows if required:
cupboard = cleaning()
EDIT:
Also, as #d-k-bo commented, if you intend to only have the operation carry out once... an if statement would also do the job
if cupboard["cleaningspray"] > 0.5: # Again assuming you intended '>' and not '<'
Otherwise, you should keep the return statement outside the while loop

Find every element's first index in a list

Here is a list a=[1,1,1,2,4,2,4,32,1,4,35,23,24,23]
I do this in python:
unique_number=list(set(a))
ans=map(lambda x:a.index(x),unique_number)
output:
<map at 0x2b98b307828>
I want to know what's wrong with my code and find an more efficient way to achieve this.
This code would work as you expected in Python 2. In Python 3, map returns an iterator. You could, e.g., convert it to a list:
>>> ans=map(lambda x:a.index(x),unique_number)
>>> list(ans)
[7, 0, 3, 10, 4, 11, 12]
You can avoid keep re-indexing and building a set first - simply build a dict iterating over a backwards as the dictionary will only keep the last value for a key (in this case - the earliest appearing index), eg:
a=[1,1,1,2,4,2,4,32,1,4,35,23,24,23]
first_index = {v:len(a) - k for k,v in enumerate(reversed(a), 1)}
# {1: 0, 2: 3, 4: 4, 23: 11, 24: 12, 32: 7, 35: 10}
This way you're only scanning the sequence once.
Try this:
for value in map(lambda x:a.index(x),unique_number):
print(value)
or append this:
for var in ans:
print(var)

PySpark's reduceByKey not working as expected

I'm writing a large PySpark program and I've recently run into trouble when using reduceByKey on an RDD. I've been able to recreate the problem with a simple test program. The code is:
from pyspark import SparkConf, SparkContext
APP_NAME = 'Test App'
def main(sc):
test = [(0, [i]) for i in xrange(100)]
test = sc.parallelize(test)
test = test.reduceByKey(method)
print test.collect()
def method(x, y):
x.append(y[0])
return x
if __name__ == '__main__':
# Configure Spark
conf = SparkConf().setAppName(APP_NAME)
conf = conf.setMaster('local[*]')
sc = SparkContext(conf=conf)
main(sc)
I would expect the output to be (0, [0,1,2,3,4,...,98,99]) based on the Spark documentation. Instead, I get the following output:
[(0, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 24, 36, 48, 60, 72, 84])]
Could someone please help me understand why this output is being generated?
As a side note, when I use
def method(x, y):
x = x + y
return x
I get the expected output.
First of all it looks like you actually want groupByKey not reduceByKey:
rdd = sc.parallelize([(0, i) for i in xrange(100)])
grouped = rdd.groupByKey()
k, vs = grouped.first()
assert len(list(vs)) == 100
Could someone please help me understand why this output is being generated?
reduceByKey assumes that f is associative and your method is clearly not. Depending on the order of operations the output is different. Lets say you start with following data for a certain key:
[1], [2], [3], [4]
Now add lets add some parentheses:
((([1], [2]), [3]), [4])
(([1, 2], [3]), [4])
([1, 2, 3], [4])
[1, 2, 3, 4]
and with another set of parentheses
(([1], ([2], [3])), [4])
(([1], [2, 3]), [4])
([1, 2], [4])
[1, 2, 4]
When you rewrite it as follows:
method = lambda x, y: x + y
or simply
from operator import add
method = add
you get an associative function and it works as expected.
Generally speaking for reduce* operations you want functions which are both associative and commutative.

Duplicating Items within a list

I am fairly new to python and am trying to figure out how to duplicate items within a list. I have tried several different things and searched for the answer extensively but always come up with an answer of how to remove duplicate items, and I feel like I am missing something that should be fairly apparent.
I want a list of items to duplicate such as if the list was [1, 4, 7, 10] to be [1, 1, 4, 4, 7, 7, 10, 10]
I know that
list = range(5)
for i in range(len(list)):
list.insert(i+i, i)
print list
will return [0, 0, 1, 1, 2, 2, 3, 3, 4, 4] but this does not work if the items are not in order.
To provide more context I am working with audio as a list, attempting to make the audio slower.
I am working with:
def slower():
left = Audio.getLeft()
right = Audio.getRight()
for i in range(len(left)):
left.insert(????)
right.insert(????)
Where "left" returns a list of items that are the "sounds" in the left headphone and "right" is a list of items that are sounds in the right headphone. Any help would be appreciated. Thanks.
Here is a simple way:
def slower(audio):
return [audio[i//2] for i in range(0,len(audio)*2)]
Something like this works:
>>> list = [1, 32, -45, 12]
>>> for i in range(len(list)):
... list.insert(2*i+1, list[2*i])
...
>>> list
[1, 1, 32, 32, -45, -45, 12, 12]
A few notes:
Don't use list as a variable name.
It's probably cleaner to flatten the list zipped with itself.
e.g.
>>> zip(list,list)
[(1, 1), (-1, -1), (32, 32), (42, 42)]
>>> [x for y in zip(list, list) for x in y]
[1, 1, -1, -1, 32, 32, 42, 42]
Or, you can do this whole thing lazily with itertools:
from itertools import izip, chain
for item in chain.from_iterable(izip(list, list)):
print item
I actually like this method best of all. When I look at the code, it is the one that I immediately know what it is doing (although others may have different opinions on that).
I suppose while I'm at it, I'll just point out that we can do the same thing as above with a generator function:
def multiply_elements(iterable, ntimes=2):
for item in iterable:
for _ in xrange(ntimes):
yield item
And lets face it -- Generators are just a lot of fun. :-)
listOld = [1,4,7,10]
listNew = []
for element in listOld:
listNew.extend([element,element])
This might not be the fastest way but it is pretty compact
a = range(5)
list(reduce(operator.add, zip(a,a)))
a then contains
[0, 0, 1, 1, 2, 2, 3, 3, 4, 4]
a = [0,1,2,3]
list(reduce(lambda x,y: x + y, zip(a,a))) #=> [0,0,1,1,2,2,3,3]

Merge python lists in a specific order/sequence

I'm trying to make two lists of the sort:
list_numbers = [1,2,3,4,5,6,7,8,9,10,11,12]
list_letters= ["onetothree", "fourtosix", "seventonine", "tentotwelve"]
into
list_both= ["onetothree",1,2,3,"fourtosix",4,5,6...]
This is just a way to describe my problem. I need to do this with all the elements in list_numbers & list_letters. The number or elements in list_numbers will always be dividable by the amount of elements in list_letters so theres no need to worry about "crooked data".
After searching for a good three hours, trying with many different kinds of "for" and "while" loops and only getting python 2.x questions, bad results and syntax errors, I thought I'd maybe deserve to post this question.
Hacky, but it'll get the job done
>>> list_numbers = [1,2,3,4,5,6,7,8,9,10,11,12]
>>> list_letters= ["onetothree", "fourtosix", "seventonine", "tentotwelve"]
>>> list(itertools.chain.from_iterable(zip(list_letters, *zip(*[list_numbers[i:i+3] for i in range(0, len(list_numbers), 3)]))))
['onetothree', 1, 2, 3, 'fourtosix', 4, 5, 6, 'seventonine', 7, 8, 9, 'tentotwelve', 10, 11, 12]
Or, the cleaner version:
>>> answer = []
>>> i = 0
>>> for letter in list_letters:
... answer.append(letter)
... for j in range(3):
... answer.append(list_numbers[i+j])
... i += j+1
...
>>> answer
['onetothree', 1, 2, 3, 'fourtosix', 4, 5, 6, 'seventonine', 7, 8, 9, 'tentotwelve', 10, 11, 12]
Of course, if you don't have sufficiently many entries in list_numbers, you this will burn you
try this:
list_numbers = [1,2,3,4,5,6,7,8,9,10,11,12]
list_letters= ["onetothree", "fourtosix", "seventonine", "tentotwelve"]
list_both=[]
c=1
for n in range(len(list_letters)):
list_both.append(list_letters[n])
list_both[c+n:c+n]=list_numbers[c-1:c+2]
c+=3
print(list_both)

Categories

Resources