Reputation: 16435
I have the following code, which should™ convert an excel column type its corresponding number. For example AA into 27 and AB into 28:
import Data.Char (ord)
import Data.List (foldl1')
columnToNumber :: String -> Int
columnToNumber s = foldl1' (\acc (i, v) -> acc + 26 ^ i * v) (values s)
where values s = zip (reverse [0..(length s)]) ((\c -> ord c - 64) <$> s)
The idea is to take the string "AA" convert it to the corresponding numbers
["A", "A"] -> [1, 1]
and zip it with the base so from right to left 26^0, 26^1, 26^2, and so on.
zip [1, 0] [1, 1] -> [(1, 1), (0, 1)]
That way the result of the fold would be
26^1 * 1 + 26^0 * 1 = 27
Unfortunately, I'm getting the following errors and I'm not sure why:
ExcelSheetColumn.hs:7:34:
Couldn't match expected type ‘Int’
with actual type ‘(Integer, Int)’
In the pattern: (i, v)
In the first argument of ‘foldl1'’, namely
‘(\ acc (i, v) -> acc + 26 ^ i * v)’
In the expression:
foldl1' (\ acc (i, v) -> acc + 26 ^ i * v) (values s)
ExcelSheetColumn.hs:7:63:
Couldn't match type ‘(Int, Int)’ with ‘Int’
Expected type: [Int]
Actual type: [(Int, Int)]
In the second argument of ‘foldl1'’, namely ‘(values s)’
In the expression:
foldl1' (\ acc (i, v) -> acc + 26 ^ i * v) (values s)
Could someone help me out?
Upvotes: 1
Views: 195
Reputation: 52280
to get it compiling you actually just have to switch foldl1'
to foldl'
and add the starting accumulator:
import Data.Char (ord)
import Data.List (foldl')
columnToNumber :: String -> Int
columnToNumber s = foldl' (\acc (i, v) -> acc + 26 ^ i * v) 0 (values s)
where values s = zip (reverse [0..(length s)]) ((\c -> ord c - 64) <$> s)
if you add the suggestion Free_D
made (start at length s - 1
):
columnToNumber :: String -> Int
columnToNumber s = foldl' (\acc (i, v) -> acc + 26 ^ i * v) 0 (values s)
where values s = zip (reverse [0..(length s -1)]) ((\c -> ord c - 64) <$> s)
you get the desired results:
λ> columnToNumber "AA"
27
λ> columnToNumber "AB"
28
I don't know if you actually gonna need this but hey why not:
what you probably don't like is that columnToNumber "A1"
is 11
- to fix this you need to tread digits differently from letters:
columnToNumber :: String -> Int
columnToNumber s = foldl' (\acc (i, v) -> acc + 26 ^ i * v) 0 (values s)
where values s = zip (reverse [0..(length s -1)]) (parse <$> s)
parse c
| c >= '0' && c <= '9' = ord c - ord '0'
| otherwise = ord c - 64
Upvotes: 6
Reputation: 43128
Looking at the definition of foldl1'
, it has to take two things that are of the same type and produce something similar
*Main Data.List> :t foldl1'
foldl1' :: (a -> a -> a) -> [a] -> a
But foldl
is what you want:
*Main Data.List> :t foldl
foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
So essentially this:
import Data.Char (ord, toUpper)
columnToNumber :: String -> Int
columnToNumber s = foldl (\acc (i, v) -> acc + 26 ^ i * v) 0 $ values s where
values s = zip [l - 1, l - 2 ..0] ((\c -> (ord.toUpper) c - 64) <$> s) where
l = length s
Upvotes: 2