3

I am trying to write a program that given an n-array of integers in increasing order and k-boxes, it splits the original array into the boxes of consecutive numbers, that is, as they appear in the input order

So far I have written the following code

int[] A = {1,2,3,4,5};
int k = 3;
int n = 5; 
for(int i = 0; i <= n - k; i++){
   for(int j = i+1; j < n-1; j++){
      int[] k1 = Arrays.copyOfRange(A, 0, i+1);
      int[] k2 = Arrays.copyOfRange(A, i+1, j+1);
      int[] k3 = Arrays.copyOfRange(A, j+1, n);
      System.out.println(Arrays.toString(k1) + "|" + Arrays.toString(k2) + "|" + Arrays.toString(k3));
   }
}

However, the problem with my code is that I have hardcoded the loops and the k-boxes and I am not sure how to resolve my problem.

The goal of the function is to generate all possibilities of element placement in each box.

Any help or ideas for the algorithm are appreciated!

Jon Doe
  • 379
  • 1
  • 3
  • 14
  • How many elements will be in a box? Should they be equal sized? – arslanbenzer Apr 18 '19 at 09:44
  • @arslanbenzer the boxes must not be empty, so at least 1 element and there is no upper limit – Jon Doe Apr 18 '19 at 09:45
  • Can you explain on what properties an element of the original should be placed in which array? – Mr. Wrong Apr 18 '19 at 09:47
  • If there are 3 boxes and 10 elements in your array, how should integers be distributed? 3-3-4 or 8-1-1, there are many possibilities – arslanbenzer Apr 18 '19 at 09:48
  • If you're looking for a datastructure, a `List` of arrays should do the trick. If you're asking for the algorithm to fill those boxes, as others have said, *you* should tell us. – Federico klez Culloca Apr 18 '19 at 09:51
  • @arslanbenzer The goal of the function is to generate all possibilities of element placement in each box. – Jon Doe Apr 18 '19 at 09:52
  • Just to make sure I understand, let's say you have your list `A=[1,2,3,4,5]` and you want to divide it into `k=3` non-empty boxes, then your expected output would be [something like this](https://tio.run/##ASwA0/9vc2FiaWX//y7FksqS4oKsZ8SAUH3KksucUX3Cu///MwpbMSwyLDMsNCw1XQ) (but implemented in Java of course)? – Kevin Cruijssen Apr 18 '19 at 09:57
  • @FedericoklezCulloca, I am asking for the algorithm or how to improve my current algorithm to be scaled to k boxes, not 3 – Jon Doe Apr 18 '19 at 09:57
  • @KevinCruijssen Yes that is precisely what I am looking for. – Jon Doe Apr 18 '19 at 09:59

2 Answers2

3

When searching a bit I came across this answer. You can use this to get all possible partitions of your input-list. You'll have to make one small modification to that code: holder.addAll(b); becomes holder.addAll(0,b);, so the values of b are added at the front instead of end, which means the original order is mostly retained instead of reversed.

