diff --git a/config.json b/config.json index cf155c6..6c84dc0 100644 --- a/config.json +++ b/config.json @@ -1111,6 +1111,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "piecing-it-together", + "name": "Piecing It Together", + "uuid": "23b80d70-283c-44be-a360-8a23a42773f4", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "rectangles", "name": "Rectangles", diff --git a/exercises/practice/piecing-it-together/.docs/instructions.md b/exercises/practice/piecing-it-together/.docs/instructions.md new file mode 100644 index 0000000..c0c9665 --- /dev/null +++ b/exercises/practice/piecing-it-together/.docs/instructions.md @@ -0,0 +1,41 @@ +# Instructions + +Given partial information about a jigsaw puzzle, add the missing pieces. + +If the information is insufficient to complete the details, or if given parts are in contradiction, the user should be notified. + +The full information about the jigsaw puzzle contains the following parts: + +- `pieces`: Total number of pieces +- `border`: Number of border pieces +- `inside`: Number of inside (non-border) pieces +- `rows`: Number of rows +- `columns`: Number of columns +- `aspectRatio`: Aspect ratio of columns to rows +- `format`: Puzzle format, which can be `portrait`, `square`, or `landscape` + +For this exercise, you may assume square pieces, so that the format can be derived from the aspect ratio: + +- If the aspect ratio is less than 1, it's `portrait` +- If it is equal to 1, it's `square` +- If it is greater than 1, it's `landscape` + +## Three examples + +### Portrait + +A portrait jigsaw puzzle with 6 pieces, all of which are border pieces and none are inside pieces. It has 3 rows and 2 columns. The aspect ratio is 1.5 (3/2). + +![A 2 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-2x3.svg) + +### Square + +A square jigsaw puzzle with 9 pieces, all of which are border pieces except for the one in the center, which is an inside piece. It has 3 rows and 3 columns. The aspect ratio is 1 (3/3). + +![A 3 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-3x3.svg) + +### Landscape + +A landscape jigsaw puzzle with 12 pieces, 10 of which are border pieces and 2 are inside pieces. It has 3 rows and 4 columns. The aspect ratio is 1.333333... (4/3). + +![A 4 by 3 jigsaw puzzle](https://assets.exercism.org/images/exercises/piecing-it-together/jigsaw-puzzle-4x3.svg) diff --git a/exercises/practice/piecing-it-together/.docs/introduction.md b/exercises/practice/piecing-it-together/.docs/introduction.md new file mode 100644 index 0000000..2fa20f6 --- /dev/null +++ b/exercises/practice/piecing-it-together/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +Your best friend has started collecting jigsaw puzzles and wants to build a detailed catalog of their collection — recording information such as the total number of pieces, the number of rows and columns, the number of border and inside pieces, aspect ratio, and puzzle format. +Even with your powers combined, it takes multiple hours to solve a single jigsaw puzzle with one thousand pieces — and then you still need to count the rows and columns and calculate the remaining numbers manually. +The even larger puzzles with thousands of pieces look quite daunting now. +"There has to be a better way!" you exclaim and sit down in front of your computer to solve the problem. diff --git a/exercises/practice/piecing-it-together/.meta/PiecingItTogether.example.ps1 b/exercises/practice/piecing-it-together/.meta/PiecingItTogether.example.ps1 new file mode 100644 index 0000000..4f354af --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/PiecingItTogether.example.ps1 @@ -0,0 +1,172 @@ +<# +.SYNOPSIS +Given partial information about a jigsaw puzzle, add the missing pieces. + +.DESCRIPTION +Calculate properties of a jigsaw puzzle with given information if possible. +If not possible due to insufficient or incorrect information, the user should be notified. +Read instructions for more information and example. +#> + +enum Format { + Unknown + Portrait + Square + Landscape +} + +class JigsawPuzzle { + [int]$Pieces + [int]$Border + [int]$Inside + [int]$Rows + [int]$Columns + [double]$AspectRatio + [Format]$Format + + GetData([PSCustomObject]$partialData) + { + if ($partialData.PSObject.Properties.Name -contains "AspectRatio") { + $this.ProcessDataFromRatio($partialData) + } elseif ($partialData.PSObject.Properties.Name -contains "Format") { + $this.ProcessDataFromFormat($partialData) + } else { + throw "insufficient data" + } + } + + FillFromRowsAndColumns() + { + $this.Pieces = $this.Rows * $this.Columns + $this.Border = 2*$this.Rows + 2*$this.Columns - 4 + $this.Inside = $this.Pieces - $this.Border + $this.AspectRatio = $this.Columns / $this.Rows + } + + [int[]]FindRowsAndColumn() { + $a, $b = 0, 0 + for ($i = 1; $i -le [Math]::Sqrt($this.Pieces); $i++) { + if (($i * $i * $this.AspectRatio) -eq $this.Pieces) { + $a, $b = $i, ($i * $this.AspectRatio) + break + } + } + return $this.Format -eq [Format]::Landscape ? [Math]::Min($a,$b), [Math]::Max($a,$b) + : [Math]::Max($a,$b), [Math]::Min($a,$b) + } + + ProcessDataFromRatio([PSCustomObject]$partialData) + { + $properties = $partialData.PSObject.Properties.Name + $this.AspectRatio = $partialData.AspectRatio + $this.Format = switch ($true) { + ($partialData.AspectRatio -gt 1) { [Format]::Landscape } + ($partialData.AspectRatio -lt 1) { [Format]::Portrait } + Default {[Format]::Square} + } + + if ($properties -contains "Pieces") { + $this.Pieces = $partialData.Pieces + $this.Rows, $this.Columns = $this.FindRowsAndColumn() + } elseif ($properties -contains "Border") { + $this.Border = $partialData.Border + $this.Rows = ($this.Border + 4) / (2 * (1 + $this.AspectRatio)) + $this.Columns = $this.Rows * $this.AspectRatio + } elseif ($properties -contains "Inside") { + $this.Inside = $partialData.Inside + $r = $this.AspectRatio + $i = $this.Inside + + $discriminant = [Math]::Pow((2 * $r + 2), 2) - (16 * $r) + (4 * $r * $i) + $rows1 = ((2 * $r + 2) + [Math]::Sqrt($discriminant)) / (2 * $r) + $rows2 = ((2 * $r + 2) - [Math]::Sqrt($discriminant)) / (2 * $r) + + $this.Rows = @($rows1, $rows2) | Where-Object { $_ -gt 0 -and [Math]::Round($_) -eq $_ } | Select-Object -First 1 + $this.Columns = $r * $this.Rows + } elseif ($properties -contains "Rows") { + $this.Rows = $partialData.Rows + $this.Columns = $this.AspectRatio * $this.Rows + } elseif ($properties -contains "Columns") { + $this.Columns = $partialData.Columns + $this.Rows = $this.Columns / $this.AspectRatio + } + $this.FillFromRowsAndColumns() + } + + ProcessDataFromFormat([PSCustomObject]$partialData) + { + $this.Format = $partialData.Format + $properties = $partialData.PSObject.Properties.Name + if ($this.Format -eq [Format]::Square) { + $this.AspectRatio = 1.0 + if ($properties -contains "Pieces") { + $this.Rows = $this.Columns = [Math]::Sqrt($partialData.Pieces) + } elseif ($properties -contains "Rows") { + $this.Rows = $partialData.Rows + $this.Columns = $this.Rows + } elseif ($properties -contains "Columns") { + $this.Columns = $partialData.Columns + $this.Rows = $this.Columns + } elseif ($properties -contains "Border") { + $this.Rows = $this.Columns = ($partialData.Border + 4) / 4 + } elseif ($properties -contains "Inside") { + $this.Rows = $this.Columns = [Math]::Sqrt($partialData.Inside) + 2 + } else { + throw "bad data" + } + } else { + $otherProperties = ($properties | Where-Object { $_ -ne "Format"}).Count + if ($otherProperties -lt 2) { + throw "insufficient data" + } + $pairs = $this.GenerateRowsAndColumnsFromFormat([PSCustomObject]$partialData) + foreach ($pair in $pairs) { + $tempPieces = $pair[0] * $pair[1] + $tempBorder = 2*$pair[0] + 2*$pair[1] - 4 + if ($tempPieces -eq $partialData.Pieces -and $tempBorder -eq $partialData.Border) { + if ($partialData.Format -eq [Format]::Landscape) { + $this.Rows = [Math]::Min($pair[0], $pair[1]) + $this.Columns = [Math]::Max($pair[0], $pair[1]) + } else { + $this.Rows = [Math]::Max($pair[0], $pair[1]) + $this.Columns = [Math]::Min($pair[0], $pair[1]) + } + } + } + + } + $this.FillFromRowsAndColumns() + $this.ValidateGivenData($partialData) + } + + [object[]]GenerateRowsAndColumnsFromFormat([PSCustomObject]$partialData) { + $properties = $partialData.PSObject.Properties.Name + $pairs = @() + if ($properties -contains "Pieces") { + $this.Pieces = $partialData.Pieces + for ($r = 1; $r -le [Math]::Sqrt($this.Pieces); $r++) { + for ($c = $r; $c -le $this.Pieces; $c++) { + if ($r * $c -eq $this.Pieces) { + $pairs += ,@($c, $r) + } + } + } + }elseif ($properties -contains "Border") { + $this.Border = $partialData.Border + $sum = ($this.Border + 4) / 2 + for ($c = 1; $c -lt ($sum - 1); $c++) { + $pairs += ,@($c, ($sum - $c)) + } + } + return $pairs + } + + ValidateGivenData([PSCustomObject]$partialData) + { + foreach ($property in $partialData.psobject.Properties) { + if ($property.Value -ne $this.PSObject.Properties[$property.Name].Value) { + throw "contradictory data" + } + } + } +} diff --git a/exercises/practice/piecing-it-together/.meta/config.json b/exercises/practice/piecing-it-together/.meta/config.json new file mode 100644 index 0000000..e6b9479 --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glaxxie" + ], + "files": { + "solution": [ + "PiecingItTogether.ps1" + ], + "test": [ + "PiecingItTogether.tests.ps1" + ], + "example": [ + ".meta/PiecingItTogether.example.ps1" + ] + }, + "blurb": "Fill in missing jigsaw puzzle details from partial data", + "source": "atk just started another 1000-pieces jigsaw puzzle when this idea hit him", + "source_url": "https://github.com/exercism/problem-specifications/pull/2554" +} diff --git a/exercises/practice/piecing-it-together/.meta/tests.toml b/exercises/practice/piecing-it-together/.meta/tests.toml new file mode 100644 index 0000000..f462c15 --- /dev/null +++ b/exercises/practice/piecing-it-together/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[ad626f23-09a2-4f5f-ba22-eec0671fa2a9] +description = "1000 pieces puzzle with 1.6 aspect ratio" + +[3e0c5919-3561-42f5-b9ed-26d70c20214e] +description = "square puzzle with 32 rows" + +[1126f160-b094-4dc2-bf37-13e36e394867] +description = "400 pieces square puzzle with only inside pieces and aspect ratio" + +[a9743178-5642-4cc0-8fdb-00d6b031c3a0] +description = "1500 pieces landscape puzzle with 30 rows and 1.6 aspect ratio" + +[f6378369-989c-497f-a6e2-f30b1fa76cba] +description = "300 pieces portrait puzzle with 70 border pieces" + +[f53f82ba-5663-4c7e-9e86-57fdbb3e53d2] +description = "puzzle with insufficient data" + +[a3d5c31a-cc74-44bf-b4fc-9e4d65f1ac1a] +description = "puzzle with contradictory data" diff --git a/exercises/practice/piecing-it-together/PiecingItTogether.ps1 b/exercises/practice/piecing-it-together/PiecingItTogether.ps1 new file mode 100644 index 0000000..a5b7d8c --- /dev/null +++ b/exercises/practice/piecing-it-together/PiecingItTogether.ps1 @@ -0,0 +1,30 @@ +<# +.SYNOPSIS +Given partial information about a jigsaw puzzle, add the missing pieces. + +.DESCRIPTION +Calculate properties of a jigsaw puzzle with given information if possible. +If not possible due to insufficient or incorrect information, the user should be notified. +Read instructions for more information and example. +#> + +enum Format { + Portrait + Square + Landscape +} + +class JigsawPuzzle { + [int]$Pieces + [int]$Border + [int]$Inside + [int]$Rows + [int]$Columns + [double]$AspectRatio + [Format]$Format + + GetData([PSCustomObject]$partialData) + { + Throw "Please implement this function" + } +} diff --git a/exercises/practice/piecing-it-together/PiecingItTogether.tests.ps1 b/exercises/practice/piecing-it-together/PiecingItTogether.tests.ps1 new file mode 100644 index 0000000..7483a85 --- /dev/null +++ b/exercises/practice/piecing-it-together/PiecingItTogether.tests.ps1 @@ -0,0 +1,107 @@ +BeforeAll { + . "./PiecingItTogether.ps1" +} + +Describe "PiecingItTogether test cases" { + BeforeEach { + $puzzle = [JigsawPuzzle]::new() + } + It "1000 pieces puzzle with 1.6 aspect ratio" { + $partialData = [PSCustomObject]@{ + Pieces = 1000 + AspectRatio = 1.6 + } + $puzzle.GetData($partialData) + + $puzzle.Pieces | Should -Be 1000 + $puzzle.Border | Should -Be 126 + $puzzle.Inside | Should -Be 874 + $puzzle.Rows | Should -Be 25 + $puzzle.Columns | Should -Be 40 + $puzzle.AspectRatio | Should -Be 1.6 + $puzzle.Format | Should -Be ([Format]::Landscape) + } + + It "square puzzle with 32 rows" { + $partialData = [PSCustomObject]@{ + Rows = 32 + Format = [Format]::Square + } + $puzzle.GetData($partialData) + + $puzzle.Pieces | Should -Be 1024 + $puzzle.Border | Should -Be 124 + $puzzle.Inside | Should -Be 900 + $puzzle.Rows | Should -Be 32 + $puzzle.Columns | Should -Be 32 + $puzzle.AspectRatio | Should -Be 1.0 + $puzzle.Format | Should -Be ([Format]::Square) + } + + It "400 pieces square puzzle with only inside pieces and aspect ratio" { + $partialData = [PSCustomObject]@{ + Inside = 324 + AspectRatio = 1.0 + } + $puzzle.GetData($partialData) + + $puzzle.Pieces | Should -Be 400 + $puzzle.Border | Should -Be 76 + $puzzle.Inside | Should -Be 324 + $puzzle.Rows | Should -Be 20 + $puzzle.Columns | Should -Be 20 + $puzzle.AspectRatio | Should -Be 1.0 + $puzzle.Format | Should -Be ([Format]::Square) + } + + It "1500 pieces landscape puzzle with 30 rows and 1.6 aspect ratio" { + $partialData = [PSCustomObject]@{ + Rows = 30 + AspectRatio = 1.6666666666666667 + } + $puzzle.GetData($partialData) + + $puzzle.Pieces | Should -Be 1500 + $puzzle.Border | Should -Be 156 + $puzzle.Inside | Should -Be 1344 + $puzzle.Rows | Should -Be 30 + $puzzle.Columns | Should -Be 50 + $puzzle.AspectRatio | Should -Be 1.6666666666666667 + $puzzle.Format | Should -Be ([Format]::Landscape) + } + + It "300 pieces portrait puzzle with 70 border pieces" { + $partialData = [PSCustomObject]@{ + Pieces = 300 + Border = 70 + Format = [Format]::Portrait + } + $puzzle.GetData($partialData) + + $puzzle.Pieces | Should -Be 300 + $puzzle.Border | Should -Be 70 + $puzzle.Inside | Should -Be 230 + $puzzle.Rows | Should -Be 25 + $puzzle.Columns | Should -Be 12 + $puzzle.AspectRatio | Should -Be 0.48 + $puzzle.Format | Should -Be ([Format]::Portrait) + } + + It "puzzle with insufficient data" { + $partialData = [PSCustomObject]@{ + Pieces = 1500 + Format = [Format]::Landscape + } + { $puzzle.GetData($partialData) } | Should -Throw "*insufficient data*" + } + + It "puzzle with contradictory data" { + $partialData = [PSCustomObject]@{ + Rows = 100 + Columns = 1000 + Format = [Format]::Square + } + { $puzzle.GetData($partialData) } | Should -Throw "*contradictory data*" + } + +}