Sunday, February 1, 2026

NoSQL Databases Still Have Risks






NoSQL, which stands for Not Only SQL, is a common term for nonrelational databases. Among popular NoSQL databases you will find the MongoDB, Cassandra, CouchDB, Redis and more. NoSQL databases have become increasingly popular thanks to their benefits in particular use cases, especially in big data and real-time Web usages where performance, scalability and flexibility are key.

Database security has been and will continue to be one of the more critical aspects of application security. Access to the database grants an attacker a dangerous amount of control over the most critical information. Although the number of SQL injection vulnerabilities has been declining since 2008 due to use of secure frameworks and improved awareness, it has remained a high-impact risk.

With the emergence of new databases and query techniques, the old attack methods become obsolete and new ones emerge. For example, most NoSQL databases do not use SQL and instead use the JavaScript Object Notation (JSON) query language and an HTTP API. This makes old techniques like SQL injection obsolete. However, NoSQL definitely does not imply zero risk. In fact, NoSQL databases are vulnerable to injection attacks, cross-site request forgery (CSRF) and other vulnerabilities.

In a paper titled “No SQL, No Injection? Examining NoSQL Security,” in Web 2.0 Security and Privacy conference it has been demonstrated that a number of techniques for injections in different runtimes using MongoDB are possible. Additionally, the paper discusses Web APIs and their risks, such as CSRF, and some deployment recommendations.

Knowing the risks is key for protecting against them. Having automated security testing is also significant for achieving consistent results. Web application scanners, for instance, can use rules for finding vulnerabilities in NoSQL databases to help you protect against the new exploitation techniques.

Tuesday, September 2, 2025

Installing MongoDB on Ubuntu

Run the following command to import the MongoDB public GPG Key:
girdhar@dcsa:~$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
Create a /etc/apt/sources.list.d/mongodb.list file using the following command.
girdhar@dcsa:~$ echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list
Now issue the following command to update the repository:
girdhar@dcsa:~$ sudo apt-get update
Now install the MongoDB by using following command:
girdhar@dcsa:~$ sudo apt-get install mongodb-10gen=2.2.3
In the above installation 2.2.3 is currently released mongodb version. Make sure to install latest version always. Now mongodb is installed successfully.

Start MongoDB
girdhar@dcsa:~$ sudo service mongodb start
Stop MongoDB
girdhar@dcsa:~$ sudo service mongodb stop
Restart MongoDB
girdhar@dcsa:~$ sudo service mongodb restart
To use mongodb run the following command
girdhar@dcsa:~$ mongo
This will connect you to running mongod instance.



If It will not run (giving some error in locks), try running following: -
Step 1: Remove lock file.
girdhar@dcsa:~$ sudo rm /var/lib/mongodb/mongod.lock

Step 2: Repair mongodb.
girdhar@dcsa:~$ sudo mongod --repair

Step 3: start mongodb.
girdhar@dcsa:~$ sudo start mongodb
or
girdhar@dcsa:~$ sudo service mongodb start

Step 4: Check status of mongodb.
girdhar@dcsa:~$ sudo status mongodb
or
girdhar@dcsa:~$ sudo service mongodb status

Step 5: Start mongo console.
girdhar@dcsa:~$ mongo

Wednesday, January 8, 2025

Doing a PhD in Computer Science by Pankaj Jalote, Professor, IIT


Article Source: www.cse.iitk.ac.in/acad/doingphd.html


PhD is the highest degree offered anywhere in the world (barring the highly uncommon DSc). Its focus, unlike regular degrees, is not learning existing knowledge but creating new knowledge. No wonder, PhD is desired by anyone wishing to `make a mark' - the brightest seek it as it allows challenges that no other degree does; the innovative desire it as it allows the possibility to innovate and create new knowledge and technologies; the ambitious seek it as it is the top of the academic ladder; and the persistent seek it as this is the one degree where persistence and self discipline are brought to full use.
Despite the romance of doing a PhD, there seems to be some `fear' frequently in the youth about pursuing a PhD - prospective students are sometimes scared because they think it is ``too difficult and they will not be able to do it'', or because ``it takes too much time'', or perhaps just because ``PhD means that you have to be a teacher and I don't want to be a teacher''. All of these reasons/fears are actually false. This note discusses some of these issues in an attempt to clarify the situation for prospective candidates.

What is Involved in a PhD?

