在 Haskell 中整理列表理解
Tidying up a list comprehension in Haskell
所以我试图在 Haskell 中生成出租车号码列表。出租车数字是可以用两种不同方式写成两个不同立方体之和的数字——最小的是
1729 = 1^3 + 12^3 = 9^3 + 10^3
.
目前,我只是为生成 "make up" 出租车号码的四个数字而烦恼,例如(1,12,9,10),并被告知使用列表理解(我不太熟悉)。此函数将生成最大数最多为 n:
的所有 4 元组
taxi n = [(a,b,c,d) | a <- [1..n], b <- [1..n], c <- [1..n], d <- [1..n], a^3 + b^3 == c^3 + d^3, a < b, a < c, c < d]
但是,有几个原因很麻烦:
- a、b、c、d 的域都是相同的,但我不知道如何简化这段代码,所以
[1..n]
只写了一次。
- 我想要一个没有上限的无限列表,所以我可以尽可能长时间地离开程序 运行 并在我喜欢的时候终止它。很明显,如果我只是设置
a <- [1..]
等,那么程序将永远不会结束任何评估。
- 程序非常慢:
taxi 50
需要 19 秒。
任何速度优化也很好,但如果不是,我使用的简单方法就足够了。
The domain for a,b,c,d are all identical but I can't work out how to simplify this code so [1..n]
is only written once.
使用[a,b,c,d] <- replicateM 4 [1..n]
.
The program is very slow: just taxi 50 takes 19 seconds.
一个廉价的改进是将您的 a<b
、a<c
和 c<d
条件融入理解中。
taxi n = [(a,b,c,d) | a <- [1..n], b <- [a+1..n], c <- [a+1..n], d <- [c+1..n], a^3 + b^3 == c^3 + d^3]
这使我的机器运行速度显着加快。
或者,为了更好地与答案的下一部分(和上一部分)进行组合,将 b
、c
和 d
视为偏移量。
taxi n =
[ (a,b,c,d)
| a <- [1..n]
, b_ <- [1..n], let b = a+b_, b<=n
, c_ <- [1..n], let c = a+c_, c<=n
, d_ <- [1..n], let d = c+d_, d<=n
, a^3 + b^3 == c^3 + d^3
]
I want an infinite list, without an upper limit.
查看我对 Cartesian product of 2 lists in Haskell for a hint. tl;dr use choices 的回答。
您的限制意味着 a < c < d < b
。所以让 b
运行 最外面,让其他 运行 在适当的较低范围内:
taxi n = [ (a,b,c,d) | b <- [1..n],
d <- [1..b-1],
c <- [1..d-1],
a <- [1..c-1],
a^3 + b^3 == c^3 + d^3 ]
要无限,只需使用 b <- [1..]
。
进一步的重大改进是计算来自其他三个变量的四个变量之一:
taxi = [ (a,b,c,d) | b <- [1..],
c <- [1..b-1],
a <- [1..c-1],
let d3 = a^3 + b^3 - c^3,
let d = round(fromIntegral(d3)**(1/3)),
c < d,
d^3 == d3 ]
在 GHCi 中使用 :set +s
进行基准测试 taxi 50
,就像您所做的那样:
Yours: (16.49 secs, 17,672,510,704 bytes)
My first: (0.65 secs, 658,537,184 bytes)
My second: (0.09 secs, 66,229,376 bytes) (modified to use b <- [1..n] again)
Daniel's first: (1.94 secs, 2,016,810,312 bytes)
Daniel's second: (2.87 secs, 2,434,309,440 bytes)
接听 Stefan 的精彩回答。给定 a^3 + b^3 == c^3 + d^3
我们只需要查看满足 0 < a < c < b
的整数。现在引入这个(无限)迭代结构
-- generates all integers x, y and z for which holds 0 < x < y < z
triplets = [(x, y, z) | z <- [3 .. ], y <- [2 .. z - 1], x <- [1 .. y - 1]]
这将使我们从我们稍后将在此处介绍的列表理解中轻松访问三元组。对于有 Python 背景的人来说,这应该等同于 Python 的 yield
。
1 2 3
1 2 4
1 3 4
2 3 4
1 2 5
1 3 5
2 3 5
1 4 5
2 4 5
3 4 5
1 2 6
1 3 6
2 3 6
1 4 6
2 4 6
3 4 6
1 5 6
2 5 6
3 5 6
4 5 6
接下来我们需要一些东西来(快速)找到最大的立方体并测试整数是否为立方体,也称为整数立方体根。这个包 Math.NumberTheory.Powers.Cubes 具有这些任务的功能。或者只使用这些
-- given integer x >= 0 find the largest integer r such that r^3 <= x
largestCube :: Integral a => a -> a
largestCube x =
let powers_of_two = iterate ((*) 2) 1
upper = head [j | j <- powers_of_two, x < j ^ 3]
in largestCubeSub 0 upper x
largestCubeSub :: Integral a => a -> a -> a -> a
largestCubeSub lower upper x
| lower + 1 == upper = lower
| b ^ 3 <= x = largestCubeSub b upper x
| otherwise = largestCubeSub lower b x
where
b = div (lower + upper) 2
-- test if an integer x >= 0 is a cube
isCube :: Integral a => a -> Bool
isCube x = (largestCube x) ^ 3 == x
现在您对前 50 个出租车号码的紧凑列表理解如下所示
*Main> condition = \a b c -> and [isCube (a^3 + b^3 - c^3), a^3 + b^3 - c^3 > c^3]
*Main> taxi = [(a, b, c, largestCube (a^3 + b^3 - c^3)) | (a, c, b) <- triplets, condition a b c]
*Main> first50 = take 50 taxi
使用
打印它们
*Main> single_line = \(x, y, z, u) -> unwords [show i | i <- [x, y, z, u]]
*Main> putStrLn $ unlines $ map single_line first50
会给
1 12 9 10
2 16 9 15
2 24 18 20
10 27 19 24
4 32 18 30
2 34 15 33
9 34 16 33
3 36 27 30
17 39 26 36
12 40 31 33
6 48 27 45
4 48 36 40
12 51 38 43
8 53 29 50
20 54 38 48
17 55 24 54
9 58 22 57
3 60 22 59
5 60 45 50
8 64 36 60
30 67 51 58
4 68 30 66
18 68 32 66
42 69 56 61
6 72 54 60
17 76 38 73
5 76 48 69
34 78 52 72
10 80 45 75
15 80 54 71
24 80 62 66
30 81 57 72
51 82 64 75
7 84 63 70
2 89 41 86
11 93 30 92
23 94 63 84
12 96 54 90
50 96 59 93
8 96 72 80
20 97 33 96
47 97 66 90
35 98 59 92
24 98 63 89
29 99 60 92
6 102 45 99
27 102 48 99
23 102 60 95
24 102 76 86
1 103 64 94
在几秒钟内它会 returns 前 50 个出租车号码。
所以我试图在 Haskell 中生成出租车号码列表。出租车数字是可以用两种不同方式写成两个不同立方体之和的数字——最小的是
1729 = 1^3 + 12^3 = 9^3 + 10^3
.
目前,我只是为生成 "make up" 出租车号码的四个数字而烦恼,例如(1,12,9,10),并被告知使用列表理解(我不太熟悉)。此函数将生成最大数最多为 n:
的所有 4 元组
taxi n = [(a,b,c,d) | a <- [1..n], b <- [1..n], c <- [1..n], d <- [1..n], a^3 + b^3 == c^3 + d^3, a < b, a < c, c < d]
但是,有几个原因很麻烦:
- a、b、c、d 的域都是相同的,但我不知道如何简化这段代码,所以
[1..n]
只写了一次。 - 我想要一个没有上限的无限列表,所以我可以尽可能长时间地离开程序 运行 并在我喜欢的时候终止它。很明显,如果我只是设置
a <- [1..]
等,那么程序将永远不会结束任何评估。 - 程序非常慢:
taxi 50
需要 19 秒。
任何速度优化也很好,但如果不是,我使用的简单方法就足够了。
The domain for a,b,c,d are all identical but I can't work out how to simplify this code so
[1..n]
is only written once.
使用[a,b,c,d] <- replicateM 4 [1..n]
.
The program is very slow: just taxi 50 takes 19 seconds.
一个廉价的改进是将您的 a<b
、a<c
和 c<d
条件融入理解中。
taxi n = [(a,b,c,d) | a <- [1..n], b <- [a+1..n], c <- [a+1..n], d <- [c+1..n], a^3 + b^3 == c^3 + d^3]
这使我的机器运行速度显着加快。
或者,为了更好地与答案的下一部分(和上一部分)进行组合,将 b
、c
和 d
视为偏移量。
taxi n =
[ (a,b,c,d)
| a <- [1..n]
, b_ <- [1..n], let b = a+b_, b<=n
, c_ <- [1..n], let c = a+c_, c<=n
, d_ <- [1..n], let d = c+d_, d<=n
, a^3 + b^3 == c^3 + d^3
]
I want an infinite list, without an upper limit.
查看我对 Cartesian product of 2 lists in Haskell for a hint. tl;dr use choices 的回答。
您的限制意味着 a < c < d < b
。所以让 b
运行 最外面,让其他 运行 在适当的较低范围内:
taxi n = [ (a,b,c,d) | b <- [1..n],
d <- [1..b-1],
c <- [1..d-1],
a <- [1..c-1],
a^3 + b^3 == c^3 + d^3 ]
要无限,只需使用 b <- [1..]
。
进一步的重大改进是计算来自其他三个变量的四个变量之一:
taxi = [ (a,b,c,d) | b <- [1..],
c <- [1..b-1],
a <- [1..c-1],
let d3 = a^3 + b^3 - c^3,
let d = round(fromIntegral(d3)**(1/3)),
c < d,
d^3 == d3 ]
在 GHCi 中使用 :set +s
进行基准测试 taxi 50
,就像您所做的那样:
Yours: (16.49 secs, 17,672,510,704 bytes)
My first: (0.65 secs, 658,537,184 bytes)
My second: (0.09 secs, 66,229,376 bytes) (modified to use b <- [1..n] again)
Daniel's first: (1.94 secs, 2,016,810,312 bytes)
Daniel's second: (2.87 secs, 2,434,309,440 bytes)
接听 Stefan 的精彩回答。给定 a^3 + b^3 == c^3 + d^3
我们只需要查看满足 0 < a < c < b
的整数。现在引入这个(无限)迭代结构
-- generates all integers x, y and z for which holds 0 < x < y < z
triplets = [(x, y, z) | z <- [3 .. ], y <- [2 .. z - 1], x <- [1 .. y - 1]]
这将使我们从我们稍后将在此处介绍的列表理解中轻松访问三元组。对于有 Python 背景的人来说,这应该等同于 Python 的 yield
。
1 2 3
1 2 4
1 3 4
2 3 4
1 2 5
1 3 5
2 3 5
1 4 5
2 4 5
3 4 5
1 2 6
1 3 6
2 3 6
1 4 6
2 4 6
3 4 6
1 5 6
2 5 6
3 5 6
4 5 6
接下来我们需要一些东西来(快速)找到最大的立方体并测试整数是否为立方体,也称为整数立方体根。这个包 Math.NumberTheory.Powers.Cubes 具有这些任务的功能。或者只使用这些
-- given integer x >= 0 find the largest integer r such that r^3 <= x
largestCube :: Integral a => a -> a
largestCube x =
let powers_of_two = iterate ((*) 2) 1
upper = head [j | j <- powers_of_two, x < j ^ 3]
in largestCubeSub 0 upper x
largestCubeSub :: Integral a => a -> a -> a -> a
largestCubeSub lower upper x
| lower + 1 == upper = lower
| b ^ 3 <= x = largestCubeSub b upper x
| otherwise = largestCubeSub lower b x
where
b = div (lower + upper) 2
-- test if an integer x >= 0 is a cube
isCube :: Integral a => a -> Bool
isCube x = (largestCube x) ^ 3 == x
现在您对前 50 个出租车号码的紧凑列表理解如下所示
*Main> condition = \a b c -> and [isCube (a^3 + b^3 - c^3), a^3 + b^3 - c^3 > c^3]
*Main> taxi = [(a, b, c, largestCube (a^3 + b^3 - c^3)) | (a, c, b) <- triplets, condition a b c]
*Main> first50 = take 50 taxi
使用
打印它们*Main> single_line = \(x, y, z, u) -> unwords [show i | i <- [x, y, z, u]]
*Main> putStrLn $ unlines $ map single_line first50
会给
1 12 9 10
2 16 9 15
2 24 18 20
10 27 19 24
4 32 18 30
2 34 15 33
9 34 16 33
3 36 27 30
17 39 26 36
12 40 31 33
6 48 27 45
4 48 36 40
12 51 38 43
8 53 29 50
20 54 38 48
17 55 24 54
9 58 22 57
3 60 22 59
5 60 45 50
8 64 36 60
30 67 51 58
4 68 30 66
18 68 32 66
42 69 56 61
6 72 54 60
17 76 38 73
5 76 48 69
34 78 52 72
10 80 45 75
15 80 54 71
24 80 62 66
30 81 57 72
51 82 64 75
7 84 63 70
2 89 41 86
11 93 30 92
23 94 63 84
12 96 54 90
50 96 59 93
8 96 72 80
20 97 33 96
47 97 66 90
35 98 59 92
24 98 63 89
29 99 60 92
6 102 45 99
27 102 48 99
23 102 60 95
24 102 76 86
1 103 64 94
在几秒钟内它会 returns 前 50 个出租车号码。