Skip to content

Commit

Permalink
add init files
Browse files Browse the repository at this point in the history
  • Loading branch information
slxiao committed Nov 22, 2019
1 parent 8becccd commit 3d93cac
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[report]
exclude_lines =
pragma: no cover
if __name__ == .__main__.:
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.pyc
.vscode
build
partition.egg-info
dist
.tox
.coverage
htmlcov
1 change: 1 addition & 0 deletions partition/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.0.2"
15 changes: 15 additions & 0 deletions partition/argument.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import argparse

def get_parser():

parser = argparse.ArgumentParser()

parser.add_argument("--numbers", help="integer numbers to be partitioned, seperated by comma")

parser.add_argument("--grouplen", type=int, help="length of groups to hold the partitioned integer numbers, default is 2", default=2)

parser.add_argument("--algorithm", help="select partition algorithms, available options are greedy, kk and dp", choices=["greedy", "kk", "dp"])

parser.add_argument("--version", action='store_true', help="print version")

return parser
20 changes: 20 additions & 0 deletions partition/dp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import math
import numpy as np
import sys

def dp(number_list, group_len=2):
if group_len != 2:
sys.exit("unsupported group length: %s for DP algorithm!" % group_len)

n = len(number_list)
k = sum(number_list)
s = int(math.floor(k/2))
p = np.zeros((s+1, n+1))
p[0] = 1
for i in range(1, s+1):
for j in range(1, n+1):
if i - number_list[j-1] >= 0:
p[i][j] = p[i][j-1] or p[i-number_list[j-1]][j-1]
else:
p[i][j] = p[i][j-1]
return p[s][n]
6 changes: 6 additions & 0 deletions partition/greedy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def greedy(number_list, group_len=2):
groups = [[] for i in range(group_len)]
for i in sorted(number_list, reverse=True):
groups.sort(key=lambda x: sum(x))
groups[0].append(i)
return groups
28 changes: 28 additions & 0 deletions partition/kk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import heapq
try:
from Queue import LifoQueue
except ImportError:
from queue import LifoQueue
import sys

def kk(number_list, group_len=2): # Karmarkar-Karp heuristic
if group_len != 2:
sys.exit("unsupported group length: %s for KK algorithm!" % group_len)
pairs = LifoQueue()
group1, group2 = [], []
heap = [(-1*i, i) for i in number_list]
heapq.heapify(heap)
while len(heap) > 1:
i, labeli = heapq.heappop(heap)
j, labelj = heapq.heappop(heap)
pairs.put((labeli, labelj))
heapq.heappush(heap, (i-j, labeli))
group1.append(heapq.heappop(heap)[1])

while not pairs.empty():
pair = pairs.get()
if pair[0] in group1:
group2.append(pair[1])
elif pair[0] in group2:
group1.append(pair[1])
return [group1, group2]
43 changes: 43 additions & 0 deletions partition/partition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import sys

import argument
import greedy
import kk
import dp
from . import __version__

def partition(args=None):
parser = argument.get_parser()
args = parser.parse_args(args)
if args.version:
print("partition v" + __version__)
return args.version

number_list = [int(i) for i in args.numbers.split(",")]

if args.algorithm == "greedy":
groups = greedy.greedy(number_list, args.grouplen)
elif args.algorithm == "kk":
groups = kk.kk(number_list, args.grouplen)
elif args.algorithm == "dp":
#TODO: fix the dp algorithm bug
groups = dp.dp(number_list, args.grouplen)
else:
sys.exit("unsupported partition algorithm: %s" % args.algorithm)
print("Partition %s into %s groups, using algorithm: %s" % (args.numbers, args.grouplen, args.algorithm))
min_groupsum = min([sum(i) for i in groups])
max_groupsum = max([sum(i) for i in groups])
min_groupsum_indices, max_groupsum_indices = [], []
for i in range(len(groups)):
print("Group: %s, numbers: %s"%(i, groups[i]))
if sum(groups[i]) == min_groupsum:
min_groupsum_indices.append(i)
if sum(groups[i]) == max_groupsum:
max_groupsum_indices.append(i)
print("Min group sum: %s, Max group sum: %s, difference: %s" % (min_groupsum, max_groupsum, max_groupsum-min_groupsum))
print("Group(s) with min sum: %s" % ",".join([str(groups[i]) for i in min_groupsum_indices]))
print("Group(s) with max sum: %s" % ",".join([str(groups[i]) for i in max_groupsum_indices]))
return groups, max_groupsum-min_groupsum

if __name__ == "__main__":
partition()
38 changes: 38 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""partition: Python number partition algorithm library
"""

from setuptools import setup


version = "0.0.1"

setup(
include_package_data=True,
name='partition',
version=version,
packages=['partition'],
entry_points={
'console_scripts': [
'partition = partition.partition:partition',
]
},
url='https://github.com/slxiao/partition',
python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4',
license='MIT',
author='slxiao',
author_email='[email protected]',
description='Python number partition algorithm library',
classifiers=[
'Intended Audience :: Developers',
'Natural Language :: English',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Software Development :: Libraries :: Python Modules',
]
)
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import pytest

@pytest.fixture
def numbers():
return [4, 5, 6, 7, 8]
7 changes: 7 additions & 0 deletions tests/test_argument.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from partition import argument


def test_get_parser(mocker):
mocked_parser = mocker.patch("argparse.ArgumentParser")
argument.get_parser()
mocked_parser.assert_called()
4 changes: 4 additions & 0 deletions tests/test_dp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from partition import dp

def test_dp(numbers):
assert dp.dp(numbers) == 1
6 changes: 6 additions & 0 deletions tests/test_greedy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from partition import greedy

def test_greedy(numbers):
groups = greedy.greedy(numbers)
assert(min([sum(i) for i in groups])) == 13
assert(max([sum(i) for i in groups])) == 17
6 changes: 6 additions & 0 deletions tests/test_kk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from partition import kk

def test_kk(numbers):
groups = kk.kk(numbers)
assert(min([sum(i) for i in groups])) == 14
assert(max([sum(i) for i in groups])) == 16
24 changes: 24 additions & 0 deletions tests/test_partition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from partition import partition
import pytest

def test_partition_with_version(mocker):
mocked_parser = mocker.patch("partition.argument.get_parser", mocker.Mock(return_value=mocker.Mock()))
mocked_parser.return_value.parse_args.return_value = mocker.Mock(version="0.0.0")
assert partition.partition() == "0.0.0"
mocked_parser.assert_called()

def test_partition(mocker):
mocked_parser = mocker.patch("partition.argument.get_parser", mocker.Mock(return_value=mocker.Mock()))
mocked_parser.return_value.parse_args.return_value = mocker.Mock(version=None, numbers="1,2,3", grouplen=2, algorithm="greedy")
mocked_greedy = mocker.patch("partition.greedy.greedy", mocker.Mock(return_value=[[1, 2], [3]]))
assert partition.partition()[1] == 0
mocked_parser.assert_called()
mocked_greedy.assert_called_once_with([1,2,3],2)

def test_partition_nonexist_algorithm(mocker):
mocked_parser = mocker.patch("partition.argument.get_parser", mocker.Mock(return_value=mocker.Mock()))
mocked_parser.return_value.parse_args.return_value = mocker.Mock(version=None, numbers="1,2,3", algorithm="foo")
with pytest.raises(SystemExit) as pytest_wrapped_e:
partition.partition()
assert pytest_wrapped_e.type == SystemExit
assert pytest_wrapped_e.value.code == "unsupported partition algorithm: foo"

0 comments on commit 3d93cac

Please sign in to comment.