Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
Set.java 4.30 KiB
package setgame;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;

/**
 * Set is the container for a game instance.
 * @author Paul A. Rubin (rubin@msu.edu)
 */
public final class Set {
  /** NCARDS is the number of cards in the game. */
  public static final int NCARDS = 81;
  /** NFEATURES is the number of features on a card. */
  public static final int NFEATURES = 4;
  /** NVALUES is the number of values a feature can have. */
  public static final int NVALUES = 3;
  /** SETSIZE is the number of cards in a set. */
  public static final int SETSIZE = 3;
  private final int[][] cards;  // the card deck
  private final HashMap<Integer, HashMap<Integer, HashSet<Integer>>>
    hasFeature;
  private int found;  // number of valid sets found

  /**
   * Constructor.
   */
  public Set() {
    cards = new int[NCARDS][NFEATURES];
    int[] divisors = new int[NFEATURES + 1];
    divisors[0] = 1;
    for (int i = 1; i <= NFEATURES; i++) {
      divisors[i] = NVALUES * divisors[i - 1];
    }
    // Fill in the cards.
    for (int c = 0; c < NCARDS; c++) {
      cards[c][0] = c % divisors[1];
      for (int f = 1; f < NFEATURES; f++) {
        cards[c][f] = (c % divisors[f + 1]) / divisors[f];
      }
    }
    // Fill in the feature possession map.
    hasFeature = new HashMap<>();
    for (int f = 0; f < NFEATURES; f++) {
      HashMap<Integer, HashSet<Integer>> m = new HashMap<>();
      for (int v = 0; v < NVALUES; v++) {
        HashSet<Integer> s = new HashSet<>();
        for (int c = 0; c < NCARDS; c++) {
          if (cards[c][f] == v) {
            s.add(c);
          }
        }
        m.put(v, s);
      }
      hasFeature.put(f, m);
    }
  }

  /**
   * Gets the set of cards with a particular value of a particular feature.
   * @param feature the feature
   * @param value the target value
   * @return the set of cards with that value
   */
  public Collection<Integer> findCards(final int feature, final int value) {
    return new HashSet<>(hasFeature.get(feature).get(value));
  }

  /**
   * Prints a set.
   * @param set the set to print
   */
  public void printSet(final Collection<Integer> set) {
    ArrayList<Integer> list = new ArrayList<>(set);
    Collections.sort(list);
    for (int c : list) {
      System.out.println(Arrays.toString(cards[c]) + " (" + c + ")");
    }
  }

  /**
   * Computes all valid sets by brute force (using recursion).
   * @return the number of sets found
   */
  public int findAllSets() {
    found = 0;
    HashMap<Integer, HashSet<Integer>> map = new HashMap<>();
    for (int f = 0; f < NFEATURES; f++) {
      map.put(f, new HashSet<>());
    }
    enumerate(1, 0, map);
    return found;
  }

  /**
   * Recursively enumerates all valid sets.
   * @param level the level (1 ... SETSIZE) at which the function is called
   * @param start the initial card index
   * @param values the accumulated values of all features
   */
  private void enumerate(final int level, final int start,
                         final HashMap<Integer, HashSet<Integer>> values) {
    // Check for vacuous cases (level or start index too high).
    if (level > SETSIZE || start == NCARDS) {
      return;
    }
    for (int c = start; c < NCARDS; c++) {
      // Clone the values map, adding the value of each feature in card c.
      HashMap<Integer, HashSet<Integer>> v = new HashMap<>();
      for (int f = 0; f < NFEATURES; f++) {
        HashSet<Integer> s = new HashSet<>(values.get(f));
        s.add(cards[c][f]);
        v.put(f, s);
      }
      if (level == SETSIZE) {
        // Final level: check for a valid set and if valid count it.
        boolean valid = true;
        for (int f = 0; f < NFEATURES; f++) {
          if (v.get(f).size() == 2) {
            valid = false;
            break;
          }
        }
        if (valid) {
          found += 1;
        }
      } else {
        // Call enumerate at the next level.
        enumerate(level + 1, c + 1, v);
      }
    }
  }

  /**
   * Gets the value of a particular feature for every card.
   * @param feature the feature
   * @return the vector of feature values
   */
  public int[] getValues(final int feature) {
    int[] v = new int[NCARDS];
    for (int c = 0; c < NCARDS; c++) {
      v[c] = cards[c][feature];
    }
    return v;
  }
}