Clarion Challenge Results: ContainsMatch

by Gordon Smith

Published 2002-10-17    Printer-friendly version

My thanks to all who entered. The challenge, while simple enough (thankfully, as any complex challenge would be horrendous to judge), threw up the usual confusions! In particular, many entrants were uncertain about the characters to ignore (the lesson learned was to write a more complete spec, and keep meddling editors away!). To this end any entry which failed the initial tests (and arrived before the deadline) was permitted one resubmission (my apologies to Ladrière Xavier whose email bounced and thus didn't get the second chance).

Okay, on to the challenge. At its core the problem can be summed up as "Does 'xyz' contain ALL the chars 'abc'." The actual challenge was more specific (to ignore all chars except "a-z, A-Z, 0-9"), but the heart of the problem remains the same.

Analyzing Algorithms

One of the more popular measures of algorithms is the "big-oh" notation ("O").

Definition: A theoretical measure of the execution of an algorithm, usually the time or memory needed, given the problem size n, which is usually the number of items.

For example: For some function f, taking n as its input, the equation f(n) = O(g(n)) would mean that it take less than some constant g times n. This is expressed as "f of n is big oh of g of n". The following procedure adheres to this example:

getLeapCount procedure(ulong theDate)
  code
  loop i = 1 to theDate
    !do the magic
  end
  return ...

Here "do the magic" will be some const value g and will execute n times. One nice thing about the "O" notation is that it says to just ignore the trivial stuff and focus on the main "decelerator" of the algorithm (a loop for example).

The most common examples are (in order of speed)

  • O(1) - Not dependant to any variable number, i.e. a constant time
  • O(log N) - A binary chop is an example of this. Remember the old game - pick a number between 1-100, then as the one person guesses the other says higher or lower, so getting to 84 would look like 50-higher, 75-higher, 87-lower, 81-higher, 84 - you got it!
  • O(N) - Proportional to the size of N (this would include a loop to N, also loops to any constant multiples of N (0.5N, 2N, 100N)). So if you double N the function would take twice as long.
  • O(N log N) - Similar to the binary chop, but also doing N activities for each iteration (a merge sort for example). 
  • O(N^2) - Proportional to N*N. So if you double N, the function would take four times longer. Two nested loops dependent on N would yield this result. 
  • O(N^3), O(N^4),..., O(N^n) - (triple/quad nested loops etc.).
  • O(N!) - which is a function which slows down exponentially on the size of N.

The above of course are generalities. It is easy to code a O(N) function which is slower than a O(N^2) function. In fact the challenge showed this nicely - anyone who used a QUEUE came last (even when their algorithms were more "efficient") - the analogy being why would you use your car to get from your bed to the toilet?

NOTE: For an expanded discussion of the various equations see Mark Dunlop's " A Rough Guide To Big-Oh Notation."

To the test

Right, time to look at the entries. Since the CompareMatch function takes two main parameters, txt and chars, I will be using N and M to represent them. Basically all the entries fell into two categories, the O(N+M) and the O(NM). Since LOWER, UPPER, INSTRING, memchr, and CLIP are all O(N) functions, the following typicalcode falls into the O(NM) category (bad):

loop i = 1 to len(clip(chars))
  if not instring(choose(noCase = true, lower(chars[i]), |
      chars[i]), choose(noCase = true, lower(txt), txt), 1, 1)
    return false
  end
end
return true

To prove the point I took the best O(N+M) solution and the best O(NM) solution and put them head to head on some small numbers:

N

M

Winner

1

1

O(N+M)

2

1

O(N+M)

1

2

O(N+M)

2

2

O(N+M)

The test was a bit disappointing as I was hoping the O(NM) solution would be quicker for the very small cases; the results where, however, very, very close!

It's also worth noting that the results where very different in debug mode versus release mode; naturally the final results are based on release mode.

The following results only include functions that passed the test (see the source code for a complete list). The scores are the percentage of the speed of the overall winner:

#15 - John Griffiths (score: 2)

Nothing wrong with Johns approach, it was basically an O(N+M) solution, but he used a QUEUE to store his character hits, had he used an array, he would have been easily in the top half.

#14 - Matt Grossmith (score: 10)

Matt's approach was very nearly a O(N+M) but ended up a O(NL + ML), where L was a list of valid chars (note the double loop because of the inner INSTRING):

...
StringLen# = len(clip(P:Chars))
loop char# = 1 to StringLen#
  SingleChar = choose(P:Nocase=True,|
    upper(P:Chars[ char# : char#]),|
    P:Chars[ char# : char#])
  CharPos# = instring(SingleChar,MasterMap,1,1)
  if CharPos#
    SrchMap[CharPos# : CharPos#] = SingleChar
  .
...

#13 - Ville Vahtera (score: 12)

Ville used a classic O(NM) solution:

...
Loop x# = 1 To Len(Tempchars)
  Loop y# = 1 To Len(Temptxt)
    If Val(Tempchars[x#]) = Val(Temptxt[y#])
       RetVal = RetVal + 1
       break
    End
  End !second loop
End !first loop
...

#12 - Stephen Bottomley (score: 12)

Stephen (not unlike Matt) had a O(N+M) solution, but used an inefficient test for invalid chars and ended up a O(L + N + M), where L was in fact three loops nested inside a 255 loop (INRANGE is a loop after all), thus pushing his solution down to this position:

...
loop Counter = 1 to 255
  if not (inrange(counter,48,57) or |
          inrange(counter,65,90) or |
          inrange(counter,97,122))
    CharArray[Counter] = 2
  .
.
...

#11 - Brice Schagane (score: 17)

Brice's solution was, to me, the obvious solution, and the one I thought I would have seen more of. While small and simple, Brice's code ended up being in the O(ML + MN + MM + MN) category (due to two inner INSTRINGs, and two inner UPPERs). The UPPERs could have been easily moved outside the loop:

...
LOOP myPos = 1 TO LEN(xChars)
  myChar = xChars[myPos]
  IF NOT INSTRING(myChar, myQualifiers, 1, 1) Then CYCLE.
  IF xNoCase Then
     myChar = UPPER(myChar)
     xText  = UPPER(xText)
  END!_IF
  IF NOT INSTRING(myChar, xText, 1, 1) Then RETURN FALSE.
END!_LOOP
RETURN TRUE
...

#10 - Jeff Berlinghoff (score: 40)

Jeff's approach was the same as Brice's, without the two inner UPPERs (look at the difference that made) - this made it a O(ML + MN) solution.

#9 - Lew Strock (score: 44)

Lew's code was the same as Jeff's, but without the fancy and unnecessary NEWing of strings.

#8 - Geoff Robinson (score: 48)

The code is getting fancy now! This is the first entry using the clib function memchr, which is basically a variant of INSTRING but only finds a single char in an array of chars. Geoff's code is a O(NM) solution, but probably slower than the other memchr solutions due to the use of the CASE string code. CASE is notoriously slow with strings (although fine with numbers):

...
LOOP i = 1 TO L:LenChars
  TestChar = chars[i]
  CASE TestChar
  OF 'a' TO 'z' OROF 'A' TO 'Z' OROF '0' TO '9'
    if ~MemChr(address(txt),Char2,L:LenTxt) |
      then return FALSE.  ! exit if no match
  END !case
END !loop
...

#7 - Brian Ekeland (score: 68)

Brian supplied a O(NM) solution, but the second fastest at that, basically because it is using a sensible conditional to filter out the unwanted chars:

...
loc:byte = loc:charsbyte[loc:charsndx]
if loc:byte < 48 |
  or (loc:byte > 57 and loc:byte < 65) |
  or (loc:byte > 90 and loc:byte < 97) |
  or loc:byte > 122
    cycle
end
...

#6 - Carl Barnes (score: 75)

Carl had two entries, a O(NM) and a O(N+M). I included both (this is the O(NM)) as Carl had stated a preference for the O(NM) because it was faster in his tests, and indeed it is the fastest O(NM) solution in this group. I disagree with Carl as his O(N+M) was faster in my test cases.

#5 - Phil Will (score: 86)

Phil had two entries, nearly identical. I included them both as they produced a 5% difference (and two places in the test). This one is a nice O(N+M) solution, local vars do not have the AUTO attribute and there is one extra use of VAL. I suspect, and maybe Phil will (no pun intended) validate, that it is the lack of AUTO that made the difference.

#4 - Mark Goldberg: (score: 87)

Mark's approach at first glance looked expensive to me due to the following code:

...
loop CurrByte = 0 to Val('0') - 1
  TxtContains[ CurrByte ] = 1 
end
loop CurrByte = Val('9') + 1 to Val('A') - 1
  TxtContains[ CurrByte ] = 1 
end
loop CurrByte = Val('Z') + 1 to Val('a') - 1  
  TxtContains[ CurrByte ] = 1 
end
loop CurrByte = Val('z') + 1 to maximum(TxtContains,1) 
  TxtContains[ CurrByte ] = 1 
end
..

But if you inspect the above closely you will find it is a < 192 iteration loop and an efficient one at that, making his procedure a O(L + N+M) procedure, where L was small.

#3 - Carl Barnes (score: 88)

Carl is back with his O(N+M) solution!

#2 - Phil Will (score: 92)

Phil is back with his "clean" O(M+N) version. This version also gets my stamp of approval on readability, as it very clearly does what it does (apart from the use of "1" for true and uppercased keywords - but hey, the latter is subjective):

...
IF noCase
  txt=UPPER(txt)
  chars=UPPER(chars)
END
CLEAR(TxtArray)
! Load Text Array (ignores 0)
LOOP I=1 TO LEN(CLIP(Txt))
  TxtArray[Val(Txt[I])]=TRUE 
END
! Check VAL
Found=1
LOOP I=1 TO LEN(CLIP(Chars))
  ThisChar=VAL(Chars[I])
  CASE ThisChar
  OF   eValZero TO eValNine |
  OROF eValA    TO eValZ    |
  OROF eVala_   TO eValz_
    IF TxtArray[ThisChar] THEN CYCLE. 
    Found=FALSE
    BREAK
  END
END
RETURN FOUND
...

Drum roll please...

And the winner is...Adriaan van Ieperen (score: 100)

Adriaan's entry (a O(M+N) solution obviously) is over 8 % faster than his nearest rival:

ReturnValue          BYTE(TRUE)
cArray               BYTE,DIM(256)
c                    LONG,AUTO
  CODE
  ! loop 1: mark usage of all characters in txt string in cArray
  LOOP c = 1 TO LEN(txt)
    IF noCase THEN
      ! check if char is between 'a' and 'z'
      ! convert to uppercase and mark usage
      IF VAL(txt[c]) >= 97 AND VAL(txt[c]) <= 122 THEN    
        cArray[VAL(txt[c]) - 32] = TRUE                   
      ELSE
        cArray[VAL(txt[c])] = TRUE
      END
    ELSE
      cArray[VAL(txt[c])] = TRUE
    END
  END
  ! loop 2: check usage of chars that 
  ! need to exist in txt string
  ! if a char is not used then set returnvalue 
  ! to false and break out of loop
  LOOP c = 1 TO LEN(chars)
    ! filter out all chars that are not
    ! between 'A' to 'Z', 'a' to 'z'
    ! and '0' to '9'
    IF (VAL(chars[c]) >= 97 AND VAL(chars[c]) <= 122) OR |  
       (VAL(chars[c]) >= 65 AND VAL(chars[c]) <= 90)  OR |  
       (VAL(chars[c]) >= 48 AND VAL(chars[c]) <= 57)  THEN  
      IF noCase THEN
        IF VAL(chars[c]) >= 97 AND VAL(chars[c]) <= 122 THEN
          IF NOT cArray[VAL(chars[c]) - 32] THEN
            ReturnValue = FALSE
            BREAK
          END
        ELSIF NOT cArray[VAL(chars[c])] THEN
            ReturnValue = FALSE
            BREAK
        END
      ELSIF NOT cArray[VAL(chars[c])] THEN
          ReturnValue = FALSE
          BREAK
      END
    END
  END
  RETURN ReturnValue

Why is Adriaan's code so fast? There are two main reasons:

  1. Very few calls into the c55runx.dll. LEN and VAL are the only functions called. When code is this tight, calling other procedures becomes an issue.
  2. Simple IF statements: Adriaan tells the compiler exactly what to do. It might produce more machine code for each path, but each path does exactly one thing.

Summary

I hope these entries have provided some new ideas on how to keep your utility libraries small and fast. It is always worthwhile looking at any given algorithm and seeing if you can change it into a better "big-oh" category. One of the problems with a closed runtime like c55runx is that you can only guess as to the efficiency of a given library function. It's interesting to see if you could improve on, say, CLIP or INSTRING.

Now that the generic benchmarker is in place it may be a good platform for similar exercises! I would like to propose a series of challenges with the ultimate goal of building up a library of utility functions for all to use (I would be keen to see various "container" classes). To this end I would suggest that any future challenge would be permitted to use any available functionality within this library.

Download the source


Prior to joining TopSpeed Development Centre, Gordon Smith worked for an Irish company developing software for multi-national pharmaceutical companies. He was also a member of the 3rd party accessories program (Compile Manager 2) and developed the Clarion Class Browser. He is currently working with several of the original TopSpeed Developers at Seisint Inc..

Printer-friendly version

Reader Comments

Posted on Thursday, October 17, 2002 by David LeYanna

I love learning. Thanks. Lets see a series.

djl

 

Posted on Friday, October 18, 2002 by Geoff Robinson

Thanks Gordon for the analysis!

It never occurred to me that CASE would be so slow on letter ranges as opposed to numbers!  I really thought I had an "ace up my sleeve" with memChr and was surprised when Carl Barnes came up with a similar solution (but a better case statement!).

Congratulations to Adriaan!  Looking at the winning entry I thought it probably could be made faster by moving the tests for noCase outside the loops and returning immediately on finding no match:

ContainsMatch   PROCEDURE  (string txt, string chars, byte noCase)
cArray               BYTE,DIM(256)
c                    LONG,AUTO

  CODE

  ! loop 1: mark usage of all characters in txt string in cArray

  if noCase

      LOOP c = 1 TO LEN(txt)
          IF VAL(txt[c]) >= 97 AND VAL(txt[c]) <= 122 THEN    ! check if char is between 'a' and 'z'
            cArray[VAL(txt[c]) - 32] = TRUE                   ! convert to uppercase and mark usage
          ELSE
            cArray[VAL(txt[c])] = TRUE
          END
      END

      ! loop 2: check usage of chars that need to exist in txt string
      !         if a char is not used then return false
      LOOP c = 1 TO LEN(chars)
        IF (VAL(chars[c]) >= 97 AND VAL(chars[c]) <= 122) OR |  ! filter out all chars that are not
           (VAL(chars[c]) >= 65 AND VAL(chars[c]) <= 90)  OR |  ! between 'A' to 'Z', 'a' to 'z'
           (VAL(chars[c]) >= 48 AND VAL(chars[c]) <= 57)  THEN  ! and '0' to '9'

            IF VAL(chars[c]) >= 97 AND VAL(chars[c]) <= 122 THEN
              IF NOT cArray[VAL(chars[c]) - 32] THEN
                return FALSE
              END
            ELSIF NOT cArray[VAL(chars[c])] THEN
              return FALSE
            END
        END
      END
  else

      LOOP c = 1 TO LEN(txt)
          cArray[VAL(txt[c])] = TRUE
      END

      ! loop 2: check usage of chars that need to exist in txt string
      !         if a char is not used then return false
      LOOP c = 1 TO LEN(chars)
        IF (VAL(chars[c]) >= 97 AND VAL(chars[c]) <= 122) OR |  ! filter out all chars that are not
           (VAL(chars[c]) >= 65 AND VAL(chars[c]) <= 90)  OR |  ! between 'A' to 'Z', 'a' to 'z'
           (VAL(chars[c]) >= 48 AND VAL(chars[c]) <= 57)  THEN  ! and '0' to '9'

          IF NOT cArray[VAL(chars[c])] THEN
              Return FALSE
          END
        END
      END
  end
  RETURN TRUE

but testing this found it to run around the same speed which is surprising.

Finally I had also done another attempt that didn't go into the competition as I thought my memChr version was faster but it may be of interest in that I used two longs to hold a bit array of the 62 chars we were interested in.  Here it is (with the faster numeric Case statement!):

ContainsMatch     PROCEDURE  (string txt, string chars, byte noCase)

L:LenChars   LONG,AUTO
L:LenTxt     LONG,AUTO
i            LONG,AUTO

Long1        LONG
Long2        LONG
TestChar     STRING(1),AUTO
TestByte     BYTE,OVER(TestChar)

  CODE

    if nocase
        txt   = lower(txt)
        chars = lower(chars)
    end

    L:LenChars = len(chars)
    L:LenTxt   = len(txt)

    ! we have 62 characters to deal with - bitmap 'chars' into 2 longs (64 bits)
    LOOP i = 1 TO L:LenChars
        TestChar = chars[i]
        CASE TestByte
          OF 97 TO 122 !a-z
             Long1 = BOR(Long1, BSHIFT(1, TestByte - 97))  ! set mapped bit on
          OF 65 TO 90  !A-Z
             Long2 = BOR(Long2, BSHIFT(1, TestByte - 65))  ! set mapped bit on
          OF 48 TO 52  !0-4
             Long1 = BOR(Long1, BSHIFT(1, TestByte - 22))  ! set mapped bit on
          OF 53 TO 57  !5-9
             Long2 = BOR(Long2, BSHIFT(1, TestByte - 27))  ! set mapped bit on
        END !case
    END !loop

    ! We now have a bitmap representing all the chars which we need to find
    ! Start searching until they are all found OR we run out of chars in txt

    LOOP i = 1 TO L:LenTxt
        TestChar = Txt[i]
        CASE TestByte
          OF 97 TO 122 !a-z
             long1 = BAND(Long1, BXOR(-1, BSHIFT(1, TestByte - 97)))  ! set mapped bit off
          OF 65 TO 90  !A-Z
             long2 = BAND(Long2, BXOR(-1, BSHIFT(1, TestByte - 65)))  ! set mapped bit off
          OF 48 TO 52  !0-4
             long1 = BAND(Long1, BXOR(-1, BSHIFT(1, TestByte - 22)))  ! set mapped bit off
          OF 53 TO 57  !5-9
             long2 = BAND(Long2, BXOR(-1, BSHIFT(1, TestByte - 27)))  ! set mapped bit off
        END !case
    WHILE Long1 or Long2

    return choose(~(Long1 or Long2))

Regards

GeoffR

 

Posted on Friday, October 18, 2002 by Brice Schagane

Interesting.  You indicate that the fewer calls into the c55runx.dll will increase performance.  Does this hold true, regardless of how the program is compiled?  What is the difference between compiling as LOCAL and STANDALONE?

 

Posted on Friday, October 18, 2002 by Gordon Smith

GeoffR,
Knowing that "CASE" is slow with strings versus numerics is just one of those things people seem to find out via word of mouth (I know I did).  
Yes Adriaan's solution can be improved on (I know of 1 trick which would remove the big "if" block in the middle), maybe another challenege to "make it the fastest" would be interesting?
Gordon.

 

Posted on Friday, October 18, 2002 by Gordon Smith

Brice Schagane,
LOCAL will be insignificantly faster (it will save 1 extra dereference).
In general calls into c55runX arn't an issue, but you have to "think" about what the call is doing (an UPPER has to loop through every char!  so UPPER('a') is an O(1), but UPPER('abc') become an O(N)...

Gordon.


 

Posted on Saturday, October 19, 2002 by Brice

Thanks for clearing that up.  I was always under the impression that the use of dll's caused a significant performance hit.  For that reason, I try to stay away from them.

 

Posted on Monday, October 21, 2002 by Adriaan Ieperen

Wow, i actually won...

When i sent in my code i knew it wasn't really optimized and i didn't expect to even end up in the top 10.
 
It was fun to spend some time on this puzzle. I hope there will be more like this in the future.

 

Posted on Monday, October 21, 2002 by Phil Will

My final entry missed the deadline, but is just under 20% faster.  It uses a data declaration and array for upper and lower evaluation.  This is slightly faster the isUpper and isLower.  Comparisons are done only if nocase; it then uppers or lowers depending on the initial value.   

ContainsMatch PROCEDURE(string txt,string chars, byte noCase=false) !, byte

I           SHORT,AUTO
TxtArray    BYTE,DIM(256),AUTO
Found       BYTE,AUTO
ThisChar    BYTE,AUTO

eLower      EQUATE(32)                                                     ! Difference between upper and lower VALs

Alpha       STRING('<0>{47}<1>{10}<0>{7}<1>{26}<0>{6}<1>{26}<0>{133}')     ! 1=IsAlphaNumeric 0-9 a-z A-Z
AlphaArray  BYTE,DIM(255),OVER(AlPHA)
UpperStr    STRING('<0>{64}<1>{26}<0>{165}')                               ! 1=IsUpper A-Z
CharIsUpper BYTE,DIM(255),OVER(UpperStr)
LowerStr    STRING('<0>{96}<1>{26}<0>{133}')                               ! 1=IsLower a-z
CharIsLower BYTE,DIM(255),OVER(LowerStr)
   CODE

   ! Upper if not case senstive.
   CLEAR(TxtArray)

   ! Load Text Array (ignores 0)
   LOOP I=1 TO LEN(CLIP(Txt))
     TxtArray[Val(Txt[I])]=TRUE
   END

   ! Check VAL
   Found=TRUE   
   LOOP I=1 TO LEN(CLIP(Chars))
     ThisChar=VAL(Chars[I])
     CASE AlphaArray[ThisChar]
     OF 1
       IF TxtArray[ThisChar] THEN CYCLE.
       IF NoCase
         IF CharIsLower[ThisChar]
           IF TxtArray[ThisChar-eLower] THEN CYCLE.
         ELSIF CharIsUpper[ThisChar]
           IF TxtArray[ThisChar+eLower] THEN CYCLE.
         END
       END
       Found=FALSE
       BREAK
     END
   END

   RETURN FOUND

 

Posted on Tuesday, October 22, 2002 by Adriaan Ieperen

I took another look at my procedure and it turns out that it isn't that good. With a bit of work it can be made to blow the old procedure out of the water.
The new procedure is not as elegant as Phil Will's last entry (the one that didn't make it, but was in my opinion quite excellent) and it's not as interesting as Mark Goldberg's entry. But it is fast.

I found out two things that i find interesting:

1)i LONG,AUTO is much faster then using another datatype like a SHORT or a BYTE.

2) IF (cTest >= 48 AND cTest <= 57)  OR |
      (cTest >= 65 AND cTest <= 90)  OR |
      (cTest >= 97 AND cTest <= 122) THEN
This IF structure may look nasty, but is actually faster then anything else i can think of at the moment.



ContainsMatch PROCEDURE  (STRING txt, STRING chars, BYTE noCase = FALSE)
ReturnValue          BYTE(TRUE)
cArray               BYTE,DIM(256)
i                    LONG,AUTO
cTest                LONG,AUTO
  CODE

  LOOP i = 1 TO LEN(CLIP(txt))
    cArray[VAL(txt[i])] = TRUE
  END

  IF noCase THEN
    LOOP i = 1 TO LEN(CLIP(chars))
      cTest = VAL(chars[i])

      IF (cTest >= 48 AND cTest <= 57)  THEN
        IF NOT cArray[cTest] THEN
          ReturnValue = FALSE
          BREAK
        END
      END

      IF (cTest >= 65 AND cTest <= 90)  THEN
        IF NOT cArray[cTest] AND NOT cArray[cTest + 32] THEN
          ReturnValue = FALSE
          BREAK
        END
      END

      IF (cTest >= 97 AND cTest <= 122) THEN
        IF NOT cArray[cTest] AND NOT cArray[cTest - 32] THEN
          ReturnValue = FALSE
          BREAK
        END
      END
    END
  ELSE
    LOOP i = 1 TO LEN(CLIP(chars))
      cTest = VAL(chars[i])

      IF (cTest >= 48 AND cTest <= 57)  OR |
         (cTest >= 65 AND cTest <= 90)  OR |
         (cTest >= 97 AND cTest <= 122) THEN
        IF NOT cArray[cTest] THEN
          ReturnValue = FALSE
          BREAK
        END
      END
    END
  END

  RETURN ReturnValue


 

Posted on Tuesday, October 22, 2002 by Geoff Robinson

Adriaan

Better still:

On your NoCase logic:

      IF (cTest >= 48 AND cTest <= 57)  THEN
         ....
      ELSIF (cTest >= 65 AND cTest <= 90)  THEN
         ....
      ELSIF (cTest >= 97 AND cTest <= 122) THEN
         ....
      END

in other words no need to test for other conditions if you already have satisfied one (as they are mutually exclusive).

or try using a case statement??

Cheers

Geoff R

 

Posted on Wednesday, October 23, 2002 by Adriaan Ieperen

Geoff you're absolutely right. While experimenting with the procedure i completely missed that. I wonder how much difference a case statement will make compared to an if then else structure.

 

Posted on Tuesday, May 31, 2005 by Carl Barnes

"A Rough Guide To Big-Oh Notation." is no long there, he has a new article "Amorphous guide to Big-Oh Notation" http://www.cis.strath.ac.uk/~mdd/teaching/ac/big_oh.html
linked from "Algorithms and Complexity" http://www.cis.strath.ac.uk/~mdd/teaching/ac/

Amorphous => Lacking organization; formless.

To add a comment to this article you must log in.

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $189

(includes all back issues since '99)

Renewals from $139

Two years: $289

Renewals from $239

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links