Code duplication vs new variable creation [closed] - java

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 8 years ago.
Improve this question
Looking for which of the 2 options below is considered good programming practice ?
This is a case of 2 options:
duplicates code, but does not create new variables / objects
if (a > b) {
calcSum (a, b) + calcDiff(a, b);
} else {
calcSum (b, a) + calcDiff(b, a);
}
vs
does not duplicate code, but creates new variables / objects
int big;
int small
if (a > b) {
big = a;
small = b;
} else {
big = a;
small = b;
}
calcSum (big, small) + calcDiff(big, small);

Despite what others here have said, I think the second option is preferred. Duplication of your business logic makes it hard to test and hard to maintain, so you should look for ways to avoid duplicating it.
Another way to do that would be to make it a helper method (calculateSumPlusDifference(int max, int min)) and then use it:
if (a > b) {
calculateSumPlusDifference(a, b);
} else {
calculateSumPlusDifference(b, a);
}
This way you still get compact logic around a and b, but you avoid duplicating your business logic.

I would say first option is preferable, as it is both more compact and doesn't create new variables (although that is not really a big concern, it would probably be optimized out).
Also you can use the Math library's min and max methods to do it all on one line if you so desire.

They both work. However, you really what to see what's more important. Readability or efficiency? For instance, what some people suggested is using Math.max sure, this works. However, its very inefficient. Doing any sort of Math. produces a lot of extra unneeded calls. However, its more readable. So We go back down and beg the same question. Readability or efficiency.
Weigh you options, see what is maintainable, readable, and efficient. In the end they obviously both work use YOUR judgement based on the project.
TDLR: if that line of code is going to be needed to be run 100+ times at 1 millisecond go with the second approach if it's only a one time practically linearly ran code go with readability.

I feel like these answers are all missing the point of the question: is it better to duplicate code or to spend memory creating new variables to avoid that duplication?
Well, we can actually put that to the test, can't we.
I wrote a class to run both of your examples with random values of a and b (between 1 and 10), 100,000,000 times each. The results are actually very revealing:
//trial 1
Without Creating Variables: 2.220987762 seconds
vs With Creating Variables: 2.218305816 seconds
//trial 2
Without Creating Variables: 2.215427479 seconds
vs With Creating Variables: 2.220663639 seconds
//trial 3
Without Creating Variables: 2.345803733 seconds
vs With Creating Variables: 2.347936366 seconds
Basically, there is practically no difference between the two options speed-wise—even when running them one hundred million times.
So, with that established, the option that represents best practice becomes whichever one makes the code more readable and understandable, which is definitely the second.
Here's the test class, if you'd like to check for yourself:
import java.util.Random;
public class Test {
public static void main(String[] args) {
int testLoops = 100000000;
Random rand = new Random();
//first test
long startTime1 = System.nanoTime();
for(int i=0;i<testLoops;i++) {
int a = rand.nextInt(10)+1;
int b = rand.nextInt(10)+1;
int answer;
if (a > b) {
answer = calcSum (a, b) + calcDiff(a, b);
} else {
answer = calcSum (b, a) + calcDiff(b, a);
}
}
double seconds1 = (double)(System.nanoTime() - startTime1) / 1000000000.0;
System.out.println("Without Creating Variables: " + seconds1 + " seconds");
//second test
long startTime2 = System.nanoTime();
for(int i=0;i<testLoops;i++) {
int big;
int small;
int a = rand.nextInt(10)+1;
int b = rand.nextInt(10)+1;
int answer;
if (a > b) {
big = a;
small = b;
} else {
big = a;
small = b;
}
answer = calcSum (big, small) + calcDiff(big, small);
}
double seconds2 = (double)(System.nanoTime() - startTime2) / 1000000000.0;
System.out.println(" With Creating Variables: " + seconds2 + " seconds");
}
public static int calcSum(int a, int b) {
return a+b;
}
public static int calcDiff(int a, int b) {
return a-b;
}
}

In the example that you provided, the difference is negligible, referring to speed as well as readability.
It was obviously "pseudo"-code, otherwise I'd propose a method like
void calcBoth(int a, int b) {
calcSum(a,b)+calcDiff(a,b);
}
// Call:
if (a < b) calcBoth(a,b);
else calcBoth(b,a);
Maybe something like this is applicable in your case as well.

The functional programmer purist in me might suggest:
public int calc(int a, int b) {
return (a >= b)
? calcSum(a, b) + calcDiff(a, b)
: calc(b, a);
}
At a cost of one recurse you have eliminated all temporary variables and succinctly stated your intent.
An efficient compiler may even recognize the tail recursion and substitute the correct tail-recursion-avoidance mechanism.

I prefer the second because it's easier to see your intentions, but I would have just rewritten calcDiff so that the order of the parameters don't matter.
public int calcDiff(int a, into b) {
if (a > b)
return a - b;
else
return b - a;
}
Presumably the order doesn't matter in calcSum.
public int calcSum(int a, int b) {
return a + b;
}
Then all you need is calcSum(a, b) + calcDiff(a, b).
But as a more general rule I'm not afraid of creating new variables if it improves readability. It doesn't usually make much difference to speed.

Related

recursive backtracking only works for small inputs. Larger inputs cause stackOverFlow

I am working on a problem that seems to require backtracking of some sort. I have a working recursion method but stackOverFlow happens with larger inputs. Could this be solved with an iterative implementation? I am trying to implement a method that takes in two target values a and b. starting with a = 1 and b = 1, how many "adds" would it take to reach the target a and b values? adds can either make a = a + b or b = b + a, but not both.
for example, if target a = 2 and target b = 1, it takes 1 "add". a=1 & b=1, a = a + b = 2.
public static String answer(String M, String F) {
return answerRecur(new BigInteger(M), new BigInteger(F), 0);
}
public static String answerRecur(BigInteger M, BigInteger F, int its) {
if(M.toString().equals("1") && F.toString().equals("1")) {
return "" + its;
}
else if(M.compareTo(new BigInteger("0")) <=0 || F.compareTo(new BigInteger("0")) <=0) {
return "impossible";
}
String addM = answerRecur(M.subtract(F), F, its +1);
String addF = answerRecur(M, F.subtract(M), its +1);
if(!addM.equals("impossible")) {
return addM;
}
if(!addF.equals("impossible")) {
return addF;
}
return "impossible";
}
Recursive backtracking works by going through all candidate steps, do a step, recurse, undo the step.
This means that if a solution takes N items, ideally the recursion depth will not exceed N.
So: an overflow is not expected, probably too much is tried, or even infinitely recurring.
However in your case a BigInteger might be sufficient large and when using small steps (1) one would have a very recursion depth. And every call creates sufficient much. Better would be int or long instead of BigInteger.
In every call you have two candidates:
M.subtract(F)
F.subtract(M)
You evaluate both, one could stop when a result was found.
Also intelligence (of the math!) is missing: nice would be to prevent too many steps, finding as directed as possible a solution. In general this can be achieved by some way of sorting of the (2) candidates.
How one comes at a smart solution? First the math must be readable, what BigInteger is less. Try some sample solutions by hand, and look for a smart approach to order the attempts.
You can cut the recursion short, assuming keeping M and F positive:
if (M.compareTo(BigInteger.ZERO) <= 0 || F.compareTo(BigInteger.ZERO) <= 0) {
return "impossible";
}
if (M.equals(BigInteger.ONE)) {
return String.valueOf(F.intValue() - 1 + its);
}
if (F.equals(BigInteger.ONE)) {
return String.valueOf(M.intValue() - 1 + its);
}
The same can be done with integer division (and modulo):
if (M.compareTo(F) > 0) {
String addM = answerRecur(M.mod(F), F, its + M.divided(F).intValue());
}
Thinking of an iterative solution actually is possible here despite more than one recursive call, but it would not add to the quality.
Remarks:
by java convention one should use f and m for variable names.
is BigInteger really required? It causes a bit awkward code.

UVa Online Judge 100th (3n+1) uDebug shows everything as correct but wrong answer

