Context
I'll be passing an interview coding test in java soon. I am experienced with other programming languages but am very unfamiliar with Java. I am looking for critiques of the coding style much more than of the algorithm or the performance, but I am unfamiliar with the java library so any pointers to appropriate library functions or data structures are welcome.
The problem
(Source: https://adventofcode.com/2023/day/5 )
We are given an input text file containing seed ranges and integer range maps. We must map all the seeds by applying all the maps one by one, then return the minimum of all the resulting ranges.
An example input file:
seeds: 79 14 55 13
seed-to-soil map:
50 98 2
52 50 48
soil-to-fertilizer map:
0 15 37
37 52 2
39 0 15
fertilizer-to-water map:
49 53 8
0 11 42
42 0 7
57 7 4
water-to-light map:
88 18 7
18 25 70
light-to-temperature map:
45 77 23
81 45 19
68 64 13
temperature-to-humidity map:
0 69 1
1 0 69
humidity-to-location map:
60 56 37
56 93 4
The seed ranges are given as pairs range_start range_length
, so in this case the four numbers 79 14 55 13
represent two ranges [79, 79+14)
and [55, 55+13)
.
The maps are given as triplets destination_range_start source_range_start range_length
, so for instance the first map:
seed-to-soil map:
50 98 2
52 50 48
maps range [98, 98+2)
to [50, 50+2)
and range [50, 50+48)
to [52, 52+48)
.
After applying the seven maps to the initial list of seed ranges, we get a list of location ranges, and we must output the smallest location number.
The actual input file is much longer than this example and contains numbers that don't fit in int
, so I used long
throughout.
I stored the seed ranges in a Queue<long[]>
as an attribute of class SeedMaps
, and the maps in a List<List<long[]>>
also as an attribute of SeedMaps
. There is only one instance of this class in the program.
Class ReadFile
is used to parse the input file but it only has one static function and is never instanciated.
The main algorithm consists in popping a range from the queue, searching for a range in the map that intersects it, then depending on how they intersect, adding the subrange that was mapped to the list of resulting intervals, and adding the subrange that wasn't mapped, if any, back to the queue.
import java.io.File; // read file
import java.io.FileNotFoundException; // read file
import java.util.Scanner; // read text files
import java.util.Map; // Map.Entry for pairs
import java.util.AbstractMap; // AbstractMap.SimpleImmutableEntry for pairs
import java.util.List; // list of seed-to-soil, soil-to-fertilizer, etc., maps
import java.util.ArrayList; // list of seed-to-soil, etc., maps
import java.util.Arrays; // Arrays.stream(...).mapToInt(Integer::parseInt).toArray();
import java.util.Queue; // queue of seed intervals
import java.util.LinkedList; // queue of seed intervals
class ReadFile {
public static Map.Entry<long[], List<List<long[]>>> readMaps(String filename) {
try {
File myInputFile = new File(filename);
Scanner myReader = new Scanner(myInputFile);
long[] seeds = null;
List<List<long[]>> maps = List.of(new ArrayList<long[]>(), new ArrayList<long[]>(), new ArrayList<long[]>(), new ArrayList<long[]>(), new ArrayList<long[]>(), new ArrayList<long[]>(), new ArrayList<long[]>());
//List<List<long[]>> maps = new ArrayList<List<long[]>>();
//maps.add(new ArrayList<long[]>());
int mapIndex = -1;
while (myReader.hasNextLine()) {
String line = myReader.nextLine();
if (line.startsWith("seeds:")) {
String[] seedsAsStrings = line.split(":", 2)[1].trim().split("\s+");
seeds = Arrays.stream(seedsAsStrings).mapToLong(Long::parseLong).toArray();
} else if (line.indexOf("map:") != -1) {
mapIndex += 1;
} else if (line.length() < 2) {
;
} else {
long[] numbers = Arrays.stream(line.split("\s+")).mapToLong(Long::parseLong).toArray();
long sourceStart = numbers[1];
long sourceEnd = numbers[1] + numbers[2];
long increment = numbers[0] - numbers[1];
long[] startEndIncr = {sourceStart, sourceEnd, increment};
maps.get(mapIndex).add(startEndIncr);
}
}
myReader.close();
return new AbstractMap.SimpleImmutableEntry<>(seeds, maps);
} catch (FileNotFoundException e) {
System.out.println("Error: File not found.");
e.printStackTrace();
}
return null;
}
}
class SeedMaps {
private List<List<long[]>> maps;
private Queue<long[]> seedsQueue;
public SeedMaps(long[] seedsArray, List<List<long[]>> maps) {
this.maps = maps;
this.seedsQueue = new LinkedList<long[]>();
for (int i = 0; i + 1 < seedsArray.length; i += 2) {
long[] seedInterval = {seedsArray[i], seedsArray[i] + seedsArray[i+1]};
this.seedsQueue.add(seedInterval);
}
}
void mapIntervalAndUpdateQueues(long[] range, long[] sourceInterval, Queue<long[]> destQueue) {
if (range[0] <= sourceInterval[0]) {
if (range[1] < sourceInterval[1]) { // r[0] <= src[0] <= r[1] < src[1]
long[] destInterval = {sourceInterval[0] + range[2], range[1] + range[2]};
long[] sourceRightInterval = {range[1], sourceInterval[1]};
seedsQueue.add(sourceRightInterval);
destQueue.add(destInterval);
} else { // source entirely contained in range
long[] destInterval = {sourceInterval[0] + range[2], sourceInterval[1] + range[2]};
destQueue.add(destInterval);
}
} else {// src[0] < r[0] <= src[1]
if (sourceInterval[1] <= range[1]) { // src[0] < r[0] < src[1] <= r[1]
long[] destInterval = {range[0] + range[2], sourceInterval[1] + range[2]};
long[] sourceLeftInterval = {sourceInterval[0], range[0]};
seedsQueue.add(sourceLeftInterval);
destQueue.add(destInterval);
} else { // src[0] < r[0] < r[1] < src[1]
long[] destInterval = {range[0], range[1]};
long[] sourceRightInterval = {range[1], sourceInterval[1]};
long[] sourceLeftInterval = {sourceInterval[0], range[0]};
seedsQueue.add(sourceLeftInterval);
seedsQueue.add(sourceRightInterval);
destQueue.add(destInterval);
}
}
}
void applyMapToSeeds(int mapIndex) {
Queue<long[]> destQueue = new LinkedList<>();
while (!seedsQueue.isEmpty()) {
long[] sourceInterval = seedsQueue.remove();
boolean foundIntersection = false;
for (long[] range : maps.get(mapIndex)) { // range = {start, end, increment}
if (sourceInterval[0] < range[1] && range[0] < sourceInterval[1]) {
foundIntersection = true;
mapIntervalAndUpdateQueues(range, sourceInterval, destQueue);
break;
}
}
if (!foundIntersection) { // interval is not modified by map
destQueue.add(sourceInterval);
}
}
seedsQueue = destQueue;
}
public void applyMapsToSeeds() {
for (int mapIndex = 0; mapIndex < 7; ++mapIndex) {
applyMapToSeeds(mapIndex);
}
}
public void printSeeds(String name) {
System.out.println(name + ": ");
for (long[] interval: seedsQueue) {
System.out.print("[" + interval[0] + "-" + interval[1] + ") ");
}
System.out.println();
}
public void printMaps() {
for (int mapIndex = 0; mapIndex < 7; ++mapIndex) {
System.out.println("\nMap " + mapIndex);
for (long[] line : maps.get(mapIndex)) {
//System.out.print( "[" + line[1] + "-" + (line[1] + line[2]) + ") --> ");
//System.out.println((line[0] - line[1] >= 0 ? "+" : "") + (line[0] - line[1]));
System.out.print( "[" + line[0] + "-" + line[1] + ") --> ");
System.out.println((line[2] >= 0 ? "+" : "") + line[2]);
//System.out.println("[" + line[0] + "-" + (line[0] + line[2]) + "]");
}
}
}
public long getMinSeed() {
List<Long> ll = seedsQueue.stream().map((interval) -> interval[0]).toList();
return ll.stream().mapToLong((x) -> x).min().getAsLong();
}
}
public class Part2 {
public static void main(String[] args) {
String filename = args[0];
System.out.println("Reading from input file: " + filename);
Map.Entry<long[], List<List<long[]>>> seedsAndMaps = ReadFile.readMaps(filename);
SeedMaps maps = new SeedMaps(seedsAndMaps.getKey(), seedsAndMaps.getValue());
maps.printSeeds("Seeds");
maps.applyMapsToSeeds();
maps.printSeeds("Locations");
System.out.println("Minimum location: " + maps.getMinSeed());
}
}
On this example input file, the output is:
Reading from input file: input0.txt
Seeds:
[79-93) [55-68)
Locations:
[60-61) [86-90) [94-97) [82-85) [46-56) [56-60) [97-99)
Minimum location: 46