-2

I'm new to apex. And it's only been a 3 days since I started learning it. I was going through programming exercises and solving apex programming questions. And I stumbled through this. I get the same debug info for all numbers. Please help with a solution.

Integer I = 19;
Integer k=0;
Integer m=0;
Integer b=I;
for(Integer i=0;i<b;i++) {
    String S = String.valueOf(I);
    System.debug('Value of I '+ I);
    Integer l=s.length();
    for(Integer j=0;j<l;j++){
        String k=S.substring(j,j+1);
        Integer z=Integer.valueOf(k);
        m=m+z*z;
    }
    I = m;
    if(I == 1) {
    system.debug('It is an Happy Number');
        break;
    }
}

2
  • 5
    Apex is case insensitive, therefore my advice for you would be not to call variables i and I.
    – kurunve
    Commented Mar 4, 2022 at 11:06
  • 3
    For those unaware, a "happy number" is one where you eventually reach 1 by summing the squares of the digits of the number. e.g. 7 -> 49 -> 16 + 81 = 97 -> 81 + 49 = 130 -> 1 + 9 + 0 = 10 -> 1
    – Derek F
    Commented Mar 4, 2022 at 12:57

1 Answer 1

4

As mentioned in the comments, Apex is largely case-insensitive. There are some places where case sensitivity matters (things stored in maps or sets, String.contains(), anything involving a hash), but most of the time case does not matter.

So on the line String S = String.valueOf(I);, your loop variable i is shadowing your Integer I inside the loop. S ends up always being '0' as a result. Since you're re-setting the loop variable to 0 each time, I'd expect this to loop infinitely (or rather, until Salesforce stops execution for your code going over the CPU governor limit).

If you'd get past that issue, I suspect that your failure to reset your m variable would cause issues as well.

Apex is based on some version of Java, so (almost) all code must be in a method inside of a class. Anonymous apex allows you to run code without explicitly putting it into a method or class, but it's still a generally good idea to follow the language conventions (especially when you're learning).

Giving variables meaningful names (numberOfDigits, givenInteger, runningSum as opposed to l, i, m) will help make your code easier to read. Perhaps that isn't so important here, but it's important in general.

With all of that in mind, here's how I would write this

public class HappyNumber{
    // From https://mathworld.wolfram.com/HappyNumber.html
    // and http://oeis.org/A039943
    // we know that if we reach any of these numbers (other than 1, which
    //   I've excluded) then the number is not 'happy'
    // The 'new set<type>{ value }' syntax allows us to initialize the collection
    //   with literal values.
    // 'new set<type>()', the typical constructor (with parenthesis) does not
    //   allow us to do that (but if you wanted to copy a set into a new set you 
    //   could do Set<type> otherSet = new Set<type>(existingSet); )
    private Set<Integer> nonHappyIntermediates = new Set<Integer>{
        0, 4, 16, 20, 37, 42, 58, 89, 145
    };

    public Boolean isHappy(Integer givenInteger){
        // The strict definition of happy numbers requires the input to be
        //   positive.
        // It's generally accepted practice to perform your checks early.
        // If you're going to guard against null values, the null check must
        //   always be the first check.
        // Comparisons on integers are null-safe, so the null check here isn't
        //   necessary but I'm keeping it to illustrate the point (check null first)
        if(givenInteger == null || givenInteger < 0){ 
            // Throwing an exception might be better, but I don't feel like
            //   going that far
            return false;
        }

        // I think this is one of the rare times where using a while loop
        //   instead of a for loop makes sense
        // We stop looping if givenInteger is ever one of the non-happy
        //   intermediates (note the negation, '!' at the start of the statement.)
        // Set.contains() is a fast operation
        while(!nonHappyIntermediates.contains(givenInteger)){
            String intAsString = String.valueOf(givenInteger);
            Integer stringLength = intAsString.length();

            // It's important to reset the running sum on each iteration
            // The easiest way to force yourself to do that is to define
            //   this variable inside the scope of the loop
            Integer runningSum = 0;

            for(Integer index = 0; index < stringLength; index++){
                // substring() would work too, but I think mid() is a little
                //   easier to read and a little harder to mess up
                Integer digit = Integer.valueOf(intAsString.mid(index, 1));
                runningSum += digit * digit;
            }

            if(runningSum == 1){
                return true;
            }

            // Primitive types (like Integer, String, Boolean, etc...) are
            //   passed into functions by value.
            // So overwriting it like this (in the method) is safe
            givenInteger = runningSum;
        }

        return false;
    }
}

HappyNumber decider = new HappyNumber();
Integer testInt = 19;

// The 'boolean test ? <value if true> : <value if false>' is a ternary
// It can act as a shortened version of an if/else (both the true and false results
//   must be the same type)
system.debug(testInt + (decider.isHappy(testInt) ? ' is ' : ' is not ') + 'a happy Number');

// Doing string concatenation manually can be messy/error prone.
// An alternative is to use String.format() with placeholders
system.debug(String.format('{0} {1} a happy number', new List<Object>{
    testInt,                                   // gets substituted in place of {0}
    decider.isHappy(testInt) ? 'is' : 'is not' // gets substituted in place of {1}
});
5
  • 3
    P.S. Did you know that boolean comparisons are now null-safe, as they are in JavaScript? It'd be technically a bit easier to write if(givenInteger > 0) { while(...) ... } (if either side of a comparison is null, the operator returns false).
    – sfdcfox
    Commented Mar 4, 2022 at 16:10
  • @sfdcfox I think I got bitten by a null comparison some time ago and just assumed we needed a null check. Looks like this null-safe behavior stretches back to at least API v30.0 (Spring '14)
    – Derek F
    Commented Mar 4, 2022 at 16:19
  • I would mention Apex methods written to be consumed by Aura/LWC as probably the most important case-sensitivity.
    – Adrian Larson
    Commented Mar 4, 2022 at 16:57
  • @sfdcfox That's awesome! Since when? I thought I've seen that fail pretty recently still.
    – Adrian Larson
    Commented Mar 4, 2022 at 16:59
  • 1
    @AdrianLarson It was presumably released with SNO, but wasn't put in the release notes. You'd be forgiven for missing this. See this Q.
    – sfdcfox
    Commented Mar 4, 2022 at 20:00

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .