Skip to content

Commit c2ac0e6

Browse files
authored
Add new practice exercise palindrome-products (exercism#758)
* Add new practice exercise `palindrome-products` * remove unused imported type * add instruction appendix
1 parent acd53d7 commit c2ac0e6

File tree

9 files changed

+472
-0
lines changed

9 files changed

+472
-0
lines changed

config.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,29 @@
16371637
"records"
16381638
],
16391639
"difficulty": 4
1640+
},
1641+
{
1642+
"slug": "palindrome-products",
1643+
"name": "Palindrome Products",
1644+
"uuid": "3ef15a49-8f28-4a27-aa83-695db65177f9",
1645+
"practices": [
1646+
"tail-call-recursion"
1647+
],
1648+
"prerequisites": [
1649+
"recursion",
1650+
"tail-call-recursion",
1651+
"lists",
1652+
"records",
1653+
"result",
1654+
"pattern-matching",
1655+
"custom-types",
1656+
"comparison",
1657+
"maybe",
1658+
"set",
1659+
"array",
1660+
"strings"
1661+
],
1662+
"difficulty": 8
16401663
}
16411664
],
16421665
"foregone": [
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Instructions Append
2+
3+
```exercism/note
4+
Naive solutions may take a long time to pass the tests involving four digit factors, likely longer than the time limit allocated to the online test runner.
5+
You may need to work on the exercise locally to profile your solution.
6+
There are efficient solutions to this exercise, but they are challenging.
7+
```
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Instructions
2+
3+
Detect palindrome products in a given range.
4+
5+
A palindromic number is a number that remains the same when its digits are reversed.
6+
For example, `121` is a palindromic number but `112` is not.
7+
8+
Given a range of numbers, find the largest and smallest palindromes which
9+
are products of two numbers within that range.
10+
11+
Your solution should return the largest and smallest palindromes, along with the factors of each within the range.
12+
If the largest or smallest palindrome has more than one pair of factors within the range, then return all the pairs.
13+
14+
## Example 1
15+
16+
Given the range `[1, 9]` (both inclusive)...
17+
18+
And given the list of all possible products within this range:
19+
`[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 15, 21, 24, 27, 20, 28, 32, 36, 25, 30, 35, 40, 45, 42, 48, 54, 49, 56, 63, 64, 72, 81]`
20+
21+
The palindrome products are all single digit numbers (in this case):
22+
`[1, 2, 3, 4, 5, 6, 7, 8, 9]`
23+
24+
The smallest palindrome product is `1`.
25+
Its factors are `(1, 1)`.
26+
The largest palindrome product is `9`.
27+
Its factors are `(1, 9)` and `(3, 3)`.
28+
29+
## Example 2
30+
31+
Given the range `[10, 99]` (both inclusive)...
32+
33+
The smallest palindrome product is `121`.
34+
Its factors are `(11, 11)`.
35+
The largest palindrome product is `9009`.
36+
Its factors are `(91, 99)`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"jiegillet"
4+
],
5+
"files": {
6+
"solution": [
7+
"src/PalindromeProducts.elm"
8+
],
9+
"test": [
10+
"tests/Tests.elm"
11+
],
12+
"example": [
13+
".meta/src/PalindromeProducts.example.elm"
14+
]
15+
},
16+
"blurb": "Detect palindrome products in a given range.",
17+
"source": "Problem 4 at Project Euler",
18+
"source_url": "https://projecteuler.net/problem=4"
19+
}
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
module PalindromeProducts exposing (largest, smallest)
2+
3+
import Array exposing (Array)
4+
import Set
5+
6+
7+
type alias PalindromeProduct =
8+
{ value : Int
9+
, factors : List ( Int, Int )
10+
}
11+
12+
13+
smallest : Int -> Int -> Result String (Maybe PalindromeProduct)
14+
smallest min max =
15+
if min > max then
16+
Err "min must be <= max"
17+
18+
else
19+
let
20+
initQueue =
21+
new (\( i, j ) -> i * j) (<=)
22+
|> insert ( min, min )
23+
24+
traverse queue visited =
25+
case pop queue of
26+
Nothing ->
27+
Ok Nothing
28+
29+
Just ( ( i, j ), product, poppedQueue ) ->
30+
if isPalindrome product then
31+
popUntilDifferent { value = product, factors = [ ( i, j ) ] } poppedQueue
32+
33+
else if j > max then
34+
traverse poppedQueue visited
35+
36+
else
37+
let
38+
newVisited =
39+
visited |> Set.insert ( i, j + 1 ) |> Set.insert ( i + 1, j + 1 )
40+
41+
newQueue =
42+
[ ( i, j + 1 ), ( i + 1, j + 1 ) ] |> List.filter (\pair -> not (Set.member pair visited)) |> List.foldl insert poppedQueue
43+
in
44+
traverse newQueue newVisited
45+
in
46+
traverse initQueue Set.empty
47+
48+
49+
largest : Int -> Int -> Result String (Maybe PalindromeProduct)
50+
largest min max =
51+
if min > max then
52+
Err "min must be <= max"
53+
54+
else
55+
let
56+
initQueue =
57+
new (\( i, j ) -> i * j) (>)
58+
|> insert ( max, max )
59+
60+
traverse queue visited =
61+
case pop queue of
62+
Nothing ->
63+
Ok Nothing
64+
65+
Just ( ( i, j ), product, poppedQueue ) ->
66+
if isPalindrome product then
67+
popUntilDifferent { value = product, factors = [ ( i, j ) ] } poppedQueue
68+
69+
else if i < min then
70+
traverse poppedQueue visited
71+
72+
else
73+
let
74+
neighbors =
75+
[ ( i - 1, j ), ( i - 1, j - 1 ) ]
76+
77+
newVisited =
78+
List.foldl Set.insert visited neighbors
79+
80+
newQueue =
81+
neighbors
82+
|> List.filter (\pair -> not (Set.member pair visited))
83+
|> List.foldl insert poppedQueue
84+
in
85+
traverse newQueue newVisited
86+
in
87+
traverse initQueue Set.empty
88+
89+
90+
isPalindrome : Int -> Bool
91+
isPalindrome n =
92+
let
93+
string =
94+
String.fromInt n
95+
in
96+
string == String.reverse string
97+
98+
99+
popUntilDifferent : PalindromeProduct -> PriorityQueue ( Int, Int ) -> Result String (Maybe PalindromeProduct)
100+
popUntilDifferent product queue =
101+
case pop queue of
102+
Nothing ->
103+
Ok (Just { product | factors = List.sort product.factors })
104+
105+
Just ( pair, value, newQueue ) ->
106+
if value == product.value then
107+
popUntilDifferent { product | factors = pair :: product.factors } newQueue
108+
109+
else
110+
Ok (Just { product | factors = List.sort product.factors })
111+
112+
113+
114+
-- Priority Queue
115+
116+
117+
type alias PriorityQueue a =
118+
{ leq : Int -> Int -> Bool
119+
, toPriority : a -> Int
120+
, heap : Heap a
121+
}
122+
123+
124+
type alias Heap a =
125+
Array ( a, Int )
126+
127+
128+
new : (a -> Int) -> (Int -> Int -> Bool) -> PriorityQueue a
129+
new toPriority leq =
130+
PriorityQueue leq toPriority Array.empty
131+
132+
133+
insert : a -> PriorityQueue a -> PriorityQueue a
134+
insert a ({ leq, toPriority, heap } as queue) =
135+
{ queue | heap = insertHeap leq a (toPriority a) heap }
136+
137+
138+
insertHeap : (Int -> Int -> Bool) -> a -> Int -> Heap a -> Heap a
139+
insertHeap leq a p heap =
140+
bubbleUp leq (Array.length heap) (Array.push ( a, p ) heap)
141+
142+
143+
bubbleUp : (Int -> Int -> Bool) -> Int -> Heap a -> Heap a
144+
bubbleUp leq index heap =
145+
let
146+
parentIndex =
147+
(index - 1) // 2
148+
in
149+
case ( Array.get index heap, Array.get parentIndex heap ) of
150+
( Just ( newA, newP ), Just ( parentA, parentP ) ) ->
151+
if index > 0 && leq newP parentP then
152+
bubbleUp leq
153+
parentIndex
154+
(heap
155+
|> Array.set index ( parentA, parentP )
156+
|> Array.set parentIndex ( newA, newP )
157+
)
158+
159+
else
160+
heap
161+
162+
_ ->
163+
heap
164+
165+
166+
pop : PriorityQueue a -> Maybe ( a, Int, PriorityQueue a )
167+
pop ({ leq, heap } as queue) =
168+
case Array.get 0 heap of
169+
Nothing ->
170+
Nothing
171+
172+
Just ( aTop, pTop ) ->
173+
let
174+
newHeap =
175+
case Array.get (Array.length heap - 1) heap of
176+
Nothing ->
177+
heap
178+
179+
Just ( a, p ) ->
180+
heap
181+
|> Array.set 0 ( a, p )
182+
|> Array.slice 0 (Array.length heap - 1)
183+
|> bubbleDown leq 0
184+
in
185+
Just ( aTop, pTop, { queue | heap = newHeap } )
186+
187+
188+
bubbleDown : (Int -> Int -> Bool) -> Int -> Heap a -> Heap a
189+
bubbleDown leq index heap =
190+
let
191+
leftIndex =
192+
index * 2 + 1
193+
194+
rightIndex =
195+
index * 2 + 2
196+
in
197+
case ( Array.get index heap, Array.get leftIndex heap, Array.get rightIndex heap ) of
198+
( Just ( a, p ), Just ( leftA, leftP ), Nothing ) ->
199+
if leq p leftP then
200+
heap
201+
202+
else
203+
heap |> Array.set index ( leftA, leftP ) |> Array.set leftIndex ( a, p )
204+
205+
( Just ( a, p ), Just ( leftA, leftP ), Just ( rightA, rightP ) ) ->
206+
if leq p leftP && leq p rightP then
207+
heap
208+
209+
else if leq leftP rightP then
210+
bubbleDown leq leftIndex (heap |> Array.set index ( leftA, leftP ) |> Array.set leftIndex ( a, p ))
211+
212+
else
213+
bubbleDown leq rightIndex (heap |> Array.set index ( rightA, rightP ) |> Array.set rightIndex ( a, p ))
214+
215+
_ ->
216+
heap
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[5cff78fe-cf02-459d-85c2-ce584679f887]
13+
description = "find the smallest palindrome from single digit factors"
14+
15+
[0853f82c-5fc4-44ae-be38-fadb2cced92d]
16+
description = "find the largest palindrome from single digit factors"
17+
18+
[66c3b496-bdec-4103-9129-3fcb5a9063e1]
19+
description = "find the smallest palindrome from double digit factors"
20+
21+
[a10682ae-530a-4e56-b89d-69664feafe53]
22+
description = "find the largest palindrome from double digit factors"
23+
24+
[cecb5a35-46d1-4666-9719-fa2c3af7499d]
25+
description = "find the smallest palindrome from triple digit factors"
26+
27+
[edab43e1-c35f-4ea3-8c55-2f31dddd92e5]
28+
description = "find the largest palindrome from triple digit factors"
29+
30+
[4f802b5a-9d74-4026-a70f-b53ff9234e4e]
31+
description = "find the smallest palindrome from four digit factors"
32+
33+
[787525e0-a5f9-40f3-8cb2-23b52cf5d0be]
34+
description = "find the largest palindrome from four digit factors"
35+
36+
[58fb1d63-fddb-4409-ab84-a7a8e58d9ea0]
37+
description = "empty result for smallest if no palindrome in the range"
38+
39+
[9de9e9da-f1d9-49a5-8bfc-3d322efbdd02]
40+
description = "empty result for largest if no palindrome in the range"
41+
42+
[12e73aac-d7ee-4877-b8aa-2aa3dcdb9f8a]
43+
description = "error result for smallest if min is more than max"
44+
45+
[eeeb5bff-3f47-4b1e-892f-05829277bd74]
46+
description = "error result for largest if min is more than max"
47+
48+
[16481711-26c4-42e0-9180-e2e4e8b29c23]
49+
description = "smallest product does not use the smallest factor"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"type": "application",
3+
"source-directories": [
4+
"src"
5+
],
6+
"elm-version": "0.19.1",
7+
"dependencies": {
8+
"direct": {
9+
"elm/core": "1.0.5",
10+
"elm/json": "1.1.3",
11+
"elm/parser": "1.1.0",
12+
"elm/random": "1.0.0",
13+
"elm/regex": "1.0.0",
14+
"elm/time": "1.0.0",
15+
"elm/html": "1.0.0"
16+
},
17+
"indirect": {}
18+
},
19+
"test-dependencies": {
20+
"direct": {
21+
"elm-explorations/test": "2.1.0",
22+
"rtfeldman/elm-iso8601-date-strings": "1.1.4"
23+
},
24+
"indirect": {
25+
"elm/bytes": "1.0.8",
26+
"elm/virtual-dom": "1.0.3"
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)