0

I'm taking an introductory CS class, and one of the first projects had the following constraints:

You may NOT:

  • Make your program part of a package.
  • Add additional public methods or variables.
  • Use any built in Java Collections Framework classes anywhere in your program (e.g. no ArrayList, LinkedList, HashSet, etc.).
  • Use any arrays anywhere in your program.
  • Add any additional import statements (or use the “fully qualified name” to get around adding import statements).

Seeing the constraints for that project made me wonder what other things one could actually do within those constraints. The main limitation that I saw was the "no arrays" clause.

Is it possible to design a data structure subject to these constraints, which emulates the performance characteristics of an array? Specifically, the data structure should represent a sequence of fixed length, supporting operations to "get" or "set" by index, and these operations should take O(1) time, independent of the length of the sequence.

While it would be possible to build graph-like structures, like linked lists and trees, the "get" and "set" operations on these data structures would take O(n) or O(log n) time respectively. The only other thing I can think of is a class with a few thousand private fields and a switch statement to "get" or "set" by index, but this would only work for sequences up to a fixed length.

4
  • 6
    Also, you have told us what you are not allowed to do, but you haven't told us what the actual goal of the assignment is. What is the class that you are supposed to write? What does it do? What are the requirements? Commented Jan 23, 2020 at 22:33
  • I wouldn't expect a first project in an introductory CS class to need any arrays. Commented Jan 23, 2020 at 22:40
  • 2
    Lots of problems are solvable without using arrays. What are you trying to do? Commented Jan 23, 2020 at 22:57
  • 1
    I was mostly just interested in what I could create conceptually without abusing any of the built in mechanisms. This question is completely unrelated to the actual goal of the project, I was mostly just curious about what would even be possible without arrays/imports. Commented Jan 24, 2020 at 18:03

2 Answers 2

3

I think if you're following the spirit of the rules, then you provably can't do better than O(log n) time to get or set an element. The reason for this is that every object you instantiate can store at most a fixed number of data items and a fixed number of references to other objects, defined by how many fields that object has.

Let D be the (maximum) number of data items an object holds, and F be the (maximum) number of reference fields an object holds. To be clear, D counts the fields used to store the actual "array" data, and F counts the fields which are used for the data structure itself.

If your access times are O(1) then you can follow at most O(1) references to access the cell, which means your "array" size is limited to O(D * F^R) where R is a fixed limit on the number of references you're allowed to follow to fulfil one operation. If all three of D, F and R are constant, then so is the size of your "array". It follows that emulating the performance characteristics of an arbitrary-sized array data structure is impossible given the constraints.

This argument can be extended a little bit further to prove that R must be at least O(log n) in order to reach n distinct data items; i.e. that you must follow at least O(log n) references to access an item. You can use a complete binary tree to actually achieve this bound.


That said, there is at least one way to follow the letter of the rules without following the spirit of them.

You are strictly forbidden from using arrays or JCF library classes, but the only rules about third-party library classes are that you aren't allowed to import them or refer to them by a fully-qualified name. You could use the ClassLoader.loadClass method to load a collection class from a third-party library, instantiate it by reflection, assign it to a variable of type Object, and then call its methods by reflection. This is technically allowed because loadClass takes the "binary name", not the "fully qualified name" of the class you want to load. (I'll leave it to the lawyers to argue whether you would need to load a class whose binary name isn't also a fully qualified name.)

For the pedants: I interpret the rule about arrays as saying you must have no arrays in your code (except, presumably, String[] args in the main method), not no arrays in other people's code that your code calls; otherwise e.g. your program is forbidden from printing any output because data written to System.out gets buffered in an array. I think it is unlikely the rule is intended to forbid printing any output.

Sign up to request clarification or add additional context in comments.

5 Comments

Do you know the complexity of StringBuffer and StringBuilder? I think they don't violate the rules and could be used for chars or abused for small numbers.
@HeapOverflow I would think both are backed by arrays, so get and set by index would be O(1). That's indeed a considerably simpler way to follow the letter but not the spirit of the rules.
Another way is using System.getProperty and System.setProperty, which work with strings. So you could encode arbitrary data in a string, so long as you don't use an array to parse it.
Are those also O(1)? Do they use a hash table?
@HeapOverflow Yes, the System.getProperties method returns java.util.Properties, which extends Hashtable. docs.oracle.com/javase/8/docs/api/java/util/Properties.html
0

Without any addressable space, this leaves you with a tree structure, where each node holds a potential value and a number of children. One child per node would make it a linked list, two children a binary tree, and the more children per node you allow the faster the access becomes - eventually O(1) when the number of children per node fits or exceeds the array size.

The operations for an array are:

  • initialize with an initial number of slots
  • get length of array
  • get element at index
  • set element at index

I came up with following solution (binary tree, package and public modifier on methods omitted as requested, which makes it usable only from within the default package):

public class Array<T> {

    private final int length;
    private final Cell<T> root = new Cell<>();

    Array(int length) {
        if (length < 0) {
            throw new IllegalArgumentException("Array length must be >=0")
        }
        this.length = length;
    }

    T get(int index) {
        return cell(index).value;
    }

    void set(int index, T value) {
        cell(index).value = value;
    }

    int length() {
        return length;
    }

    private Cell<T> cell(int index) {
        if (index < 0 || index >= length) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        Cell<T> pointer = root;
        while (index > 0) {
            pointer = (index & 1) == 0 ? pointer.left() : pointer.right();
            index >>= 1;
        }
        return pointer;
    }

    private class Cell<T> {

        private Cell<T> left;
        private Cell<T> right;
        private T value;

        Cell<T> left() {
            return left = left != null ? left : new Cell<>();
        }

        Cell<T> right() {
            return right = right != null ? right : new Cell<>();
        }
    }
}

1 Comment

However many children per node you have, so long as it's a fixed number, the running time isn't O(1). You can only achieve O(1) time operations if the number of children per node is allowed to vary as a function of n.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.