This is my first UVa submission so I had a few problems in the way. The biggest hurdle that took my time so far was probably getting all the formats correctly (I know, shouldn't have been too hard but I kept getting runtime error without knowing what that actually meant in this context). I did finally get past that runtime error, but I still get "Wrong answer."
Listed below are the things I've done for this problem. I've been working on this for the last few hours, and I honestly thought about just dropping it altogether, but this will bother me so much, so this is my last hope.
Things I've done:
considered int overflow so changed to long at applicable places
got the whole list (1-1000000) in the beginning through memorization for computation time
submitted to uDebug. Critical input and Random input both show matching output.
submitted to to UVa online judge and got "Wrong Answer" with 0.13~0.15 runtime.
Things I'm not too sure about:
I think I read that UVa doesn't want its classes to be public. So I left mine as class Main instead of the usual public class Main. Someone from another place mentioned that it should be the latter. Not sure which one UVa online judge likes.
input. I used BufferedReader(new InputStreaReader (System.in)) for this. Also not sure if UVa online judge likes this.
I thought my algorithm was correct but because of "Wrong answer," I'm not so sure. If my code is hard to read, I'll try to describe what I did after the code.
Here is my code:
class Main {
public static int mainMethod(long i, int c, List<Integer> l) {
if (i==1)
return ++c;
else if (i%2==0) {
if (i<1000000&&l.get((int)i)!=null)
return l.get((int)i)+c;
else {
c++;
return mainMethod(i/2, c, l);
}
}
else {
if (i<1000000&&l.get((int)i)!=null)
return l.get((int)i)+c;
else {
c++;
return mainMethod(i*3+1, c, l);
}
}
}
public static int countMax(int x, int y, List<Integer> l) {
int max=0;
if (x>y) {
int temp = x;
x= y;
y = temp;
}
for (int i=x; i<=y; i++) {
if (l.get(i)>max)
max = l.get(i);
}
return max;
}
public static void main(String[] args) {
List<Integer> fixed = Arrays.asList(new Integer[1000000]);
for (long i=1; i<1000000; i++) {
fixed.set((int)i, mainMethod(i,0,fixed));
}
String s;
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while ((s = br.readLine())!=null) {
int x = -1;
int y = -1;
for (String split : s.split("\\s+")) {
if (!split.equals("\\s+") && x==-1) {
x = Integer.parseInt(split);
} else if (!split.equals("\\s+") && x!=-1) {
y = Integer.parseInt(split);
}
}
if (x!=-1&&y!=-1)
System.out.println(Integer.toString(x) + " " + Integer.toString(y) + " " + Integer.toString(countMax(x,y,fixed)));
}
} catch (IOException e) {
} catch (NumberFormatException e) {
}
}
}
I apologize for generic names for methods and variables. mainMethod deals with memorization and creating the initial list. countMax deals with the input from the problem (15 20) and finding the max length using the list. The for loop within the main method deals with potential empty lines and too many spaces.
So my (if not so obvious) question is, what is wrong with my code? Again, this worked perfectly fine on uDebug's Random Input and Critical Input. For some reason, however, UVa online judge says that it's wrong. I'm just clueless as to where it is. I'm a student so I'm still learning. Thank you!
Haven't spotted your error yet, but a few things that may make it easier to spot.
First off:
int goes to 2^31, so declaring i in mainMethod to be long is unnecessary. It also states in the problem specification that no operation will overflow an int, doesn't it? Getting rid of the extraneous longs (and (int) casts) would make it easier to comprehend.
Second:
It's probably clearer to make your recursive call with c + 1 than ++c or doing c++ before it. Those have side effects, and it makes it harder to follow what you're doing (because if you're incrementing c, there must be a reason, right?) What you're writing is technically correct, but it's unidiomatic enough that it distracts.
Third:
So, am I missing something, or are you never actually setting any of the values in the List in your memoization function? If I'm not blind (which is a possibility) that would certainly keep it from passing as-is. Wait, no, definitely blind - you're doing it in the loop that calls it. With this sort of function, I'd expect it to mutate the List in the function. When you call it for i=1, you're computing i=4 (3 * 1 + 1) - you may as well save it.

Need explaination on two different approach to find factorial

I am a beginner.I already learned C. But now Java is seeming difficult to me. As in C programming my approach was simple , when I looked at Book's programs for simple task such as Factorial, its given very complex programs like below -
class Factorial {
// this is a recursive method
int fact(int n) {
int result;
if(n==1) return 1;
result = fact(n-1) * n;
return result;
}
}
class Recursion {
public static void main(String args[]) {
Factorial f = new Factorial();
System.out.println("Factorial of 3 is " + f.fact(3));
System.out.println("Factorial of 4 is " + f.fact(4));
System.out.println("Factorial of 5 is " + f.fact(5));
}
}
Instead, when I made my own program (given below) keeping it simple , it also worked and was easy. Can anyone tell me what's the difference between two ?
public class Simplefacto {
public static void main(String[] args) {
int n = 7;
int result = 1;
for (int i = 1; i <= n; i++) {
result = result * i;
}
System.out.println("The factorial of 7 is " + result);
}
}
also can anyone tell me what is java EE and java SE ?
The first approach is that of recursion. Which is not always fast and easy. (and usually leads to StackOverflowError, if you are not careful). The second approach is that of a normal for loop. Interstingly, both approaches are valid even in "C".
I think you should not compare Java programs with C programs. Both languages were designed for different reasons.
There are two main differences between those programs:
Program 1 uses recursion
Program 2 uses the imperative approach
Program 1 uses a class where all program logic is encapsuled
Program 2 has all the logic "like the good old C programs" in one method
The first method is Recursive. This means that the method makes calls to itself and the idea behind this is that recursion (when used appropriately) can yield extremely clean code, much like your factorial method. Formatted correctly is should look more like:
private int factorial(int n) {
if(n==1) return n;
return fact(n-1) * n;
}
So that's a factorial calculator in two lines, which is extremely clean and short. The problem is that you can run into problems for large values of n. Namely, the infamous StackOverflowError.
The second method is what is known as iterative. Iterative methods usually involve some form of a loop, and are the other option to recursion. The advantage is that they make quite readable and easy to follow code, even if it is somewhat more verbose and lengthy. This code is more robust and won't fall over for large values of n, unless n! > Integer.MAX_VALUE.
In the first case, you are adding a behavior that can be reused in multiple behaviors or main() while in the second case, you are putting inline code thats not reusable. The other difference is the recursion vs iteration. fact() is based on recursion while the inline code in main() is achieving the same thing using iteration

HOW to port "compact" Python to "compact" Java?

A friend is doing an online Scala course and shared this.
# Write a recursive function that counts how many different ways you can make
# change for an amount, given a list of coin denominations. For example, there
# are 3 ways to give change for 4 if you have coins with denomiation 1 and 2:
# 1+1+1+1, 1+1+2, 2+2.
If you are attending and still working on a solution, don't read this!
(disclaimer: even if my Python solution may be wrong, I don't want to influence your thinking if you are on the course, one way or the other! I guess it is the thinking that goes into it that yields learning, not just the "solving"...)
That aside...
I thought I'd have a go at it in Python as I don't have the Scala chops for it (I am not on the course myself, just interested in learning Python and Java and welcome "drills" to practice on).
Here's my solution, which I'd like to port to Java using as compact a notation as possible:
def show_change(money, coins, total, combo):
if total == money:
print combo, '=', money
return 1
if total > money or len(coins) == 0:
return 0
c = coins[0]
return (show_change(money, coins, total + c, combo + [c]) +
show_change(money, coins[1:], total, combo))
def make_change(money, coins):
if money == 0 or len(coins) == 0:
return 0
return show_change(money, list(set(coins)), 0, [])
def main():
print make_change(4, [2, 1])
if __name__ == '__main__':
main()
Question
How compact can I make the above in Java, allowing the use of libraries external to the JDK if they help?
I tried doing the porting myself but it was getting very verbose and I thought the usual "there must be a better way of doing this"!
Here my attempt:
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
public class MakeChange {
static int makeChange(int money, int[] coins) {
if (money == 0 || coins.length == 0) {
return 0;
}
return showChange(money, Ints.asList(coins), 0, new ArrayList<Integer>());
}
static int showChange(int money, List<Integer> coins, int total,
List<Integer> combo) {
if (total == money) {
System.out.printf("%s = %d%n", combo, total);
return 1;
}
if (total > money || coins.isEmpty()) {
return 0;
}
int c = coins.get(0);
List<Integer> comboWithC = Lists.newArrayList(combo);
comboWithC.add(c);
return (showChange(money, coins, total + c, comboWithC) + showChange(money,
coins.subList(1, coins.size()), total, combo));
}
public static void main(String[] args) {
System.out.println(makeChange(4, new int[] { 1, 2 }));
}
}
Specifically, what I dislike a lot is having to do the stuff below just to pass a copy of the list with an element appended to it:
List<Integer> comboWithC = Lists.newArrayList(combo);
comboWithC.add(c);
Please show me how compact and readable Java can be. I am still a beginner in both languages...
Really, almost everything you're doing here is directly convertible to Java, without much extra verbosity.
For example:
def make_change(money, coins):
if money == 0 or len(coins) == 0: return 0
return calculate_change(money, list(set(coins)), 0)
The obvious Java equivalent is:
public static int make_change(int money, int coins[]) {
if (money == 0 || coins.length == 0) return 0;
return calculate_change(money, coins, 0);
}
A few extra words here and there, an extra line because of the closing brace, and of course the explicit types… but beyond that, there's no big change.
Of course a more Python and Javariffic (what is the equivalent word?) version would be:
def make_change(money, coins):
if money == 0 or len(coins) == 0:
return 0
return calculate_change(money, list(set(coins)), 0)
The obvious Java equivalent is:
public static int make_change(int money, int coins[]) {
if (money == 0 || coins.length == 0) {
return 0;
}
return calculate_change(money, coins, 0);
}
So, Java gets one extra closing brace plus a few chars of whitespace; still not a big deal.
Putting the whole thing inside a class, and turning main into a method, adds about 3 more lines. Initializing an explicit array variable instead of using [2, 1] as a literal is 1 more. And System.out.println is a few characters longer than print, and length is 3 characters longer than len, and each comment takes two characters // instead of one #. But I doubt any of that is what you're worried about.
Ultimately, there's a total of one line that's tricky:
return (calculate_change(money, coins, total + c, combo + [c]) +
calculate_change(money, coins[1:], total, combo))
A Java int coins[] doesn't have any way to say "give me a new array with the tail of the current one". The easiest solution is to pass an extra start parameter, so:
public static int calculate_change(int money, int coins[], int start, int total) {
if (total == money) {
return 1;
}
if (total > money || coins.length == start) {
return 0;
}
return calculate_change(money, coins, 0, total + coins[start]) +
calculate_change(money, coins, start + 1 total);
}
In fact, nearly everything can be trivially converted to C; you just need to pass yet another param for the length of the array, because you can't calculate it at runtime as in Java or Python.
The one line you're complaining about is an interesting point that's worth putting a bit more thought into. In Python, you've got (in effect):
comboWithC = combo + [c]
With Java's List, this is:
List<Integer> comboWithC = Lists.newArrayList(combo);
comboWithC.add(c);
This is more verbose. But that's intentional. Java List<> is not meant to be used this way. For small lists, copying everything around is no big deal, but for big lists, it can be a huge performance penalty. Python's list was designed around the assumption that most of the time, you're dealing with small lists, and copying them around is perfectly fine, so it should be trivial to write. Java's List was designed around the assumption that sometimes, you're dealing with huge lists, and copying them around is a very bad idea, so your code should make it clear that you really want to do that.
The ideal solution would be to either use an algorithm that didn't need to copy lists around, or to find a data structure that was designed to be copied that way. For example, in Lisp or Haskell, the default list type is perfect for this kind of algorithm, and there are about 69105 recipes for "Lisp-style lists in Java" or "Java cons" that you should be able to find online. (Of course you could also just write your a trivial wrapper around List that added an "addToCopy" method like Python's __add__, but that's probably not the right answer; you want to write idiomatic Java, or why use Java instead of one of the many other JVM languages?)

simplifying code via refactoring

Is there a refactoring tool, either for C or Java that can simplify this type of redundant code. I believe this is called data propagation.
This is essentially what an optimizing compiler would do.
public int foo() {
int a = 3;
int b = 4;
int c = a + b;
int d = c;
System.out.println(c);
return c;
}
into
public int foo() {
int c = 7;
System.out.println(c);
return c;
}
I think it's not a good idea.
It's for example the following code:
long hours = 5;
long timeInMillis = hours * 60 * 1000;
That's much more cleaner and understandable than just:
long timeInMillis = 300000;
I can offer a solution for C. My solution uses the two tools that I described in another answer here (in reverse order).
Here is your program, translated to C:
int foo() {
int a = 3;
int b = 4;
int c = a + b;
int d = c;
printf("%d", c);
return c;
}
Step 1: Constant propagation
$ frama-c -semantic-const-folding t.c -lib-entry -main foo
...
/* Generated by Frama-C */
/*# behavior generated:
assigns \at(\result,Post) \from \nothing; */
extern int ( /* missing proto */ printf)() ;
int foo(void)
{
int a ;
int b ;
int c ;
int d ;
a = 3;
b = 4;
c = 7;
d = 7;
printf("%d",7);
return (c);
}
Step 2: Slicing
$ frama-c -slice-calls printf -slice-return foo -slice-print tt.c -lib-entry -main foo
...
/* Generated by Frama-C */
extern int printf() ;
int foo(void)
{
int c ;
c = 7;
printf("%d",7);
return (c);
}
Yes, the best refactoring tool I've seen people using is thier brain.
The brain seems a remarkably good tool for logically organising code for consumption by other brains. It can also be used to enhance the code with comments, where appropriate, and impart additional meaning with layout and naming.
Compilers are good for optimising the code for consumption by an underlying layer closer to transistors that make up the processor. One of the benefits of a higher generation programming langauge is that it doesen't read like something a machine made.
Apologies if this seems a little glib and unhelpful. I certainly have used variaious tools but I don't recall any tool that handled "data propogation."
Eclipse (and I'm sure NetBeans and IntelliJ) has almost all these refactorings available. I'll give the specifics with Eclipse. Start with:
public int foo() {
int a = 3;
int b = 4;
int c = a + b;
int d = c;
System.out.println(c);
return c;
}
First, d will show as a warning that you have an unread local variable. <CTRL>+1 on that line and select "Remove d and all assignments". Then you have:
public int foo() {
int a = 3;
int b = 4;
int c = a + b;
System.out.println(c);
return c;
}
Next, highlight the a in int c = a + b; and type <CTRL>+<ALT>+I to inline a. Repeat with b and you will have:
public int foo() {
int c = 3 + 4;
System.out.println(c);
return c;
}
Now you're almost there. I don't know of a refactoring to convert 3+4 into 7. It seems like it would be easy for someone to implement, but is probably not a common use-case as others have pointed out that, depending on the domain, 3+4 can be more expressive than 7. You could go further and inline c, giving you:
public int foo() {
System.out.println(3 + 4);
return 3 + 4;
}
But it is impossible to know if this an improvement or a step backwards without knowing the 'real' problem with the original code.
the semantic information of the code may get lost. possible dependencies might break. In short: only the programmer knows which variables are important or may become important, since only the programmer knows the context of the code. I'm afraid you'll have to do the refactoring yourself
Yes, IntelliJ offers this functionality inside of their community edition. Now to address a more serious issue, I am pretty sure you are mixing up compilation with refactoring. When you compile something you take a language higher than machine code and convert it into machine code (essentially). What you want is to remove declarations that are redundant inside the high level language that is your program file, .c,.java,etc . It is quite possible that the compiler has already optimized the less than great code into what you propose, there are tools available to see what it is doing. In terms of refactoring less is typically better, but do not sacrifice maintainability for less lines of code.
One possible approach is to put it into a symbolic math program (like Mathematica or Maple) and have it do the simplification for you. It will do it regardless of whether they are constants or not.
The drawback is that you need to convert the code to a different language. (Though it could be mostly copy and paste if the syntax is similar.) Furthermore, it could be dangerous if you expect certain integer types to overflow at a specific size. Symbolic math programs don't care and will optimize it according to the "math". Same thing goes for floating-point round-off errors.
In your example, if you enter this into Mathematica:
a = 3;
b = 4;
c = a + b;
d = c;
c
Will output this in Mathematica:
7
Of course you can't just copy and paste because it's a different language and different syntax, but it's the best thing I have in mind for your question. I myself use Mathematica to simplify expressions and other math before I throw it into C/C++.
For a more complicated example involving unknowns:
Original C Code:
int a = 3 + x*x;
int b = 4 + y*y;
int c = a + b - 7 + 2*x*y;
int d = c;
Enter this into Mathematica (which is still mostly copy+paste):
a = 3 + x*x;
b = 4 + y*y;
c = a + b - 7 + 2*x*y;
d = c;
FullSimplify[c]
Output:
(x + y)^2
Which transforms back into the following C-code:
d = (x + y)
d = d * d;
This is obviously much more simple than the original code. In general, symbolic programs will even handle non-trivial expressions and will do just as well (or even better) than any compiler internal.
The final drawback is that symbolic math programs like Mathematica or Maple aren't free and are fairly expensive. SAGE is an open-sourced program, but I hear it is not as good as either Mathematica or Maple.
If you're talking about C, you could look at the compiled, optimized assembly code. Then you could refactor your C code to the same structure as the optimized assembly. Like Alfredo said, though, that could lead to more ambiguous code.
Why not compile the code using an optimizing compiler. Then decompile the code. It is just my thought and I have not tried it out.

Categories