3

The next lines should show how its has to work..

[14,2,344,41,5,666] after [(14,2),(2,1),(344,3),(5,1),(666,3)]

["Zoo","School","Net"] after [("Zoo",3),("School",6),("Net",3)]

Thats my code up to now

zipWithLength :: [a] -> [(a, Int)]
zipWithLength (x:xs) = zipWith (\acc x -> (x, length x):acc) [] xs

I want to figure out what the problem in the second line is.

sshine
  • 15,635
  • 1
  • 41
  • 66
Janik Ti
  • 75
  • 4
  • 1
    You need `map`, not `zipWith`. – chi Mar 23 '18 at 13:06
  • 1
    `map (\x -> (x, length x)) ["Zoo", "School"]` works but not with a list of numbers. The `length` function does not return the "length" of a number (written in decimal notation). – Stéphane Laurent Mar 23 '18 at 13:07
  • You can use [this package](https://hackage.haskell.org/package/number-length) for the length of a number. – Stéphane Laurent Mar 23 '18 at 13:10
  • 1
    Your function isn't well typed. How do you know a value of any arbitrary type `a` *has* any notion of length? `Int`, for instance, doesn't have a length. A string representation of an `Int` does, but there are many such representations: base 2, base 10, base 16? Written as an English word? A Russian word? – chepner Mar 23 '18 at 14:42
  • 1
    This is really a case where you want your own type class that behaves like `Foldable`. `class HasLength a where mylength :: a -> Int`. Anything with a `Foldable` instance has a trivial `HasLength` instance (`mylength = length`), but I don't recall if there is an easy way to express that. Other types like `Int` that don't have a `Foldable` instance can still have a `HasLength` instance. (For example, `instance HasLength Int where mylength = length . show`.) – chepner Mar 23 '18 at 14:57

4 Answers4

2

If you transform the numbers into strings (using show), you can apply length on them:

Prelude> let zipWithLength = map (\x -> (x, length (show x)))
Prelude> zipWithLength [14,2,344,41,5,666]
[(14,2),(2,1),(344,3),(41,2),(5,1),(666,3)]

However, you cannot use the same function on a list of strings:

Prelude> zipWithLength ["Zoo","School","Net"]
[("Zoo",5),("School",8),("Net",5)]

The numbers are not the lengths of the strings, but of their representations:

Prelude> show "Zoo"
"\"Zoo\""
Prelude> length (show "Zoo")
5

As noted in the comments, similar problems may happen with other types of elements:

Prelude> zipWithLength [(1.0,3),(2.5,3)]
[((1.0,3),7),((2.5,3),7)]
Prelude> show (1.0,3)
"(1.0,3)"
Prelude> length (show (1.0,3))
7
bli
  • 7,549
  • 7
  • 48
  • 94
  • Thank you for now! – Janik Ti Mar 23 '18 at 13:17
  • Note that it will give you a wrong result when applied to a list of strings. – bli Mar 23 '18 at 13:18
  • Not that for floating numbers this will yield a maybe unexpected result: `[(1.0,3),(2.5,3)]` for the input `[1,2.5]`. This includes the dot and also the `0` decimal. – Stéphane Laurent Mar 23 '18 at 13:20
  • I would do two functions. I would use `length` for types like `[a]` and the package I linked above for numbers. – Stéphane Laurent Mar 23 '18 at 13:21
  • But this doesnt work : `zipWithLength :: [a] -> [(a, Int)]`
    `zipWithLength [] = []`
    `zipWithLength (x:xs) = map (\x -> (x, length (show x)))`
    – Janik Ti Mar 23 '18 at 13:22
  • @JanikTi You don't need to decompose the list into `head:tail` to use `map`. When you pattern-match on `(x:xs)`, `x` will be an element of a list, and you will not be able to map something on it (unless the elements are in turn of type `[a]`). – bli Mar 23 '18 at 13:23
  • I forgot Thx, so i tried this but no positiv result:
    `zipWithLength :: [a] -> [(a, Int)]`
    `zipWithLength [] = []`
    `zipWithLength = map (\x -> (x, length (show x)))`
    – Janik Ti Mar 23 '18 at 13:27
  • @JanikTi Note that `map` already deals with empty lists correctly. – bli Mar 23 '18 at 13:30
2

If you want to apply a function on every element of a list, that is a map :: (a -> b) -> [a] -> [b]. The map thus takes a function f and a list xs, and generates a list ys, such that the i-th element of ys, is f applied to the i-th element of xs.

So now the only question is what mapping function we want. We want to take an element x, and return a 2-tuple (x, length x), we can express this with a lambda expression:

mapwithlength = map (\x -> (x, length x))

Or we can use ap :: Monad m => m (a -> b) -> m a -> m b for that:

import Control.Monad(ap)

mapwithlength = map (ap (,) length)

A problem is that this does not work for Ints, since these have no length. We can use show here, but there is an extra problem with that: if we perform show on a String, we get a string literal (this means that we get a string that has quotation marks, and where some characters are escaped). Based on the question, we do not want that.

We can define a parameterized function for that like:

mapwithlength f = map (ap (,) (length . f))

We can basically leave it to the user. In case they want to work with integers, they have to call it with:

forintegers = mapwithlength show

and for Strings:

forstrings = mapwithlength id
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
1

After installing the number-length package, you can do:

module Test where
import           Data.NumberLength

-- use e.g for list of String
withLength :: [[a]] -> [([a], Int)]
withLength = map (\x -> (x, length x))

-- use e.g for list of Int
withLength' :: NumberLength a => [a] -> [(a, Int)]
withLength' = map (\x -> (x, numberLength x))

Examples:

>>> withLength ["Zoo", "bear"]
[("Zoo",3),("bear",4)]
>>> withLength' [14, 344]
[(14,2),(344,3)]
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
1

As bli points out, calculating the length of a number using length (show n) does not transfer to calculating the length of a string, since show "foo" becomes "\"foo\"". Since it is not obvious what the length of something is, you could parameterise the zip function with a length function:

zipWithLength :: (a -> Int) -> [a] -> [(a, Int)]
zipWithLength len = map (\x -> (x, len x))

Examples of use:

> zipWithLength (length . show) [7,13,666]
[(7,1),(13,2),(666,3)]

> zipWithLength length ["Zoo", "School", "Bear"]
[("Zoo",3),("School",6),("Bear",4)]

> zipWithLength (length . concat) [[[1,2],[3],[4,5,6,7]], [[],[],[6],[6,6]]]
[([[1,2],[3,4],[5,6,7]],7),([[],[],[6],[6,6]],3)]
sshine
  • 15,635
  • 1
  • 41
  • 66