After that you can use two filters:

  • One to check that all values in a (flattened) partition are still in the original order, removing any that are not.
  • And one to filter it based on the amount of chunks k (for which I've used a Java 8+ stream-filter).

Here a possible solution:

import java.util.*;
import java.util.stream.Collectors;
class Main{
  public static void main(String[] args) {
    int[] A = {1,2,3,4,5};

    // Convert your int-array to an Integer-ArrayList:
    List<Integer> inputList = Arrays.stream(A).boxed().collect(Collectors.toList());

    // Get all possible partitions of this List:
    List<List<List<Integer>>> partitions = partition(inputList);
    System.out.println("All partitions: ");
    prettyPrintPartitions(partitions);

    // Remove all items which aren't in the original order:
    filterPartitionsByOriginalOrder(partitions, A);
    System.out.println("\nPartitions that are in the original order: ");
    prettyPrintPartitions(partitions);

    // Filter them based on amount of chunks `k`:
    for(int k=2; k<A.length; k++){
      System.out.println("\nPartitions of size "+k+" (and also in the original order): ");
      List<List<List<Integer>>> filteredPartitions = filterPartitionsByAmountOfChunks(partitions, k);
      prettyPrintPartitions(filteredPartitions);
    }
  }

  private static void prettyPrintPartitions(List<List<List<Integer>>> partitions){
    for(List<List<Integer>> partition : partitions){
      System.out.println(partition);
    }
  }

  /* Method to get all partitions (all possible ways to divide the list in a variable amount of chunks) of a List of Integers */
  private static List<List<List<Integer>>> partition(List<Integer> inputList) {
    List<List<List<Integer>>> result = new ArrayList<>();
    if(inputList.isEmpty()){
      List<List<Integer>> empty = new ArrayList<>();
      result.add(empty);
      return result;
    }
    // Note that this algorithm only works if inputList.size() < 31
    // since you overflow int space beyond that. This is true even
    // if you use Math.pow and cast back to int.
    int limit = 1 << (inputList.size() - 1);
    // Note the separate variable to avoid resetting
    // the loop variable on each iteration.
    for(int j=0; j<limit; j++){
      List<List<Integer>> parts = new ArrayList<>();
      List<Integer> part1 = new ArrayList<>();
      List<Integer> part2 = new ArrayList<>();
      parts.add(part1);
      parts.add(part2);
      int i = j;
      for(Integer item : inputList){
        parts.get(i%2).add(item);
        i >>= 1;
      }
      for(List<List<Integer>> b : partition(part2)){
        List<List<Integer>> holder = new ArrayList<>();
        holder.add(part1);
        // Add them at the start instead of end so the items hold the original order
        holder.addAll(0, b);
        result.add(holder);
      }
    }
    return result;
  }

  /* Method to filter a List of List of List of Integers (partitions) based on a given amount of chunks `k` */
  private static List<List<List<Integer>>> filterPartitionsByAmountOfChunks(List<List<List<Integer>>> partitions, int k){
    List<List<List<Integer>>> filteredPartitions = partitions.stream()
                                                             .filter(partition -> partition.size() == k)
                                                             .collect(Collectors.toList());
    return filteredPartitions;
  }


  /* Method to remove any partition that (flattened) isn't in the same order as the original given int-array */
  private static void filterPartitionsByOriginalOrder(List<List<List<Integer>>> partitions, int[] A){
    partitions.removeIf(partition -> {
      int index = 0;
      for(List<Integer> part : partition){
        for(int value : part){
          // The value is not at the same index in the original array,
          // so remove the partition
          if(value != A[index]){
            return true;
          }
          index++;
        }
      }
      return false;
    });
  }
}

Which outputs:

All partitions: 
[[1, 2, 3, 4, 5]]
[[1], [2, 3, 4, 5]]
[[2], [1, 3, 4, 5]]
[[1, 2], [3, 4, 5]]
[[1], [2], [3, 4, 5]]
[[3], [1, 2, 4, 5]]
[[1, 3], [2, 4, 5]]
[[1], [3], [2, 4, 5]]
[[2, 3], [1, 4, 5]]
[[2], [3], [1, 4, 5]]
[[1, 2, 3], [4, 5]]
[[1], [2, 3], [4, 5]]
[[2], [1, 3], [4, 5]]
[[1, 2], [3], [4, 5]]
[[1], [2], [3], [4, 5]]
[[4], [1, 2, 3, 5]]
[[1, 4], [2, 3, 5]]
[[1], [4], [2, 3, 5]]
[[2, 4], [1, 3, 5]]
[[2], [4], [1, 3, 5]]
[[1, 2, 4], [3, 5]]
[[1], [2, 4], [3, 5]]
[[2], [1, 4], [3, 5]]
[[1, 2], [4], [3, 5]]
[[1], [2], [4], [3, 5]]
[[3, 4], [1, 2, 5]]
[[3], [4], [1, 2, 5]]
[[1, 3, 4], [2, 5]]
[[1], [3, 4], [2, 5]]
[[3], [1, 4], [2, 5]]
[[1, 3], [4], [2, 5]]
[[1], [3], [4], [2, 5]]
[[2, 3, 4], [1, 5]]
[[2], [3, 4], [1, 5]]
[[3], [2, 4], [1, 5]]
[[2, 3], [4], [1, 5]]
[[2], [3], [4], [1, 5]]
[[1, 2, 3, 4], [5]]
[[1], [2, 3, 4], [5]]
[[2], [1, 3, 4], [5]]
[[1, 2], [3, 4], [5]]
[[1], [2], [3, 4], [5]]
[[3], [1, 2, 4], [5]]
[[1, 3], [2, 4], [5]]
[[1], [3], [2, 4], [5]]
[[2, 3], [1, 4], [5]]
[[2], [3], [1, 4], [5]]
[[1, 2, 3], [4], [5]]
[[1], [2, 3], [4], [5]]
[[2], [1, 3], [4], [5]]
[[1, 2], [3], [4], [5]]
[[1], [2], [3], [4], [5]]

Partitions that are in the original order: 
[[1, 2, 3, 4, 5]]
[[1], [2, 3, 4, 5]]
[[1, 2], [3, 4, 5]]
[[1], [2], [3, 4, 5]]
[[1, 2, 3], [4, 5]]
[[1], [2, 3], [4, 5]]
[[1, 2], [3], [4, 5]]
[[1], [2], [3], [4, 5]]
[[1, 2, 3, 4], [5]]
[[1], [2, 3, 4], [5]]
[[1, 2], [3, 4], [5]]
[[1], [2], [3, 4], [5]]
[[1, 2, 3], [4], [5]]
[[1], [2, 3], [4], [5]]
[[1, 2], [3], [4], [5]]
[[1], [2], [3], [4], [5]]

Partitions of size 2 (and also in the original order): 
[[1], [2, 3, 4, 5]]
[[1, 2], [3, 4, 5]]
[[1, 2, 3], [4, 5]]
[[1, 2, 3, 4], [5]]

Partitions of size 3 (and also in the original order): 
[[1], [2], [3, 4, 5]]
[[1], [2, 3], [4, 5]]
[[1, 2], [3], [4, 5]]
[[1], [2, 3, 4], [5]]
[[1, 2], [3, 4], [5]]
[[1, 2, 3], [4], [5]]

Partitions of size 4 (and also in the original order): 
[[1], [2], [3], [4, 5]]
[[1], [2], [3, 4], [5]]
[[1], [2, 3], [4], [5]]
[[1, 2], [3], [4], [5]]

Try it online.

Kevin Cruijssen
  • 9,153
  • 9
  • 61
  • 135
  • 1
    Nice, giving credit to the other answer, but then adding a lot of helpful on content! – GhostCat Apr 18 '19 at 10:36
  • It work perfectly but there is a slight issue. It generates all possibilities ([[4, 5], [3], [1, 2]]) which is invalid as the boxes must have consecutive numbers, that is, as they appear in the input order. So if I merge all of the boxes, I would end up with the original array in the original order. i hope you get what I am trying to say – Jon Doe Apr 18 '19 at 11:01
  • @JonDoe Ah ok; yes, I understand what you mean. I had the same thing in the 05AB1E program I made in the comment. Which is why I added the additional `ʒ˜Q}` to the rest of the program `.Œʒ€gĀP}`, to filter the partitions and only keep those which are flattened equal to the input-list. Will try to think of something for a second filter in the Java code (although perhaps a different partition method would be -much- better for performance). – Kevin Cruijssen Apr 18 '19 at 11:05
  • @JonDoe Ok, I've added a second method to remove any partition that isn't in the original order. It might be better for performance if the three methods were combined somehow, so instead of creating a list with all partitions and removing/filtering those we don't need after, we somehow combine the three to avoid creating those partitions to begin with, but I don't know what would be a feasible way to accomplish this merge. This should do for now. – Kevin Cruijssen Apr 18 '19 at 11:33
  • @KevinCruijssen Perfect. Thanks a lot! – Jon Doe Apr 18 '19 at 11:38
  • @JonDoe You're welcome :) I actually learned a lot from answering this myself. Something like this is easy and took me a few minutes in 05AB1E, but in Java without convenient builtins, this was a bit harder. The main credit goes to the answer I linked, which I've used as a base. Will favorite your question as reference for myself. ;) – Kevin Cruijssen Apr 18 '19 at 11:41
