Permute the pathways across features for one side in the
    network. Used in `permute_pathways_across_features`

    Parameters
    -----------
    pathway_feature_tuples : list(tup(str, int))
      a tuple list [(pathway, feature)] where the pathway, feature pairing
      indicates that a pathway was overrepresented in that feature
    permutation_max_iters : int
      specify the maximum number of iterations, limit the number of attempts
      we have to generate a permutation

    Returns
    -----------
    list(tup(str, int)), the list of pathway, feature pairings after the
      permutation

def _pathway_feature_permutation(pathway_feature_tuples,
                                 permutation_max_iters):
    """Permute the pathways across features for one side in the
    network. Used in `permute_pathways_across_features`

    Parameters
    -----------
    pathway_feature_tuples : list(tup(str, int))
      a tuple list [(pathway, feature)] where the pathway, feature pairing
      indicates that a pathway was overrepresented in that feature
    permutation_max_iters : int
      specify the maximum number of iterations, limit the number of attempts
      we have to generate a permutation

    Returns
    -----------
    list(tup(str, int)), the list of pathway, feature pairings after the
      permutation
    """

    pathways, features = [list(elements_at_position)
                          for elements_at_position in
                          zip(*pathway_feature_tuples)]

    original_pathways = pathways[:]

    random.shuffle(pathways)
    feature_block_locations = {}
    i = 0
    while i < len(pathways):
        starting_index = i
        current_feature = features[i]
        pathway_set = set()
        # input is grouped by feature, so we want to keep track of the start
        # and end of a given "block" of the same feature--this corresponds
        # to all the pathways overrepresented in that feature.
        while i < len(pathways) and features[i] == current_feature:
            # check the results of the permutation. if `pathway_set` does
            # not contain the current pathway, we are maintaining the
            # necessary invariants in our permutation thus far.
            if pathways[i] not in pathway_set:
                pathway_set.add(pathways[i])
            else:
                k = 0
                random_pathway = None
                while True:
                    # select another random pathway from the list
                    # and get the feature to which it belongs
                    j = random.choice(range(0, len(pathways)))
                    random_pathway = pathways[j]
                    random_feature = features[j]
                    if (random_pathway != pathways[i] and
                            random_pathway not in pathway_set):
                        # if this is a feature we have not already seen,
                        # we are done.
                        if random_feature not in feature_block_locations:
                            break
                        # otherwise, look at the indices that correspond
                        # to that feature's block of pathways
                        feature_block_start, feature_block_end = \
                            feature_block_locations[random_feature]
                        pathway_block = pathways[feature_block_start:
                                                 feature_block_end]
                        # make sure that the current pathway is not in
                        # that block--ensures that we maintain the invariant
                        # after the swap
                        if pathways[i] not in pathway_block:
                            break
                    k += 1
                    if k > permutation_max_iters:
                        print("Permutation step: reached the maximum "
                              "number of iterations {0}.".format(
                                permutation_max_iters))
                        return None
                pathway_set.add(random_pathway)
                pathways[j] = pathways[i]
                pathways[i] = random_pathway
            i += 1
        ending_index = i
        feature_block_locations[current_feature] = (
            starting_index, ending_index)

    if original_pathways == pathways:
        return None
    return list(zip(pathways, features))