Skip to content
Permalink
Browse files

Merge branch 'test/django' into test/django-settings

  • Loading branch information...
chenc-allegheny committed Mar 14, 2019
2 parents 34f3a82 + edc57dc commit bf2bf4daec98a064f314001ea519588b519e9863
@@ -138,19 +138,6 @@ to a value larger than 1 and smaller than half of the total number of students.
pipenv run python3 gatorgrouper_cli.py --file filepath
```

### Group Size

To specify the size of the groups, use the flag `--group-size`.

```shell
pipenv run python3 gatorgrouper_cli.py --file filepath --group-size 4
```

This indicates that groups should each contain 4 members. The provided group
size should be greater than or equal to 2 and equal to or less than half the total
number of students. If the group size is not specified, the default group size
is 3.

### Number of groups

To specify the number of groups the students should be placed in, use the flag
@@ -163,7 +150,7 @@ pipenv run python3 gatorgrouper_cli.py --file filepath --num-groups 4
This indicates that the students should be divided into 4 groups. The number of
groups should be at minimum 2 and at maximum the number of half of the students to
be placed into groups. This flag can be used along side `--absentees`, `--method=random`,
and `--round-robin`.
and `--method=rrobin`.

### Random Grouping Method

@@ -182,10 +169,10 @@ small discussion groups, or peer editing.

### Round-robin Grouping Method

To group students using the round-robin method, use the flag `--round-robin`.
To group students using the round-robin method, use the flag `--method=rrobin`.

```shell
pipenv run python3 gatorgrouper_cli.py --file filepath --round-robin
pipenv run python3 gatorgrouper_cli.py --file filepath --method=rrobin
```

The round-robin method takes the responses from the Sheet into account when
@@ -257,6 +244,75 @@ pipenv run python3 gatorgrouper_cli.py --debug

If neither of these flags are set, logging will only be shown if an error occurs.

### Kernighan-Lin Grouping Method

The Kernighan-Lin algorithm creates a k-way graph partition that determines the
grouping of students based on their preferences for working with other students
and compatibility with other classmates. The graph recognizes student compatibility
through numerical weights (indicators of student positional relationship on the graph).
This grouping method allows for a systematic approach and balanced number of student
groups capable of tackling different types of work. Students should enter student
name, number of groups, objective weights (optional), objective_measures(optional),
students preferred to work with (optional), preference weight(optional),
and preferences_weight_match(optional). Note that number of groups must be at
least 2 and be a power of 2, i.e. 2, 4, 8...

NOTE: `--method graph` and `--num-group` are required to create groups.

It is required to use the graph argument to generate groups through the graph
partitioning. To generate groups using the Kernighan-Lin grouping algorithm use
the flag `--method graph`

```shell
pipenv run python gatorgrouper_cli.py --file filepath --method graph
--num-group NUMBER
```

To load student preferences, a preference weight, use the flag `--preferences`

```shell
pipenv run python gatorgrouper_cli.py --file filepath --method graph
--num-group NUMBER --preferences filepath
```

To indicate student preference weight use the flag `--preferences_weight`

```shell
pipenv run python gatorgrouper_cli.py --file filepath --method graph
--num-group NUMBER --preferences filepath --preferences_weight PREFERENCES_WEIGHT
```

To indicate preference weight match use the flag `--preferences_weight_match`

```shell
pipenv run python gatorgrouper_cli.py --file filepath --method graph
--num-group NUMBER --preferences filepath --preferences_weight PREFERENCES_WEIGHT
--preferences_weight_match PREFERENCES_WEIGHT_MATCH
```

To add objective measures use the flag `--objective_measures`

```shell
pipenv run python gatorgrouper_cli.py --file filepath --method graph
--num-group NUMBER --objective_measures LIST --objective_weights LIST
```

To add objective weights use the flag `--objective_weights`

```shell
pipenv run python gatorgrouper_cli.py --file filepath --method graph
--num-group NUMBER --objective_measures LIST --objective_weights LIST
```

A command line of all agruments would be:

```shell
pipenv run python gatorgrouper_cli.py --file filepath --method graph
--num-group NUMBER --preferences filepath --preferences-weight PREFERENCES_WEIGHT
--preferences-weight-match PREFERENCES_WEIGHT_MATCH --objective-measures LIST
--objective-weights LIST
```

### Full Example

```shell
BIN +60 KB (130%) db.sqlite3
Binary file not shown.
@@ -8,6 +8,11 @@
DEFAULT_GRPSIZE = 3
DEFAULT_NUMGRP = 3
DEFAULT_ABSENT = ""
DEFAULT_PREFERENCES = None
DEFAULT_PREFERENCES_WEIGHT = 1.1
DEFAULT_PREFERENCES_WEIGHT_MATCH = 1.3
DEFAULT_OBJECTIVE_WEIGHTS = None
DEFAULT_OBJECTIVE_MEASURES = None

# assertion
NONE = ""
@@ -1,6 +1,5 @@
"""Contains all of the group creation algorithms"""

import copy
import logging
import itertools
import random
@@ -9,41 +8,6 @@


# group_random.py
# pylint: disable=bad-continuation
def group_random_group_size(
responses: Union[str, List[List[Union[str, bool]]]], grpsize: int
) -> Union[List[List[List[Union[str, bool]]]], List[List[str]]]:
""" group responses using randomization approach """

# use itertools to chunk the students into groups
iterable = iter(responses)
groups = list(iter(lambda: list(itertools.islice(iterable, grpsize)), []))

# deal with the last, potentially partial group
last_group_index = len(groups) - 1
if len(groups[last_group_index]) < grpsize:

# distribute them throughout the other groups
logging.info("Partial group identified; distributing across other groups.")
lastgroup = groups[last_group_index]
outliers = copy.deepcopy(lastgroup)
groups.remove(lastgroup)
while outliers:
for group in groups:
if outliers:
group.append(outliers[0])
outliers = outliers[1:]
else:
break

# scoring and return
scores, ave = [], 0
scores, ave = group_scoring.calculate_avg(groups)
logging.info("scores: %s", str(scores))
logging.info("average: %d", ave)
return groups


def group_random_num_group(responses: str, numgrp: int) -> List[List[str]]:
""" group responses using randomization approach """
# number of students placed into a group
@@ -84,48 +48,6 @@ def shuffle_students(


# group_rrobin.py
def group_rrobin_group_size(responses, grpsize):
""" group responses using round robin approach """

# setup target groups
groups = list() # // integer div
responsesToRemove = list()
numgrps = len(responses) // grpsize
logging.info("target groups: %d", numgrps)
for _ in range(numgrps):
groups.append(list())

# choose a random column from the student responses as the priority
# column to distribute students by
indices = list(range(0, numgrps))
random.shuffle(indices)
target_group = itertools.cycle(indices)
priorityColumn = random.randint(1, len(responses[0]) - 1)
logging.info("column priority: %d", priorityColumn)

# iterate through the responses and check if the priority column is true
# if it is, add that response to the next group
for response in responses:
if response[priorityColumn] is True:
groups[target_group.__next__()].append(response)
responsesToRemove.append(response)

# remove the responses that were already added to a group
responses = [x for x in responses if x not in responsesToRemove]

# disperse anyone not already grouped
while responses:
groups[target_group.__next__()].append(responses[0])
responses.remove(responses[0])

# scoring and return
scores, ave = [], 0
scores, ave = group_scoring.calculate_avg(groups)
logging.info("scores: %s", str(scores))
logging.info("average: %d", ave)
return groups


def group_rrobin_num_group(responses, numgrps):
""" group responses using round robin approach """

@@ -144,10 +66,6 @@ def group_rrobin_num_group(responses, numgrps):
priorityColumn = random.randint(1, len(responses[0]) - 1)
logging.info("column priority: %d", priorityColumn)

# iterate through the responses and check if the priority column is true
# if it is, add that response to the next group
logging.info("column priority: %d", priorityColumn)

# iterate through the responses and check if the priority column is true
# if it is, add that response to the next group
for response in responses:
@@ -10,14 +10,18 @@

def recursive_kl(graph: Graph, numgrp=2) -> List[Set[int]]:
"""
Recursively use Kernighan-Lin algorithm to create a k-way graph partition
Recursively use the Kernighan-Lin algorithm to create a k-way graph partition.
This function will either return two groups or more than two depending on the
value of numgrp. Each group generated is different from the previous.
"""
power = log(numgrp, 2)
if power != int(power) or power < 1:
raise ValueError("numgrp must be a power of 2 and at least 2.")
# For a group of two bisect it and return two groups
if numgrp == 2:
# Base case for recursion: use Kernighan-Lin to create 2 groups
return list(kernighan_lin_bisection(graph))
# For the next group of two divide numgrp by 2
next_numgrp = numgrp / 2
groups = []
for subset in kernighan_lin_bisection(graph):
@@ -31,10 +35,11 @@ def total_cut_size(graph: Graph, partition: List[int]) -> float:
Computes the sum of weights of all edges between different subsets in the partition
"""
cut = 0.0
# Edges are added from the nodes on the graph, creating subsets
for i, subset1 in enumerate(partition):
for subset2 in partition[i:]:
# Sum of weights added from all subsets and set equal to cut
cut += cut_size(graph, subset1, T=subset2)
print(subset1, subset2, cut)
return cut


@@ -58,10 +63,13 @@ def compatibility(
If no measures are specified, "avg" is used as a default.
"""
if not len(a) == len(b):
raise Exception("Tuples passed to compatibility() must have same size")
# Raise an exception notice if student tuples don't match
raise Exception("Tuples passed to compatibility() must have same size.")
if objective_weights is None:
# Return length
objective_weights = [1] * len(a)
if objective_measures is None:
# Default to return average if set equal to None
objective_measures = ["avg"] * len(a)
scores = []
for a_score, b_score, weight, measure in zip(
@@ -80,6 +88,8 @@ def compatibility(
compat = int(a_score == b_score)
elif measure == "diff":
compat = abs(a_score - b_score)
else:
raise Exception("Invalid measure")

# Scale the compatibility of a[i] and b[i] using the i-th objective weight
scores.append(compat * weight)
@@ -96,7 +106,8 @@ def group_graph_partition(
preferences_weight_match=1.3,
):
"""
Form groups using recursive Kernighan-Lin algorithm
Form groups using recursive Kernighan-Lin algorithm by reading in students list
and weight list and partitioning the vertices.
"""
# Read in students list and the weight list
students = [item[0] for item in inputlist]
@@ -133,17 +144,3 @@ def group_graph_partition(
for p in partition:
groups.append([inputlist[i] for i in p])
return groups


if __name__ == "__main__":
student_data = [
["one", 0, 0],
["two", 0, 0.5],
["three", 0.5, 0],
["four", 0.75, 0.75],
["five", 0.8, 0.1],
["six", 0, 1],
["seven", 1, 0],
["eight", 1, 1],
]
student_groups = group_graph_partition(student_data, 4)
Oops, something went wrong.

0 comments on commit bf2bf4d

Please sign in to comment.
You can’t perform that action at this time.