views:

775

answers:

6

I have two strings given as arguments to a Haskell function. s1 is smaller than s2 if s1 is shorter than s2 or if they have the same length and s1 is lexicographically smaller than s2.

+2  A: 

Try this:

compare s1 s2

(This returns LT, EQ, or GT).

Andrew Hare
I think the OP wants shorter strings to be 'smaller' than longer strings regardless of their contents. compare "b" "aa" returns GT, but I think the OP would want a function that returns LT in this case.
Pourquoi Litytestdata
+2  A: 

String is an instance of Ord and therefore you can use all of those methods to lexicographically compare string. As Andrew said, that's essentially compare but also the comparison operators, (<) among others.

smaller :: Ord a => a -> a -> Bool
smaller a b = a < b

This works for all types implementing Ord (and is really just a crude wrapper for (<)), including String.

Konrad Rudolph
+1 Nice explanation!
Andrew Hare
I think the OP wants shorter strings to be 'smaller' than longer strings regardless of their contents. For example, smaller "b" "aa" should return True. (<) doesn't take lengths into account so I don't believe your answer is quite what the OP is asking for.
Pourquoi Litytestdata
+2  A: 

The normal string compare only works on lexicographical ordering, not the length of strings.

So you'd have to write your own function to also check for the length:

smaller :: String -> String -> Bool
smaller s1 s2 | length s1 < length s2 = True
              | length s1 > length s2 = False 
              | otherwise             = s1 < s2

Or a bit more general:

compareStrings :: String -> String -> Ordering
compareStrings s1 s2 | length s1 < length s2 = LT
                     | length s1 > length s2 = GT
                     | otherwise             = compare s1 s2

Example:

ghci> compare "ab" "z"
LT
ghci> compareStrings "ab" "z"
GT


We were toying around with Monoids at university last week, and we came up with this lovely alternative Ord instance:

instance Ord a => Ord [a] where
  compare = comparing length
              `mappend` comparing head `mappend` comparing tail

But if you don't quite understand this, I suggest you stick with the first definition ;-)

Tom Lokhorst
Part of the reason comparing lengths is useful is many implementations of String in other languages store or cache the string length. The benefit is that most string comparisons will then take time O(1) as a function of the lengths of the strings. That is not the case in Haskell. All String comparisons with the native implementation will take time at least O(min(m,n)) as a function of the lengths of the strings.
Justice
Sure, and this isn't even the fastest implementation possible. It's just that I thought the questioner asked for a version of compare where the strings were first checked for length before lexicographical order. This might be useful if you want to print a list of strings and think it prettier to print smaller strings first.
Tom Lokhorst
@Tom: I'm not sure your smaller function works correctly if s2 is longer than s1. For example, smaller "aa" "b" returns True. It seems to me that the OP wants the function to return False in this case.
Pourquoi Litytestdata
@Pourquoi; Right, I forgot a case, thanks!
Tom Lokhorst
+8  A: 

I'd use something like the following:

smaller :: String -> String -> Bool
smaller s1 s2 | len1 /= len2         = (len1 < len2)
              | otherwise            = (s1 < s2)
              where (len1, len2) = (length s1, length s2)

Here's a sample run, in Hugs:

Main> smaller "b" "aa"
True
Main> smaller "aa" "b"
False
Main> smaller "this" "that"
False
Main> smaller "that" "this"
True
Pourquoi Litytestdata
This is obviously faster than my version, since it only computes the length of the strings once. It can probably be made even faster by doing a direct pattern match on the input strings, thereby merging this definition and the definition of the `length` function.
Tom Lokhorst
Haskell is all about generalization and good coding practices, and it has a very good type (class) system. Why not rewrite the function as `compare' :: Ord a => [a] -> [a] -> Ordering` or so?
Aleksandar Dimitrov
@Aleksander: It depends what the OP wants. Since the OP has asked a fairly elementary question, perhaps he/she wants an elementary answer? I don't doubt that there are faster and/or more idiomatic ways to write such a function in Haskell, but perhaps it's better to keep things simple to help the OP learn.
Pourquoi Litytestdata
+6  A: 

The one-pass solution:

lengthcompare :: Ord a => [a] -> [a] -> Ordering
lengthcompare = lc EQ
 where
  lc lx [] [] = lx
  lc _ [] _ = LT
  lc _ _ [] = GT
  lc EQ (v:vs) (w:ws) = lc (compare v w) vs ws
  lc lx (_:vs) (_:ws) = lc lx vs ws

smaller :: Ord a => [a] -> [a] -> Bool
smaller s1 s2 = lengthcompare s1 s2 == LT
Ganesh Sittampalam
That's exactly the one-pass solution I was thinking of.
Tom Lokhorst
Yeah - thought it was worth spelling out because it's quite an elegant use of pattern-matching.
Ganesh Sittampalam
+3  A: 

An shorter version of the mappend version by Tom Lokhorst above:

import Data.Monoid (mappend)
import Data.Ord (comparing)

compareStrings :: String -> String -> Ordering
compareStrings = comparing length `mappend` comparing id

Another way, taking advantage of the ordering of tuples:

import Data.Ord (comparing)
import Control.Arrow ((&&&))

compareStrings :: String -> String -> Ordering
compareStrings = comparing (length &&& id)
newacct
Oh right, I was thinking of an alternative definition, but when defining a new function, you can obviously use the existing ordering on lists. Nice definitions!
Tom Lokhorst
Also, I really need to start learning the Arrows library. I continuously see people coming up with these nice elegant definitions, and that's just using the function instances...
Tom Lokhorst
Yes, these are nice and short.comparing id == compare, so here's another alternative:comparing length `mappend` compare
Martijn