<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeSet;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Exercise18031
{
	private class Rectangle
	{
		String rectId = "";
		int posX = 0;
		int posY = 0;
		int lenX = 0;
		int lenY = 0;
		boolean beenChecked = false;
		public String toString()
		{
			return String.format( "id-%s px-%d py-%d lx-%d ly-%d",
					rectId, posX, posY, lenX, lenY );
		}
	}
	private static final String cl = "e18031.";

	public static void main( String args[] )
	{
		final String here = cl +"m ";
		if ( args.length &lt; 1 )
		{
			throw new RuntimeException( here +"add a filename argument" );
		}
		String userSaysFile = args[ 0 ];
		List&lt;String&gt; fileLines = new LinkedList&lt;&gt;();
		try
		{
			Path where = Paths.get( userSaysFile );
			fileLines = Files.readAllLines( where );
		}
		catch ( IOException | InvalidPathException ie )
		{
			System.err.println( here +"couldn't read file "+ userSaysFile +" because "+ ie );
			return;
		}
		Exercise18031 solvesTheProblem = new Exercise18031();
		solvesTheProblem.with( fileLines );
	}


	void with( List&lt;String&gt; fileLines )
	{
		final String here = cl +"w ";
		// 4TESTS
		fileLines.clear();
		fileLines.add( "#11 @ 1,1: 3x3" );
		fileLines.add( "#22 @ 2,1: 3x3" );
		fileLines.add( "#33 @ 1,2: 3x3" );
		// fileLines.add( "#44 @ 5,4: 1x1" );
		// fileLines.add( "#55 @ 1,6: 7x3" );
		// 4TESTS
		List&lt;Rectangle&gt; userRectangles = parse( fileLines );
		Map&lt;String, Rectangle&gt; allRect = new TreeMap&lt;&gt;();
		NavigableMap&lt;Integer, List&lt;String&gt;&gt; xxPositions = new TreeMap&lt;&gt;();
		NavigableMap&lt;Integer, List&lt;String&gt;&gt; yyPositions = new TreeMap&lt;&gt;();
		for ( Rectangle currRect : userRectangles )
		{
			allRect.put( currRect.rectId, currRect );
			List&lt;String&gt; shapesAtXx = xxPositions.get( currRect.posX );
			if ( shapesAtXx == null )
			{
				shapesAtXx = new LinkedList&lt;&gt;();
			}
			shapesAtXx.add( currRect.rectId );
			xxPositions.put( currRect.posX, shapesAtXx );
			List&lt;String&gt; shapesAtYy = yyPositions.get( currRect.posY );
			if ( shapesAtYy == null )
			{
				shapesAtYy = new LinkedList&lt;&gt;();
			}
			shapesAtYy.add( currRect.rectId );
			yyPositions.put( currRect.posY, shapesAtYy );
		}
		String delimiter = "@@", separator = "^^";
		Set&lt;String&gt; bothOcclude = occlusionPairs(
				userRectangles, xxPositions, yyPositions, delimiter );
		// NOTE create new rectangles at the intersection
		Map&lt;String, Rectangle&gt; allJoins = new TreeMap&lt;&gt;();
		xxPositions.clear();
		yyPositions.clear();
		for ( String comboId : bothOcclude )
		{
			String[] pair = comboId.split( delimiter );
			Rectangle first = allRect.get( pair[0 ] );
			Rectangle last = allRect.get( pair[ 1 ] );
			Rectangle intersect = intersectionOf( first, last, comboId );
			System.out.println( here +"occluded at "+ intersect ); // 4TESTS
			allJoins.put( comboId, intersect );
			List&lt;String&gt; shapesAtXx = xxPositions.get( intersect.posX );
			if ( shapesAtXx == null )
			{
				shapesAtXx = new LinkedList&lt;&gt;();
			}
			shapesAtXx.add( intersect.rectId );
			xxPositions.put( intersect.posX, shapesAtXx );
			List&lt;String&gt; shapesAtYy = yyPositions.get( intersect.posY );
			if ( shapesAtYy == null )
			{
				shapesAtYy = new LinkedList&lt;&gt;();
			}
			shapesAtYy.add( intersect.rectId );
			yyPositions.put( intersect.posY, shapesAtYy );
		}
		System.out.println( here +"combined area is "+ overlappingAreaOf( allJoins, xxPositions, yyPositions, separator ) );
	}


	/** format: #[[ number ]] @ [[ x ]],[[ y ]]: [[ width ]]x[[ height ]]
		ex: #121 @ 55,2424: 224x2424 */
	List&lt;Rectangle&gt; parse( List&lt;String&gt; rectangleDesc )
	{
		final String here = cl +"p ";
		List&lt;Rectangle&gt; rectangles = new LinkedList&lt;&gt;();
		int idInd = 1, xposInd = idInd +1, yposInd = xposInd +1,
				xlenInd = yposInd +1, ylenInd = xlenInd +1; // one indexed because 0 is the whole thing
		int currInd = 0;
		String rectangleFormat = "#(\\d+) @ (\\d+),(\\d+): (\\d+)x(\\d+)";
		String expression = "";
		try
		{
			Pattern fsmSpec = Pattern.compile( rectangleFormat );
			Matcher fsmRuntime;
			for ( String currInput : rectangleDesc )
			{
				expression = currInput; // 4TESTS ensuring that I can get the problematic line, otherwise unnecessary
				fsmRuntime = fsmSpec.matcher( currInput );
				Rectangle currRect = new Rectangle();
				if ( fsmRuntime.find( currInd ) )
				{
					currRect.rectId = fsmRuntime.group( idInd );
					currRect.posX = Integer.valueOf( fsmRuntime.group( xposInd ) );
					currRect.posY = Integer.valueOf( fsmRuntime.group( yposInd ) );
					currRect.lenX = Integer.valueOf( fsmRuntime.group( xlenInd ) );
					currRect.lenY = Integer.valueOf( fsmRuntime.group( ylenInd ) );
					rectangles.add( currRect );
				}
			}
		}
		catch ( IllegalArgumentException | IllegalStateException iae )
		{
			System.err.println( here +"couldn't interpret "+ expression
					+" as a rectangle "+ iae );
		}
		return rectangles;
	}


	Set&lt;String&gt; occlusionPairs( List&lt;Rectangle&gt; userRectangles,
			NavigableMap&lt;Integer, List&lt;String&gt;&gt; xxPositions,
			NavigableMap&lt;Integer, List&lt;String&gt;&gt; yyPositions,
			String delimiter )
	{
		Set&lt;String&gt; bothOcclude = new TreeSet&lt;&gt;();
		final boolean inclusive = true;
		for ( Rectangle currRect : userRectangles )
		{
			NavigableMap&lt;Integer, List&lt;String&gt;&gt; xxOcclusions = xxPositions.subMap(
					 currRect.posX, inclusive, currRect.posX + currRect.lenX, ! inclusive );
			NavigableMap&lt;Integer, List&lt;String&gt;&gt; yyOcclusions = yyPositions.subMap(
					currRect.posY, inclusive, currRect.posY + currRect.lenY, ! inclusive );
			Set&lt;String&gt; inXxShadow = new TreeSet&lt;&gt;();
			Set&lt;String&gt; inYyShadow = new TreeSet&lt;&gt;();
			for ( Integer currXx : xxOcclusions.keySet() )
			{
				List&lt;String&gt; idsAtXx = xxOcclusions.get( currXx );
				for ( String idAt : idsAtXx )
				{
					if ( idAt.equals( currRect.rectId ) )
					{
						continue;
					}
					else
					{
						inXxShadow.add( idAt );
					}
				}
			}
			for ( Integer currYy : yyOcclusions.keySet() )
			{
				List&lt;String&gt; idsAtYy = yyOcclusions.get( currYy );
				for ( String idAt : idsAtYy )
				{
					if ( idAt.equals( currRect.rectId ) )
					{
						continue;
					}
					else
					{
						inYyShadow.add( idAt );
					}
				}
			}
			// NOTE find the intersection of the two sets, these actually overlap the current rectangle
			for ( String xxId : inXxShadow )
			{
				for ( String yyId : inYyShadow )
				{
					if ( xxId.equals( yyId )
						&amp;&amp;  ! bothOcclude.contains( xxId + delimiter + currRect.rectId ) ) // avoiding inserting when we find the reverse of an existing pair
					{
						bothOcclude.add( currRect.rectId + delimiter + xxId );
					}
				}
			}
		}
		return bothOcclude;
	}


	int overlappingAreaOf( Map&lt;String, Rectangle&gt; allJoins,
			NavigableMap&lt;Integer, List&lt;String&gt;&gt; xxPositions,
			NavigableMap&lt;Integer, List&lt;String&gt;&gt; yyPositions,
			String delimiter )
	{
		int combinedArea = 0;
		final boolean inclusive = true;
		boolean multiOverlap;
		for ( String pairId : allJoins.keySet() )
		{
			multiOverlap = false;
			Rectangle currRect = allJoins.get( pairId );
			NavigableMap&lt;Integer, List&lt;String&gt;&gt; xxOcclusions = xxPositions.subMap(
					 currRect.posX, inclusive, currRect.posX + currRect.lenX, ! inclusive );
			NavigableMap&lt;Integer, List&lt;String&gt;&gt; yyOcclusions = yyPositions.subMap(
					currRect.posY, inclusive, currRect.posY + currRect.lenY, ! inclusive );
			Set&lt;String&gt; inXxShadow = new TreeSet&lt;&gt;();
			Set&lt;String&gt; inYyShadow = new TreeSet&lt;&gt;();
			for ( Integer currXx : xxOcclusions.keySet() )
			{
				List&lt;String&gt; idsAtXx = xxOcclusions.get( currXx );
				for ( String idAt : idsAtXx )
				{
					if ( idAt.equals( currRect.rectId ) )
					{
						continue;
					}
					else
					{
						inXxShadow.add( idAt );
						multiOverlap |= true;
					}
				}
			}
			for ( Integer currYy : yyOcclusions.keySet() )
			{
				List&lt;String&gt; idsAtYy = yyOcclusions.get( currYy );
				for ( String idAt : idsAtYy )
				{
					if ( idAt.equals( currRect.rectId ) )
					{
						continue;
					}
					else
					{
						inYyShadow.add( idAt );
					}
				}
			}
			// NOTE find the intersection of the two sets, these actually overlap the current rectangle
			for ( String xxId : inXxShadow )
			{
				for ( String yyId : inYyShadow )
				{
					if ( xxId.equals( yyId ) )
					{
						Rectangle first = allJoins.get( xxId );
						Rectangle intersect = intersectionOf( first, currRect, delimiter );
						if ( ! first.beenChecked || ! currRect.beenChecked )
						{
							// if they've both been checked, then this is a really multi overlap (3 pairs) avoiding overdraft
							combinedArea -= intersect.lenX * intersect.lenY;
						}
						if ( ! first.beenChecked )
						{
							combinedArea += first.lenX * first.lenY;
							first.beenChecked = true;
						}
						if ( ! currRect.beenChecked )
						{
							combinedArea += currRect.lenX * currRect.lenY;
							currRect.beenChecked = true;
						}
						multiOverlap |= true;
					}
				}
			}
			if ( ! multiOverlap )
			{
				combinedArea += currRect.lenX * currRect.lenY;
				currRect.beenChecked = true;
			}
			// else handled above
		}
		return combinedArea;
	}


	Rectangle intersectionOf( Rectangle some, Rectangle other, String idToUse )
	{
		Rectangle between = new Rectangle();
		between.rectId = idToUse;
		between.posX = Math.max( some.posX, other.posX );
		between.posY = Math.max( some.posY, other.posY );
		int endSome = some.posX + some.lenX;
		int endOther = other.posX + other.lenX;
		between.lenX = Math.min( endSome, endOther ) - between.posX;
		endSome = some.posY + some.lenY;
		endOther = other.posY + other.lenY;
		between.lenY = Math.min( endSome, endOther ) - between.posY;
		return between;
	}

}



























</pre></body></html>