First let us clearly understand what is involved in doing a PhD. PhD is, as everyone knows, about doing research. And research is about formulating problems or questions whose answers the research or practitioner community wants to know, and whose answers are not known. Doing research is to provide some answer to these questions. So, the key aspects of doing a PhD are (a) formulating a question or a problem that is of interest and that can be solved, and (b) providing a useful/interesting solution to the stated problem. The results obtained are presented in national/international conferences, and/or submitted to scientific journals.
The problems that are addressed by a PhD scholar can range from very difficult open problems to evolutionary technology issues. In Computer Science, the problem area can range from highly theoretical/mathematical to modeling and experimentation or building new technologies. For example, there are a lot of open problems regarding the complexity of solving some problems algorithmically (for example, checking whether a number is a prime - a problem for which an efficient solution was proposed only recently by Prof. Agarwal of CSE/IITK). These problems typically involve complex theoretical and mathematical development. Similarly, there are many problems that require understanding the behavior of various systems. Approaching such problems frequently uses modeling and experimentation. Then there are problems of the type where something innovative and useful is done using computers and software. Working on such problems typically involves building systems and prototypes. In other words, a scholar doing a PhD in Computer Science has a wide range of areas to choose from, depending on his inclination, ability, and interests.
Generally, PhD programs world over proceed as follows: do some course work, pass some qualifying exam, and then write a thesis that has to be defended (sometimes, at the early stages of the thesis a `proposal' may have to be submitted.) In a place like CSE/IIT Kanpur, generally, a student who joins the PhD program after completing his/her B.Tech/BE will spend about 1 year doing the courses, about 1 to 2 years for formulating the problem, which also requires an in-depth study of the chosen area, and about 2 years or more for developing the solutions and writing the thesis. Once the thesis is written, it is examined by some experts and a thesis defense is scheduled. This process takes about 6 months, but the candidate can start his post PhD job once the thesis is submitted. Hence, doing a PhD takes about as much time as doing a BE, or the amount of time a doctor spends doing his residency and MD.
Doing a PhD is indeed hard. However, the difficulty is not because extreme intelligence is necessary. Brilliance, of course, helps - brilliant people can attack hard problems and produce solid results and leave a permanent mark on the field. However, students with good academic background and some amount of creativity can also do a PhD, and do quite well. Completing a PhD primarily requires a drive, motivation, and hard work. Hard and motivated work determines not only the quality of the final work, but also the amount of time needed to complete the PhD. In general, a PhD can be completed in 4 to 5 years - 4 years for the motivated and 5 for the not-so-motivated.

What are the Options after PhD?

Clearly, one of the main job opportunities after PhD is a faculty position in some University, College, or an Institute. This option, world over, is one of the most preferred options for people with PhD. One of the reasons for this is that in today's world, the best quality people require freedom in their work and have a strong desire to ``make a mark''. Both of these are well supported by research activities a faculty undertakes as part of his job - he has the freedom to select the problems he works on, and through his research he creates new knowledge which is published under his authorship. This is a very strong motivating factor and very good people across the world sacrifice other benefits for academic freedom and possibilities to create and innovate.
In faculty positions it should be mentioned that for Computer Science, as there is a shortage of qualified faculty in most good institutions, there are always openings in places like IITs/NITs/RECs/Central Universities, etc. In addition, now some private universities as well as institutions set up by overseas universities coming up which also are looking for qualified faculty (and these places are paying much more than Govt. scales - up to 3 times more!)
However, teaching is by no means the only option for a PhD. India today is fast becoming a center for global R&D. Due to the quality of its manpower, and the lower cost, many organizations have started setting up R&D centers in India. And these centers are looking for PhDs (and not finding them, as there are not enough getting produced!) Some example of these are IBM India Research Center in New Delhi, GE Research in Bangalore, Mentor Graphics in Hyderabad, Cadence, etc.
Besides these research centers, most of the big Indian IT companies have started high end technology development and consulting, and R&D centers or cells, in which they need PhDs. Almost all the IT majors - TCS, Infosys, Wipro, etc. employ a large number of PhDs (upward of about 50 each) and are always looking for more people to enhance their R&D activities. This activity will increase in these companies as these companies become larger. A few years back, we had done an informal survey on salaries with these companies and we were told that most of them will offer at least twice as much starting salary for a PhD as for a BE. Starting salaries for many of these positions are of the order of Rs 50,000 PM or more.
Besides these opportunities in India, people with PhDs are global citizens and move around quite a bit. Immediately after PhD, there are opportunities for post-doctoral work in US and Europe. While working as a researcher/faculty, there are opportunities for visiting faculty appointments or visiting researchers in US, Europe, Singapore, etc., where a faculty member from India can spend a year or two in overseas universities or research labs. Many faculty members from India avail of this from time to time.

Thursday, May 2, 2024

Machine language

Problem 1. Hand-compile the following C fragments into Beta assembly language. You can assume that the necessary storage allocation for each variable or array has been done and that a UASM label has been defined that indicates the first storage location for that variable or array. All of the variables are stored in main memory (in the first 32k bytes of main memory so that they can be addressed by a 16-bit literal). You can also assume that all variables and arrays are C integers, i.e., 32-bit values.
  1. Explain what Beta assembly language instruction(s) are needed to load the value of a variable that has been allocated in the first 32k bytes of main memory (i.e., at an address less than 0x8000). How would your answer change if the variable was located at address outside this range (e.g., at address 0x12468).
    If the storage for the variable is located at an address less than 0x8000, the 16-bit constant field of the LD instruction can hold the complete address. Note that the 16-bit constant is sign-extended, so our address has to fit in 15 bits. So LD(R31,addr,R0) would load the contents Mem[addr] into R0 assuming addr < 0x8000. For addresses >= 0x8000 the 16-bit constant field isn't large enough to hold the address. In these cases one could use the LDR instruction to load a 32-bit address into a register and the use LD to fetch the data:
    vaddr:
       LONG(0x12468)
    ...
       LDR(vaddr,R0)    ; load address of variable into R0
       LD(R0,0,R0)      ; load Mem[address] into R0
    
  1. a = b + 3*c;

       LD(c,R1)
       SHLC(R1,1,R0)    ; 2*c
       ADD(R0,R1,R0)    ; + c
       LD(b,R1)
       ADD(R1,R0,R0)   
       ST(R0,a)
    
  1. if (a > b) c = 17;
       LD(a,R0)
       LD(b,R1)
       CMPLE(R0,R1,R0)
       BT(R0,_L2)
       CMOVE(17,R0)
       ST(R0,c)
    _L2:
    
  1. if (sxt_short) { b = (b << 16) >> 16; }
       LD(sxt_short,R0)
       BEQ(R0,_L3)
       LD(b,R1)
       SHLC(R1,16,R0)    ; shift so that bit 15 is now bit 31
       SRAC(R0,16,R0)    ; shift back, replicating sign bit
       ST(R0,b)
    
  1. cjt->salary += 3752;
    Assume that the salary component of the structure pointed to by cjt has a byte offset of 8 from the beginning of the structure.
       LD(cjt,R1)
       LD(R1,8,R0)
       ADDC(R0,3752,R0)
       ST(R0,8,R1)
    
  1. a[i] = a[i-1];
       LD(i,R0)
       SHLC(R0,2,R0)
       LD(R0,a-4,R1)
       ST(R1,a,R0)
    
  1. sum = 0;
    for (i = 0; i < 10; i = i+1) sum += i;
       ST(R31,sum)
       ST(R31,i)
    _L7:
       LD(sum,R0)
       LD(i,R1)
       ADD(R0,R1,R0)
       ST(R0,sum)
       ADDC(R1,1,R1)
       ST(R1,i)
       CMPLTC(R1,10,R0)
       BT(R0,_L7)
    


Problem 2. In block structured languages such as C or Java, the scope of a variable declared locally within a block extends only over that block, i.e., the value of the local variable cannot be accessed outside the block. Conceptually, storage is allocated for the variable when the block is entered and deallocated when the block is exited. In many cases, this means the compiler if free to use a register to hold the value of the local variable instead of a memory location.
Consider the following C fragment:
int sum = 0;
{ int i;
  for (i = 0; i < 10; i = i+1) sum += i;
}
  1. Hand-compile this loop into assembly language, using registers to hold the values of the local variables "i" and "sum".
       MOVE(R31,R2)      ; R2 holds sum
       ST(R2,sum)
       MOVE(R31,R1)      ; R1 holds i
    _L5:
       ADD(R2,R1,R2)
       ADDC(R1,1,R1)
       CMPLTC(R1,10,R0)
       BT(R0,_L5)
       ST(R2,sum)
    
  1. Define a memory access as any access to memory, i.e., instruction fetch, data read (LD), or data write (ST). Compare the number of total number of memory accesses generated by executing the optimized loop with the total number of memory access for the unoptimized loop (part G of the preceding problem).
    The unoptimized code has an 8 instruction loop that makes 4 data accesses; 10 loop iterations => 120 memory accesses. There are 4 additional memory accesses to initialize sum and i. Total = 124.The optimized code has a 4 instruction loop that makes 0 data accesses; 10 loop iterations => 40 memory accesses. There are 6 additional memory accesses to initializes sum and i, and to store sum at the end of the loop. Total = 46.
  1. Some optimizing compilers "unroll" small loops to amortize the overhead of each loop iteration over more instructions in the body of the loop. For example, one unrolling of the loop above would be equivalent to rewriting the program as
    int sum = 0;
    { int i;
      for (i = 0; i < 10; i = i+2) { sum += i; sum += i+1; }
    }
    
    Hand-compile this loop into Beta assembly language and compare the total number of memory accesses generated when it executes to the total number of memory accesses from part (1).
       MOVE(R31,R2)      ; R2 holds sum
       ST(R2,sum)
       MOVE(R31,R1)      ; R1 holds i
    _L5:
       ADD(R2,R1,R2)
       ADDC(R1,1,R0)
       ADD(R2,R0,R2)
       ADDC(R1,2,R1)
       CMPLTC(R1,10,R0)
       BT(R0,_L5)
       ST(R2,sum)
    
    This code has a 6 instruction loop that makes 0 data accesses; 5 loop iterations => 30 memory accesses. There are 6 additional memory accesses to initializes sum and i, and to store sum at the end of the loop. Total = 36.


Problem 3.
  1. Hand-assemble the following Beta assembly language program:
            I = 0x5678
            B = 0x1234
    
            LD(I,R0)
            SHLC(R0,2,R0)
            LD(R0,B,R1)
            MULC(R1,17,R1)
            ST(R1,B,R0)
            I = 0x5678
            B = 0x1234
    
            LD(R31,I,R0)        011000 00000 11111 0101 0110 0111 1000 = 0x601F5678
            SHLC(R0,2,R0)       111100 00000 00000 0000 0000 0000 0010 = 0xF0000002
            LD(R0,B,R1)         011000 00001 00000 0001 0010 0011 0100 = 0x60201234
            MULC(R1,17,R1)      110010 00001 00001 0000 0000 0001 0001 = 0xA8210011
            ST(R1,B,R0)         011001 00001 00000 0001 0010 0011 0100 = 0x64201234
    
  1. What C statement might have been compiled into the code fragment above?
    B[I] = B[I] * 17;


Problem 4. Hand-assemble the following Beta branch instructions into their binary representation:
  1. foo: BR(foo) [recall that BR(label) = BEQ(R31,label,R31)]
        BEQ    R31   R31   offset = -1
        011101 11111 11111 1111 1111 1111 1111
    
  1. BR(bar)
    bar:
        BEQ    R31   R31   offset = 0
        011101 11111 11111 0000 0000 0000 0000
    
  1. foo = 0x100
    . = 0x1000
    BF(R17,foo,R31)
        BEQ    R31   R17   offset = (0x100 - 0x1004)/4 = 0xFC3F
        011101 11111 10001 1111 1100 0011 1111
    
  1. Explain why PC-relative branch addressing is a good choice for computers like the Beta that can encode only a "small" constant in each instruction.
    Branches are used to implement conditional and looping constructs (e.g., ifwhilefor). So most branch targets are just a few instructions away. With PC-relative addressing, we can reach targets 32768 instructions before or 32767 instructions after the branch, independent of the actual absolute address of the branch. So used as an offset, the 16-bit constant can accommodate most branch targets even for very large programs. Used as an absolute address, branch targets would be constrained to be in the first 32K of memory.
  1. Suppose a different computer could encode an arbitrary 32-bit constant in an instruction (using, e.g., a variable-length instruction encoding). Would PC-relative addressing still make sense? Why?
    Even if a complete absolute address could be encoded in an instruction, PC-relative address might be much more compact since most branch targets are nearby, i.e., we could get by with a 16-bit offset instead of a 32-bit absolute address.


Problem 5.
  1. True or false: The Beta SUBC opcode could be eliminated since every SUBC instruction can be replaced an equivalent ADDC instruction.
    False: SUBC(Rx,0x8000,Rx) subtracts -32768 from Rx. The ADDC equivalent would add 32768 to Rx, but we can't express that constant in the signed, 16-bit constant field provided in the Beta instruction format.
  1. What is the binary representation for the Beta instruction SUBC(R17,12,R22)?
     SUBC   R22   R17          12
    110001 10110 10001 0000 0000 0000 1100 = 0xC6D1000C
    
  1. A certain TA wants to know what would happen if the Beta as implemented in the lab executed 0xEDEDEDED as an instruction. What does happen?
    0xEDEDEDED = 111011 01111 01101 1110 1101 1110 1101
    The opcode field correspsonds to an illegal instruction opcode which causes the beta to take a trap (saving the PC+4 of the offending instruction in the XP register) and set the PC to ILLOP.
  1. Suppose that the Beta instruction BR(error) were assembled into memory location 0x87654. Assuming that the instruction works as intended (i.e., when executed, control is transferred to the first instruction in the error routine), which of the following is the best statement about the possible values for the symbol "error"?
    1. it depends on the first instruction in the error routine.
    2. it can have any 32-bit value
    3. it can have any 32-bit value that is a multiple of 4
    4. it is a multiple of 4 in the range 0x7F658 to 0x8F654 inclusive.
    5. it is a multiple of 4 in the range 0x67658 to 0xA7654 inclusive.
    6. none of the above
    (E): A branch instruction in which the branch is taken will multiply the sign-extended 16-bit literal field by 4 and add it to PC+4.
    if literal = 0x8000 then
      new PC = 0x87654 + 4 - (8000 * 4) = 0x67658
    if literal = 0x7FFF then
      new PC = 0x87654 + 4 + (7FFF * 4) = 0xA7654
    


Problem 6. The Meta is a processor similar to the Beta, except that the data paths have been modified to accommodate the addition of a new Subtract One and Branch instruction:
  Usage: SOB(Ra,label,Rc)
  Operation:
    literal = ((OFFSET(label) - OFFSET(current inst))/4) - 1
    PC = PC + 4
    EA = PC + 4*SEXT(literal)
    Reg[Rc] = Reg[Ra] - 1
    if (Reg[Ra]- 1) != 0 then PC = EA
As with branches in the Beta, the binary encoding of the SOB instruction places the low-order 16 bits of the "literal" value in the low-order 16 bits of the instruction. The designers of the Meta implementation have used the Meta's ALU to perform the subtraction.
  1. Suppose R1 contains the value 1. How will executing SOB(R1,label,R31) change register R1 and the PC?
    R1 is unchanged since the destination register (Rc) of the example SOB instruction is R31. Reg[R1]-1 = 0, so the branch is not taken and so the PC will point to the instruction following the SOB.
  1. Consider the following instruction sequence:
    loop: ADD(R1,R2,R3)
          SOB(R4,loop,R4)
    
    Assuming the ADD instruction is placed in location 0x108 of memory, what are the contents of the low-order 16 bits of the SOB instruction?
    Actually we don't need to know the address of the ADD instruction to answer the question since the SOB instruction (like all Beta branches) uses PC-relative addressing. Remembering that the branch offset is computed from the PC of the instruction following the SOB, the correct contents of the offset field is -2 = 0xFFFE.
  1. A schematic for the adder circuitry in the ALU of the Meta is shown below:
    What would be the correct values for OP[2:0] in order to perform a subtract (i.e., SUM = A - B)?
    SUM = A - B = A + (~B + 1). Setting OP2 = 1 and OP1 = 0 selects ~B as the XB input to the 32-bit add, setting OP0 = 1 asserts the carry in for the low-order bit of the 32-bit add and hence provides the required "+1". So OP[2:0] = 0b101.
  1. What would be the correct values for OP[2:0] in order to perform the decrement needed for the SOB instruction (i.e., SUM = A - 1)?
    If OP[2:0] = 0b110, the XB input is set to (B or ~B) = all ones, the two's complement representation for -1. Carry-in should be set to 0.
  1. Is it possible to use the logic above to do an increment (i.e., SUM = A+1)?
    Yes, OP[2:0] = 0b001, setting XB to 0 and the carry-in to 1.


Problem 7. A local junk yard offers older CPUs with non-Beta architectures that require several clocks to execute each instruction. Here are the specifications:
ModelClock RateAvg. clocks/Inst.
x40 Mhz2.0
y100 Mhz10.0
z60 Mhz3.0
You are going to choose the machine which will execute your benchmark program the fastest, so you compiled and ran the benchmark on the three machines and counted the total instructions executed:
    x: 3,600,000 instructions executed
    y: 1,900,000 instructions executed
    z: 4,200,000 instructions executed
  1. Based on the above data which machine would you choose?
    Total execution time:

    x: (3,600,000 insts)(2 clocks/inst)(25 ns/inst) = 0.18 seconds
    y: (1,900,000 insts)(10 clocks/inst)(10 ns/inst) = 0.19 seconds
    z: (4,200,000 insts)(3 clocks/inst)(16.67 ns/inst) = 0.21 seconds
    X ran the benchmark the fastest.


Problem 8. Kerry DeWay is proposing to add a "Load Constant" instruction LDC(const,Rx) to the Beta instruction set. LDC loads the 32-bit constant const in register Rx. She can't convince the hardware team to implement LDC directly and consequently plans to define it as a macro. She is considering the following alternative implementations:
[1] .macro LDC(const,Rx) {
       LD(.+8,Rx)
       BR(.+8)
       LONG(const)
    }

[2] .macro LDC(const,Rx) {
       PUSH(R17)
       BR(.+8,R17)
       LONG(const)
       LD(R17,0,Rx)
       POP(R17)
    }

[3] .macro LDC(const,Rx) {
       ADDC(R31,const >> 16,Rx)
       SHLC(Rx,16,Rx)
       ADDC(Rx,const & 0xFFFF,Rx)
    }
Kerry tries each definition on a few test cases and convinces herself each works fine. The Quality Assurance team isn't so sure and complains that Kerry's LDC implementations don't all work for every choice of register (Rx), every choice of constant (const), and every choice of code location.
  1. Evaluate each approach and decide whether it works under all circumstances or if it fails, indicate that it misbehaves for certain choices of Rx, const or code location.
    [1] fails if the code is located so that the LD instruction is at, e.g., address 0x7FFC since we can't represent .+8 = 0x8004 in the 16-bit literal field of the LD instruction.[2] fails for LDC(const,R17) since the POP(R17) at the end of the macro restores the old value of R17, wiping out the constant we just loaded.[3] fails for any const which has bit 15 set (e.g., 0x8000) since the final ADDC will sign-extended its literal field, adding 0xFFFF to the high half of Rx.


Problem 9. Which of the following Beta instruction sequences might have resulted from compiling the following C statement?
int x[20], y;
y = x[1] + 4;
  1. LD (R31, x + 1, R0)
    ADDC (R0, 4, R0)
    ST (R0, y, R31)
    Not this one. If x[0] is stored at location x, x[1] is stored at location x + 4 since x[] is an integer array and each integer takes one word (4 bytes).
  1. CMOVE (4, R0)
    ADDC (R0, x + 4, R0)
    ST (R0, y, R31)
    Not this one. The second instructions adds the address of x[1] to R0, not the contents of x[1].
  1. LD (R31, x + 4, R0)
    ST (R0, y + 4, R31)
    Not this one. This stores x[1] in the location following the one word of storage allocated for y.
  1. CMOVE (4, R0)
    LD (R0, x, R1)
    ST (R1, y, R0)
    Not this one. This implements y[1] = x[1].
  1. LD (R31, x + 4, R0)
    ADDC (R0, 4, R0)
    ST (R0, y, R31)
    Yes!
  1. ADDC (R31, x + 1, R0)
    ADDC (R0, 4, R0)
    ST (R0, y, R31)
    Not this one. The ADDC instruction loads the address of x plus 1 into R0.


Problem 10. An unnamed associate of yours has broken into the computer (a Beta of course!) that 6.004 uses for course administration. He has managed to grab the contents of the memory locations he believes holds the Beta code responsible for checking access passwords and would like you to help discover how the password code works. The memory contents are shown in the table below:
Address Contents (in hexadecimal)
0x100 0xC05F0008
0x104 0xC03F0000
0x108 0xE060000F
0x10C 0xF0210004
0x110 0xA4230800
0x114 0xF4000004
0x118 0xC4420001
0x11C 0x77E20002
0x120 0x77FFFFF9
0x124 0xA4230800
0x128 0x605F0124
0x12C 0x90211000
  1. Reconstruct the Beta assembly code that corresponds to the binary instruction encoding shown above. If the code sequence contains branches, be sure to indicate the destination of each branch.
    Address Contents        Opcode  Rc      Ra      Rb      Assembly
    0x100 0xC05F0008 110000 00010  11111   ADDC(R31, 0x8, R2)
    0x104 0xC03F0000 110000 00001  11111  ADDC(R31, 0x0, R1)
    0x108 0xE060000F 111000 00011  00000  ANDC(R0, 0xF, R3)
    0x10C 0xF0210004 111100 00001  00001  SHLC(R1, 0x4, R1)
    0x110 0xA4230800 101001 00001 00011 00001 OR(R3, R1, R1)
    0x114 0xF4000004 111101 00000  00000  SHRC(R0, 0x4, R0)
    0x118 0xC4420001 110001 00010  00010  SUBC(R2, 0x1, R2)
    0x11C 0x77E20002 011101 11111  00010  BEQ(R2,0x128) *
    0x120 0x77FFFFF9 011101 11111  11111  BEQ(R31,0x108) ** 
    0x124 0xA4230800 101001 00001  00011  not an opcode
    0x128 0x605F0124 011000 00010  11111  LD(0x0124,R2)
    0x12C 0x90211000 100100 00001  00001 00010 CMPEQ(R1,R2,R1)
    
    * The literal in instruction 0x11c is 0x2, so the corresponding label in
      Beta assembly is
     PC + 4 + 4*literal = 0x11c + 4 + 4*2 = 0x128
    ** In instruction 0x120, SEXT(literal) = -7, so the corresponding label
       in Beta assembly is
     PC + 4 + 4*literal = 0x120 + 4 + 4*(-7) = 0x124 - 0x01C = 0x108
    
  1. Further investigation reveals that the password is just a 32-bit integer which is in R0 when the code above is executed and that the system will grant access if R1 = 1 after the code has been executed. What "passnumber" will gain entry to the system?
    Let's analyze this assembly by translating it to pseudo-code:
     R2 = 8; /* R2 is used as a counter */
     R1 = 0;
    
    loop: R3 = R0 & 0xF; /* R3 stores the current low nibble of R0 */
     R1 = R1 << 4;
     R1 = R3 | R1;
     R0 = R0 >> 4;
     R2 = R2 - 1;
     if R2 == 0 goto done;
     goto loop;
    
    data: 0xA4230800
    
    done: LD(data,R2);
     if (R1 == R2)
      R1=1;
     else
      R1=0;
    
    We can see that the code shifts R1 left by a nibble (4 bits) and ors it with the low nibble (R3) of the user's entered password (R0). It then shifts the user's password right by a nibble and loops back to the beginning. It does this a total of 8 times. The net effect is to reverse the order of the nibbles in R0 and to store this into R1. The result is then compared to 0xA4230800. Therefore, in order for the entered password to be accepted, it must be the nibble-reversed version of 0xA4230800.
    Thus, the "passnumber" required to enter is 0x0080324A

Monday, April 29, 2024

STACK Memory

Let's look at the following example and see how the local variables are set in the stack area:
void f1()
{
 int a;
 short b[4];
 double c;
 f2();
 f3();
} 
When we call f1() function, space needs to be set aside. The following picture shows activation records/stack frame:
locals 
When the function f1() is called, the stack pointer will be decremented by 20 bytes which is the size of the variables of f1().
stack_pointer 
void f2()
{
 int x;
 char *y;
 char *z[2];
 f3();
}

void f3()
{
 double m[3];
 int n;
}
The following diagram shows the movement of stack pointer, sp: f1()->f1.f2()->f2.f3()->f2.f3() exits->f1.f2() exits.
stack_pointer_f1_f2_f3
After the f1.f2(), when we call f1.f3(), the variables of the function f3() simply overwrites them onto the area where the variables of f2() were.

Thursday, March 7, 2024

Google Search Tips and Tricks

You may be using Google search multiple times per day to locate something on the web. Learning how to use Google search effectively can boost your productivity instantly.
1. Identify Local Time for Any City in the World using Google
If you want to know current local time in a particular city, use the following method. To see the current local time in Los Angeles do the following. Go-ahead and try this yourself for your local city and see how it works.
Syntax: time in PLACE

time Los Angeles

Google Identify Time in a Place
Fig: Find out current time of any worldwide city

2. Exclude Keywords in the Search

If you want Google to exclude a word while searching the web page, use – (minus) before the search as shown below. This example searches for the pages which has the word ebooks, and without the word free.
ebooks -free

3. Search for Keywords with Similar Meaning. Include Synonym Keywords in Search

Instead of searching for only the given word, using ~ before the keyword you can instruct Google to search for webpages with the exact given word or the words which has same meaning. In the following example, giving ~tutorial also searches for keywords: guide, manual, reference etc.
nagios ~tutorial

(or)

debian installation ~tutorial

4. Match Any Single Word in the Search Using *

While searching, if you are not sure about which keyword to be placed in the phrase, you can match any single word using *.

For example, if you want to search for examples of vim substitution, and you are not sure whether to search for “vim editor find and replace examples”, or “vim editor search and replace examples”, then use * , which will match either find, search or any other word, as shown below.
vim editor * and replace examples
Note: You can also match multiple words by using multiple number of *’s respectively.

5. Use OR in Google Search

Using OR operator in between the words makes the following kind of search possible in Google. Following example will search for bash examples or bash programs.
bash examples OR programs

Note: The keyword OR should be in uppercase

6. Identify Definition a Word

To view the definition of a word use the following method.
Syntax: define: <word>

define: tech savvy

7. Search for a Range Using ..

If you are looking for a product in a specific price range use the following. It will search for the pages with text PDA, and $400 to $450 ranged text.
Syntax: text $100..$125

PDA $400..$450

8. Mathematical Calculations using Google

Normally for doing the metric conversions we will be using some online conversion websites or conversion softwares. However just by using Google search box you can do calculations, unit conversions and money conversions as explained below.
You can use the Google search box as your scientific calculator as
sqrt(10)

9. Unit Conversion using Google

The following will show the equivalent pounds for one kg.
kg in pound
So when you want to know what is the equivalent pounds of kg you can view it as:
Use Google for Measurement Convertor
Fig: Online Unit Conversion using Google
Following are few other possible conversions. Click on these link to see the results.

10. Money Conversion using Google

Following converts US Dollars in Euros (or) US Dollars in Rupees.
USD in Euro

(or)

USD in INR
Convert USD to Any other Currencies using Google
Fig: Currency Conversion Using Google

11. Searching within a Specific Website

You can search for a text in a specific website. The following example shows how to search only within our blog – www.thegeekstuff.com.
examples site:www.thegeekstuff.com
Note: There is no space between site: and the website address.

12. Google Search for a Given Keywords (both without and with sequence)

Basic Search for a Given Keywords – Without Sequence

The very basic feature of Google which everybody is using today is searching for pages which has the given text as:
linux command line history examples
Google - Search for Keywords without Sequence
Fig: Basic Google Search for Keywords with no specific sequence
Note: Highlighted words are not in the order as we given in the search box.

Basic Search with Keywords In a Given Sequence

If you want Google to show only the pages which has the words in the given sequence, then double quote that search string as:
"guide to install php5 from source"
Google Keywords in Sequence
Fig: Basic Search for Keywords in a specific sequence
Note: Highlighted words are in the same order as we given in the search box.

13. Search Based on File Type

The following examples searches only the matching keywords inside a Power Point presentations. In the same way, you can search for the doc, pdf and other file types.
linux introduction filetype:ppt

14. Google Advanced Search Page

If you are not able to remember some of the advanced search syntax mentioned in this article, then use the Google Advanced search page as shown below.

15. Identify Local Weather for Any City in the World using Google

To see the current weather in Los Angeles do the following. Go-ahead and try this yourself for your local city and see how it works.
Syntax: weather PLACE

weather Los Angeles
Google Weather Forecast for Local City
Fig: Find out current weather forecast of any worldwide city

Thursday, December 28, 2023

NP Complete Class

Introduction

One of the - confusing - topics in Algorithm design is the concept of NP completeness. If you search the topic on the internet you will probably find tons of articles and lectures on the subject however in this short article I will summarize it so that it is easy to remember by the average student or software engineer.

It is all about Running Time

Computational complexity analysis refers to the study of computer algorithms in terms of efficiency. Given a problem of some input size, we need to know how fast or slow it runs as the input size grows to large values. Consider the following examples:

·      Looking an item up in a perfect hash table takes a constant time, theoretically speaking, no matter how big the hash table is, the item can be found instantly (assuming it exists), the algorithm is said to have a running time of O(1). This is indeed very fast.

·      The running time to look up an item in a sorted binary search tree of (n) elements is O(Log(n)) for example if the number of items is (32) then the number of comparisons needed to find an element is (5) in the worst case. As you can see this is a fast algorithm

·      Searching for an element in an unsorted array runs in linear time O(n). If the array contains 100 elements then you need to make 100 comparisons to find the element in the worst case. This means, the algorithm will slow down linearly as the input size grows. To be exact, the running time linearly grows as the input size becomes larger.

Polynomial versus Exponential

Algorithms with a running time function of the form O(n^k) where (k) is a constant are said to run (or solved) in polynomial time. Theoretically speaking, they are fast even if (k) is large. On the other hand, algorithms with a running time of the form O(k^n) where (k) is a constant are said to run in exponential time. These algorithms are very slow. Actually it may take a computer years to finish running the algorithm.

P, NP, NP-Complete, NP-Hard

Now we know what running time means, based on that we can classify problems into classes depending on how complex they are. I will be using plain English as opposed to mathematical terms in order to make it easy to understand.

P Problem

P means that there exists an algorithm to solve the problem which runs in polynomial time. If you are still not sure what running time or polynomial means then please read the introduction one more time.

NP Problem

NP problem means that there exists an algorithm to verify a given solution is correct in polynomial time. Note that we are talking about solution verification. We are not talking about the solution for the problem because no one has ever yet discovered a fast (polynomial) solution for NP problems nor proved the solution does not exist in the first place. NP does not mean None Polynomial, please do not say that in front of people because it is embarrassing, NP stands for non deterministic which means the problem can be solved in polynomial time BUT using none deterministic machine (of course it is not the regular computer we use every day, this computer is in the mind of some weird computer scientists. If you are interested you can research it on your own. Try to search for Turing Machine)

NP-Hard Problem

Now we know what NP means, NP-Hard problem is AT LEAST as hard as any NP problem. It could be harder, who knows. Again, when thinking about problem difficulty we are still referring to the running time of the algorithm. A harder problem takes more time to finish running.

Reduction

It feels bad when you try to solve a problem and it turns to be very hard to the extent that many people have tried it without success. In order to claim that the problem cannot be solved by many people then you need to prove that it is equivalent to some known hard problem. Reduction refers to transforming a problem to another well known (hard) problem so that people won’t dare to call you stupid.  This conversion process should run in polynomial time and can be used to prove a problem is NP-Complete as we will indicate later.

NP-Complete Problem

NP-Complete problem is both NP and NP-Hard. You know what NP and NP-Hard means, so NP-Complete means a problem that is easy to verify a given solution and every problem in NP can be reduced to our problem. The hard part is proving the first example of an NP-complete problem but our friend Steve Cook did that in the 1970s. Thanks to him because he did a great job so that we are not called stupid. In few words, in order to prove that a given problem is NP-Complete then

·      Show it is NP: given a solution, show that it can be verified in polynomial time

·      Show it is NP-Hard: pick an already known NP-Complete problem and show that you can reduce it back to our problem in polynomial time

What is the big deal about the question P = NP?

·      If P is the same as NP then this is good news, there are many interesting real life problems that could be solved very quickly.

·      It is going to be really embarrassing for the folks working in computer science field because it is believed (I am assuming someone got the $1000000 prize) for long time by many people that they are not the same.

·      In reality many believe they are not the same because many people knocked their heads against the wall for so long without any success and because crazy scientists need a proof a solution does not exist, it is going to be an open question.

 

Thursday, November 30, 2023

Complexity and Big-O Notation

An important question is: How efficient is an algorithm or piece of code? Efficiency covers lots of resources, including:
  • CPU (time) usage
  • memory usage
  • disk usage
  • network usage
All are important but we will mostly talk about CPU time in 367. Other classes will discuss other resources (e.g., disk usage may be an important topic in a database class).
Be careful to differentiate between:
  1. Performance: how much time/memory/disk/... is actually used when a program is run. This depends on the machine, compiler, etc. as well as the code.
  2. Complexity: how do the resource requirements of a program or algorithm scale, i.e., what happens as the size of the problem being solved gets larger.
Complexity affects performance but not the other way around.
The time required by a method is proportional to the number of "basic operations" that it performs. Here are some examples of basic operations:
  • one arithmetic operation (e.g., +, *).
  • one assignment
  • one test (e.g., x == 0)
  • one read
  • one write (of a primitive type)
Some methods perform the same number of operations every time they are called. For example, the size method of the List class always performs just one operation: return numItems; the number of operations is independent of the size of the list. We say that methods like this (that always perform a fixed number of basic operations) require constant time.
Other methods may perform different numbers of operations, depending on the value of a parameter or a field. For example, for the array implementation of the List class, the remove method has to move over all of the items that were to the right of the item that was removed (to fill in the gap). The number of moves depends both on the position of the removed item and the number of items in the list. We call the important factors (the parameters and/or fields whose values affect the number of operations performed) the problem size or the input size.
When we consider the complexity of a method, we don't really care about the exact number of operations that are performed; instead, we care about how the number of operations relates to the problem size. If the problem size doubles, does the number of operations stay the same? double? increase in some other way? For constant-time methods like the size method, doubling the problem size does not affect the number of operations (which stays the same).
Furthermore, we are usually interested in the worst case: what is the most operations that might be performed for a given problem size (other cases -- best case and average case -- are discussed below). For example, as discussed above, the remove method has to move all of the items that come after the removed item one place to the left in the array. In the worst case, all of the items in the array must be moved. Therefore, in the worst case, the time for remove is proportional to the number of items in the list, and we say that the worst-case time for remove is linear in the number of items in the list. For a linear-time method, if the problem size doubles, the number of operations also doubles.


TEST YOURSELF #1
Assume that lists are implemented using an array. For each of the following List methods, say whether (in the worst case) the number of operations is independent of the size of the list (is a constant-time method), or is proportional to the size of the list (is a linear-time method):
  • the constructor
  • add (to the end of the list)
  • add (at a given position in the list)
  • isEmpty
  • contains
  • get
Answer:

  • constructor: This method allocates the initial array, sets current to -1 and sets numItems to 0. This has nothing to do with the sequence size and is constant-time.
  • add (to the end of the list): In the worst case, the array was full and you have to allocate a new, larger array, and copy all items. In this case the number of operations is proportional to the size of the list. If the array is not full, this is a constant-time operation (because all you have to do is copy one value into the array and increment numItems).
  • add (at a given position in the list): As for the other version of add, if the array is full, time proportional to the size of the list is required to copy the values from the old array to the new array. However, even if the array is not full, this version of add can require time proportional to the size of the list. This is because, when adding at position k, all of the items in positions k to the end must be moved over. In the worst case (when the new item is added at the beginning of the list), this requires moving all items over, and that takes time proportional to the number of items in the list.
  • isEmpty: This method simply returns the result of comparing numItems with 0; this is a constant=time operation.
  • contains: This method involves looking at each item in the list in turn to see if it is equal to the given item. In the worst case (the given item is at the end of the list or is not in the list at all), this takes time proportional to the size of the list.
  • get: This method checks for a bad position and either throws an exception or returns the value in the given position in the array. In either case it is independent of the size of the list and so it is a constant-time operation.

Constant and linear times are not the only possibilities. For example, consider method createList:
    List createList( int N ) {
      List L = new List();
      for (int k=1; k<=N; k++) L.add(0, new Integer(k));
      return L;
    }
    
Note that, for a given N, the for-loop above is equivalent to:
    L.add(0, new Integer(1) );
    L.add(0, new Integer(2) );
    L.add(0, new Integer(3) );
        ...
    L.add(0, new Integer(N) );
    
If we assume that the initial array is large enough to hold N items, then the number of operations for each call to add is proportional to the number of items in the list when add is called (because it has to move every item already in the array one place to the right to make room for the new item at position 0). For the N calls shown above, the list lengths are: 0, 1, 2, ..., N-1. So what is the total time for all N calls? It is proportional to 0 + 1 + 2 + ... + N-1.
Recall that we don't care about the exact time, just how the time depends on the problem size. For method createList, the "problem size" is the value of N (because the number of operations will be different for different values of N). It is clear that the time for the N calls (and therefore the time for method createList) is not independent of N (so createList is not a constant-time method). Is it proportional to N (linear in N)? That would mean that doubling N would double the number of operations performed by createList. Here's a table showing the value of 0+1+2+...+(N-1) for some different values of N:

N0+1+2+...+(N-1)
46
828
16120
Clearly, the value of the sum does more than double when the value of N doubles, so createList is not linear in N. In the following graph, the bars represent the lengths of the list (0, 1, 2, ..., N-1) for each of the N calls.
The value of the sum (0+1+2+...+(N-1)) is the sum of the areas of the individual bars. You can see that the bars fill about half of the square. The whole square is an N-by-N square, so its area is N2; therefore, the sum of the areas of the bars is about N2/2. In other words, the time for method createList is proportional to the square of the problem size; if the problem size doubles, the number of operations will quadruple. We say that the worst-case time for createList is quadratic in the problem size.


TEST YOURSELF #2
Consider the following three algorithms for determining whether anyone in the room has the same birthday as you.
Algorithm 1: You say your birthday, and ask whether anyone in the room has the same birthday. If anyone does have the same birthday, they answer yes.
Algorithm 2: You tell the first person your birthday, and ask if they have the same birthday; if they say no, you tell the second person your birthday and ask whether they have the same birthday; etc, for each person in the room.
Algorithm 3: You only ask questions of person 1, who only asks questions of person 2, who only asks questions of person 3, etc. You tell person 1 your birthday, and ask if they have the same birthday; if they say no, you ask them to find out about person 2. Person 1 asks person 2 and tells you the answer. If it is no, you ask person 1 to find out about person 3. Person 1 asks person 2 to find out about person 3, etc.
Question 1: For each algorithm, what is the factor that can affect the number of questions asked (the "problem size")?
Question 2: In the worst case, how many questions will be asked for each of the three algorithms?
Question 3: For each algorithm, say whether it is constant, linear, or quadratic in the problem size in the worst case.

Answer:
Question 1: The problem size is the number of people in the room.
Question 2: Assume there are N people in the room. In algorithm 1 you always ask 1 question. In algorithm 2, the worst case is if no one has your birthday. Here you have to ask every person to figure this out. This is N questions. In algorithm 3, the worst case is the same as algorithm 2. The number of questions is 1 + 2 + 3 + ... + N-1 + N. We showed before that this sum is N(N+1)/2.

Question 3: Given the number of questions you can see that algorithm 1 is constant time, algorithm 2 is linear time, and algorithm 3 is quadratic time in the problem size.

Big-O Notation

We express complexity using big-O notation. For a problem of size N:
  • a constant-time method is "order 1": O(1)
  • a linear-time method is "order N": O(N)
  • a quadratic-time method is "order N squared": O(N2)
Note that the big-O expressions do not have constants or low-order terms. This is because, when N gets large enough, constants and low-order terms don't matter (a constant-time method will be faster than a linear-time method, which will be faster than a quadratic-time method). See below for an example.
Formal definition:
    A function T(N) is O(F(N)) if for some constant c and for all values of N greater than some value n0:
    T(N) <= c * F(N)
The idea is that T(N) is the exact complexity of a method or algorithm as a function of the problem size N, and that F(N) is an upper-bound on that complexity (i.e., the actual time/space or whatever for a problem of size N will be no worse than F(N)). In practice, we want the smallest F(N) -- the least upper bound on the actual complexity.
For example, consider T(N) = 3 * N2 + 5. We can show that T(N) is O(N2) by choosing c = 4 and n0 = 2. This is because for all values of N greater than 2:
3 * N2 + 5 <= 4 * N2
T(N) is not O(N), because whatever constant c and value n0 you choose, I can always find a value of N greater than n0 so that 3 * N2 + 5 is greater than c * N.

How to Determine Complexities

In general, how can you determine the running time of a piece of code? The answer is that it depends on what kinds of statements are used.
  1. Sequence of statements
      statement 1;
      statement 2;
        ...
      statement k;
      
    (Note: this is code that really is exactly k statements; this is not an unrolled loop like the N calls to add shown above.) The total time is found by adding the times for all statements:total time = time(statement 1) + time(statement 2) + ... + time(statement k)
    If each statement is "simple" (only involves basic operations) then the time for each statement is constant and the total time is also constant: O(1). In the following examples, assume the statements are simple unless noted otherwise.
  2. if-then-else statements
      if (condition) {
          sequence of statements 1
      }
      else {
          sequence of statements 2
      }
      
    Here, either sequence 1 will execute, or sequence 2 will execute. Therefore, the worst-case time is the slowest of the two possibilities: max(time(sequence 1), time(sequence 2)). For example, if sequence 1 is O(N) and sequence 2 is O(1) the worst-case time for the whole if-then-else statement would be O(N).
  3. for loops
      for (i = 0; i < N; i++) {
          sequence of statements
      }
      
    The loop executes N times, so the sequence of statements also executes N times. Since we assume the statements are O(1), the total time for the for loop is N * O(1), which is O(N) overall.
  4. Nested loopsFirst we'll consider loops where the number of iterations of the inner loop is independent of the value of the outer loop's index. For example:
      for (i = 0; i < N; i++) {
          for (j = 0; j < M; j++) {
              sequence of statements
          }
      }
      
    The outer loop executes N times. Every time the outer loop executes, the inner loop executes M times. As a result, the statements in the inner loop execute a total of N * M times. Thus, the complexity is O(N * M). In a common special case where the stopping condition of the inner loop is j < N instead of j < M (i.e., the inner loop also executes N times), the total complexity for the two loops is O(N2).Now let's consider nested loops where the number of iterations of the inner loop depends on the value of the outer loop's index. For example:
      for (i = 0; i < N; i++) {
          for (j = i+1; j < N; j++) {
              sequence of statements
          }
      }
      
    Now we can't just multiply the number of iterations of the outer loop times the number of iterations of the inner loop, because the inner loop has a different number of iterations each time. So let's think about how many iterations that inner loop has. That information is given in the following table:
    Value of iNumber of iterations of inner loop
    0N
    1N-1
    2N-2
    ......
    N-22
    N-11
    So we can see that the total number of times the sequence of statements executes is: N + N-1 + N-2 + ... + 3 + 2 + 1. We've seen that formula before: the total is O(N2).

    TEST YOURSELF #3
    What is the worst-case complexity of the each of the following code fragments?
    1. Two loops in a row:
        for (i = 0; i < N; i++) {
            sequence of statements
        }
        for (j = 0; j < M; j++) {
            sequence of statements
        }
        
      How would the complexity change if the second loop went to N instead of M?
    2. A nested loop followed by a non-nested loop:
        for (i = 0; i < N; i++) {
            for (j = 0; j < N; j++) {
                sequence of statements
            }
        }
        for (k = 0; k < N; k++) {
            sequence of statements
        }
        
    3. A nested loop in which the number of times the inner loop executes depends on the value of the outer loop index:
        for (i = 0; i < N; i++) {
            for (j = N; j > i; j--) {
                sequence of statements
            }
        }
        
    Answer:
    1. The first loop is O(N) and the second loop is O(M). Since you don't know which is bigger, you say this is O(N+M). This can also be written as O(max(N,M)). In the case where the second loop goes to N instead of M the complexity is O(N). You can see this from either expression above. O(N+M) becomes O(2N) and when you drop the constant it is O(N). O(max(N,M)) becomes O(max(N,N)) which is O(N).
    2. The first set of nested loops is O(N2) and the second loop is O(N). This is O(max(N2,N)) which is O(N2).
    3. This is very similar to our earlier example of a nested loop where the number of iterations of the inner loop depends on the value of the index of the outer loop. The only difference is that in this example the inner-loop index is counting down from N to i+1. It is still the case that the inner loop executes N times, then N-1, then N-2, etc, so the total number of times the innermost "sequence of statements" execites is O(N2).

  5. Statements with method calls:When a statement involves a method call, the complexity of the statement includes the complexity of the method call. Assume that you know that method f takes constant time, and that method g takes time proportional to (linear in) the value of its parameter k. Then the statements below have the time complexities indicated.
      f(k);  // O(1)
      g(k);  // O(k)
      
    When a loop is involved, the same rule applies. For example:
      for (j = 0; j < N; j++) g(N);
      
    has complexity (N2). The loop executes N times and each method call g(N) is complexity O(N).

    TEST YOURSELF #4
    For each of the following loops with a method call, determine the overall complexity. As above, assume that method f takes constant time, and that method g takes time linear in the value of its parameter.
      1. for (j = 0; j < N; j++) f(j);
      
      2. for (j = 0; j < N; j++) g(j);
      
      3. for (j = 0; j < N; j++) g(k);
      
    Answer: 

  1. Each call to f(j) is O(1). The loop executes N times so it is N x O(1) or O(N).
  2. The first time the loop executes j is 0 and g(0) takes "no operations". The next time j is 1 and g(1) takes 1 operations. The last time the loop executes j is N-1 and g(N-1) takes N-1 operations. The total work is the sum of the first N-1 numbers and is O(N2).
  3. Each time through the loop g(k) takes k operations and the loop executes N times. Since you don't know the relative size of k and N, the overall complexity is O(N x k).

Best-case and Average-case Complexity

Some methods may require different amounts of time on different calls, even when the problem size is the same for both calls. For example, consider the add method that adds an item to the end of the list. In the worst case (the array is full), that method requires time proportional to the number of items in the list (because it has to copy all of them into the new, larger array). However, when the array is not full, add will only have to copy one value into the array, so in that case its time is independent of the length of the list; i.e., constant time.
In general, we may want to consider the best and average time requirements of a method as well as its worst-case time requirements. Which is considered the most important will depend on several factors. For example, if a method is part of a time-critical system like one that controls an airplane, the worst-case times are probably the most important (if the plane is flying towards a mountain and the controlling program can't make the next course correction until it has performed a computation, then the best-case and average-case times for that computation are not relevant -- the computation needs to be guaranteed to be fast enough to finish before the plane hits the mountain).
On the other hand, if occasionally waiting a long time for an answer is merely inconvenient (as opposed to life-threatening), it may be better to use an algorithm with a slow worst-case time and a fast average-case time, rather than one with so-so times in both the average and worst cases.
Note that calculating the average-case time for a method can be tricky. You need to consider all possible values for the important factors, and whether they will be distributed evenly.

When do Constants Matter?

Recall that when we use big-O notation, we drop constants and low-order terms. This is because when the problem size gets sufficiently large, those terms don't matter. However, this means that two algorithms can have thesame big-O time complexity, even though one is always faster than the other. For example, suppose algorithm 1 requires N2 time, and algorithm 2 requires 10 * N2 + N time. For both algorithms, the time is O(N2), but algorithm 1 will always be faster than algorithm 2. In this case, the constants and low-order terms do matter in terms of which algorithm is actually faster.
However, it is important to note that constants do not matter in terms of the question of how an algorithm "scales" (i.e., how does the algorithm's time change when the problem size doubles). Although an algorithm that requires N2 time will always be faster than an algorithm that requires 10*N2 time, for both algorithms, if the problem size doubles, the actual time will quadruple.
When two algorithms have different big-O time complexity, the constants and low-order terms only matter when the problem size is small. For example, even if there are large constants involved, a linear-time algorithm will always eventually be faster than a quadratic-time algorithm. This is illustrated in the following table, which shows the value of 100*N (a time that is linear in N) and the value of N2/100 (a time that is quadratic in N) for some values of N. For values of N less than 104, the quadratic time is smaller than the linear time. However, for all values of N greater than 104, the linear time is smaller.
N100*NN2/100
102104102
103105104
104106106
105107108
1061081010
1071091012