Skip to content

Instantly share code, notes, and snippets.

@youssef3wi
Created October 31, 2024 13:01
Show Gist options
  • Select an option

  • Save youssef3wi/951efa5ccf835215cafdaf904d4e3fca to your computer and use it in GitHub Desktop.

Select an option

Save youssef3wi/951efa5ccf835215cafdaf904d4e3fca to your computer and use it in GitHub Desktop.
Back to school

Back to school

It's back-to-school time!

In order to buy supplies for your kids, you have to go to 4 different types of shop:

  • a bookshop to buy a workbook,
  • a school shop to buy some glue,
  • a sport store to buy a pair of sneakers,
  • a stationery store to buy a pencil.

Shopping for school isn't a fun hobby for you, so you started to search for the shortest path. Then you realized your town has many shops of these types, and it may take too long to evaluate all the paths one by one.

So, before searching for the best path, you just want to count them.

The type of shop is encoded in an integer from 0 to 3. The 0 is bookshop, type 1 is school shop, type 2 is sport store, type 3 is stationery.

As input, you get a description of your town in the following form :

  • shops: a list of integers, telling the shop types. For example [2, 3, 1, 1, 0] means the shop at index 0 is a sport store, the shop at index 1 is a stationery store, the shops at index 2 and 3 are school shops, etc.
  • roads: a list of a couple of 2 integers, showing the roads between shops. For example [[0, 1], [0, 3]] means there is a road between the shop of index 0 and the shop of index 1, then another road between the shop of index 0 and the shop index 3.

All roads are bi-directional. There is at most 1 road between 2 shops. So you will never have [[2, 5], [5, 2]].

You must return the number of valid paths. A valid path visits exactly one shop of each type, in any order, by using the available roads.

Visiting the same 4 shops in two different orders counts as two valid paths.

There are a maximum of 50 shops and 250 roads.

Example

With these inputs:

shops = [1, 0, 3, 2, 2]
roads = [[0, 1], [1, 2], [1, 4], [2, 3], [2, 4]]

The town looks like this:

  • 1 -> 0
  • 0 -> 3
  • 3 -> 2
  • 2 -> 0

The valid paths are below; each list contains the 4 shop indexes to visit:

[0, 1, 2, 3]
[0, 1, 2, 4]
[0, 1, 4, 2]
[3, 2, 1, 0]
[4, 2, 1, 0]
[2, 4, 1, 0]

As there are 6 solutions, your code must output 6.