1

Depending on this wekipedia post stars and bars and a similar answer of mine for somehow related question here on SO, here is another way without streams:

public static void main(String[] args) {
    int[] arr = {1,2,3,4,5,6,7};
    int k = 3;
    permute(arr,k);
}

public static void permute(int [] arr, int k ) {
    int combinations = (int) Math.pow(2, arr.length - 1);
    List<List<List<Integer>>> result = new ArrayList<>();
    for (int i = 0; i < combinations; i++) {
        String operators = String.format("%" + (arr.length - 1) + "s", Integer.toBinaryString(i)).replace(' ', '0');

        int count = 0;
        List<List<Integer>> tempListOfList = new ArrayList<>();
        List<Integer> tempList = new ArrayList<>();
        for (int x = 0; x < operators.length(); x++) {                
            tempList.add(arr[x]);
            if (operators.charAt(x) == '0') {
                if(count < k-1){
                    tempListOfList.add(tempList);
                    tempList = new ArrayList<>();                    
                }
                count++;
            }                
        }            
        tempList.add(arr[arr.length-1]);
        tempListOfList.add(tempList);
        if(count == k-1){
            result.add(tempListOfList);                
        }            
    }
    for(List<List<Integer>> parts: result){
        System.out.println(parts);
    }
}
Eritrean
  • 15,851
  • 3
  • 22
  • 28
  • Ah, very nice approach with the binary strings. And the code is also a lot more compact than mine, which is a plus for me (especially since I'm [a pretty active code-golfer](https://codegolf.stackexchange.com/users/52210/kevin-cruijssen)). ;) +1 from me! – Kevin Cruijssen Apr 18 '19 at 13:26
  • That works very good as well. I was wondering if there is a way to optimise it though. The main for loops goes through 2^n cycles – Jon Doe Apr 18 '19 at 16:56