public static int countPaths(List<Integer> shops, List<List<Integer>> roads)

  • Parameters:
    • shops — The type of each shop, ordered according to their indexes.
    • roads — The roads between shops. Each element is a couple of two integers.
  • Returns: the number of paths that visits one shop of each type.
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static java.util.Arrays.asList;
/**
* It's back-to-school time!
* <p>
* In order to buy supplies for your kids, you have to go to 4 different types of shop:
* <ul>
* <li>a bookshop to buy a workbook,</li>
* <li>a school shop to buy some glue,</li>
* <li>a sport store to buy a pair of sneakers,</li>
* <li>a stationery store to buy a pencil.</li>
* </ul>
* Shopping for school isn't a fun hobby for you, so you started to search for the shortest path.
* Then you realized your town has many shops of these types, and it may take too long
* to evaluate all the paths one by one.
* <p>
* So, before searching for the best path, you just want to count them.
* <p>
* The type of shop is encoded in an integer from 0 to 3.
* The 0 is bookshop, type 1 is school shop, type 2 is sport store, type 3 is stationery.
* <p>
* As input, you get a description of your town in the following form :
* <ul>
* <li>{@code shops}: a list of integers, telling the shop types. For example {@code [2, 3, 1, 1, 0]}
* means the shop at index 0 is a sport store, the shop at index 1 is a stationery store, the shops
* at index 2 and 3 are school shops, etc.</li>
* <li>{@code roads}: a list of a couple of 2 integers, showing the roads between shops. For
* example {@code [[0, 1], [0, 3]]} means there is a road between the shop of index 0 and the shop
* of index 1, then another road between the shop of index 0 and the shop index 3.</li>
* </ul>
*
* <b>All roads are bi-directional. There is at most 1 road between 2 shops.</b> So you will never
* have {@code [[2, 5], [5, 2]]}.
* <p>
* You must return the number of valid paths.
* A valid path visits exactly one shop of each type, in any order, by using the available roads.
* <p>
* Visiting the same 4 shops in two different orders counts as two valid paths.
* <p>
* <b>There are a maximum of 50 shops and 250 roads.</b>
* <p>
* <h2>Example</h2>
* With these inputs:
* <br>
* {@code shops = [1, 0, 3, 2, 2]}
* <br>
* {@code roads = [[0, 1], [1, 2], [1, 4], [2, 3], [2, 4]]}
* <br>
* The town looks like this:
* <ul>
* <li>1 -> 0</li>
* <li>0 -> 3</li>
* <li>3 -> 2</li>
* <li>2 -> 0</li>
* </ul>
* The valid paths are below; each list contains the 4 shop indexes to visit:<br>
* {@code [0, 1, 2, 3]}<br>
* {@code [0, 1, 2, 4]}<br>
* {@code [0, 1, 4, 2]}<br>
* {@code [3, 2, 1, 0]}<br>
* {@code [4, 2, 1, 0]}<br>
* {@code [2, 4, 1, 0]}
* <p>
* As there are 6 solutions, your code must output 6.
*/
public class BackToSchool {
private static final boolean DEBUG = false;
/**
* @param shops The type of each shop, ordered according to their indexes.
* @param roads The roads between shops. Each element is a couple of two integers.
* @return the number of paths that visits one shop of each type.
*/
public static int countPaths(List<Integer> shops, List<List<Integer>> roads) {
final List<Path[]> paths = new ArrayList<>();
for (int idx = 0; idx < roads.size(); idx++) {
// build a path
traverse(idx, roads, shops, paths);
}
return paths.size() * 2; // two-ways
}
/**
* Traverses the graph of roads and shops, and finds all valid routes that visit all the shops.
*
* @param start The starting index of the roads list.
* @param roads A list of lists, where each inner list represents a road with two vertex indices.
* @param shops A list of shop.
* @param routes A list to store the valid routes as arrays of vertex.
*/
private static void traverse(int start, final List<List<Integer>> roads, final List<Integer> shops, final List<Path[]> routes) {
Deque<Path> stack = new ArrayDeque<>();
stack.push(Path.of(roads.get(start).get(0), roads.get(start).get(1)));
while (!stack.isEmpty()) {
Path current = stack.pop(); // a starting point to create a route that allows you to visit all the shops.
for (int idx = start + 1; idx < roads.size(); idx++) { // try with neighbors
Path fromChain = current;
if (stack.size() == 1) { // peek the path to compare (from, to)
fromChain = stack.peek();
}
Path toChain = Path.of(roads.get(idx).get(0), roads.get(idx).get(1));
if (fromChain.value.equals(toChain.key) || (fromChain.value.equals(toChain.value))) { // can build a chain
stack.addLast(toChain);
}
if (stack.size() == 2 && isValidChain(shops, current, stack.getFirst(), stack.getLast())) { // Can we create a route?
Path[] route = new Path[]{current, stack.getFirst(), stack.getLast()};
if (DEBUG) {
debugRoute(route);
debugRoute(stack.getLast(), stack.getFirst(), current);
}
routes.add(route);
stack.removeLast();
}
}
stack.addFirst(current); // restore starting point
stack.removeLast(); // remove the last point to proceed with the next points.
start++;
}
}
/**
* Checks if a given chain of paths is a valid route that visits all the shops.
*
* @param shops The list of shop indices.
* @param chain The chain of paths to be validated.
* @return True if the chain is a valid route, false otherwise.
*/
private static boolean isValidChain(List<Integer> shops, Path... chain) {
if (chain.length == 0) {
return false;
}
int idx = 1;
Set<Integer> visited = new HashSet<>();
Path lastPath = chain[0];
visited.add(shops.get(lastPath.key));
while (idx < chain.length) {
Path path = chain[idx];
if (!path.key.equals(lastPath.value) && !path.value.equals(lastPath.value)) { // bi-directional
return false;
}
visited.add(shops.get(path.key));
visited.add(shops.get(path.value));
lastPath = path;
idx++;
}
return visited.size() == 4;
}
/**
* Prints a debug message for a given route.
*
* @param paths The array of paths that represent the route.
*/
private static void debugRoute(Path... paths) {
if (paths.length != 3) {
return;
}
System.out.println("--------------Route--------------");
Path lastPath = paths[0];
System.out.printf("(%d, %d)", lastPath.key, lastPath.value);
for (int idx = 1; idx < paths.length; idx++) {
if (lastPath.value.equals(paths[idx].key)) {
System.out.printf(" (%d, %d)", paths[idx].key, paths[idx].value);
} else {
System.out.printf(" (%d, %d)", paths[idx].value, paths[idx].key);
}
lastPath = paths[idx];
}
System.out.printf("%n");
}
public static void main(String[] args) {
System.out.println("Input = shops: [1, 0, 3, 2, 2], roads : [[0, 1], [1, 2], [1, 4], [2, 3], [2, 4]]");
System.out.printf("Output = %d%n", countPaths(asList(1, 0, 3, 2, 2), asList(asList(0, 1), asList(1, 2), asList(1, 4), asList(2, 3), asList(2, 4))));
System.out.println("----------------------");
}
/**
* A generic utility class that represents a key-value pair.
* It provides a way to associate two objects of potentially different types
* and carry them around as a single unit.
*
* @param <T> The type of the key object.
* @param <U> The type of the value object.
*/
static class Pair<T, U> {
public final T key;
public final U value;
private Pair(T key, U value) {
this.value = value;
this.key = key;
}
/**
* Factory method that creates a new {@link Pair} instance with the given key and value.
*
* @param <T> The type of the key object.
* @param <U> The type of the value object.
* @param key The key object to be stored in the instance.
* @param value The value object to be stored in the instance.
* @return A new instance with the specified key and value.
*/
public static <T, U> Pair<T, U> of(T key, U value) {
return new Pair<>(key, value);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Pair)) return false;
Pair<?, ?> pair = (Pair<?, ?>) o;
return Objects.equals(key, pair.key);
}
@Override
public int hashCode() {
return Objects.hashCode(key);
}
}
/**
* Represents a path between two nodes.
* It provides additional methods to access the departure and arrival nodes.
*/
static class Path extends Pair<Integer, Integer> {
private Path(Integer key, Integer value) {
super(key, value);
}
/**
* Returns the departure node of the path.
*
* @return The departure node of the path.
*/
public Integer getDeparture() {
return key;
}
/**
* Returns the arrival node of the path.
*
* @return The arrival node of the path.
*/
public Integer getArrival() {
return value;
}
/**
* Factory method that creates a new {@link Path} instance with the given departure and arrival nodes.
*
* @param from The departure path to be stored in the instance.
* @param to The arrival path to be stored in the instance.
* @return A new instance with the specified departure and arrival.
*/
public static Path of(Integer from, Integer to) {
return new Path(from, to);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment