Why can't I print a line of code

I'm trying to just print what's on line 11. I can print to everything else but that line. Any hints?

1 Like

Welcome to the Forums, @geraldinejns.

hasPrefix is case-sensitive; it believes that "hello" != "Hello".

Unfortunately there's no built-in version of hasPrefix that is suitable for use with human-readable text.

Instead, you probably want to use localizedStandardRange(of:) from the Swift standard library¹, e.g.:

let question = "Hello, how are you?"

if question.startIndex == question.localizedStandardRange(of: "hello")?.lowerBound {
    print("Why hello!")
    …
} else {
    …
}

That will (in principle) use the appropriate criteria based on the user's locale. i.e. that almost always means being case- and diacritic-insensitive, and might² also be width-insensitive in languages & locales where that's important (e.g. Japanese).

It's not the most efficient way to check for a prefix (it will pointlessly try to find the substring anywhere in the string, not just at the very start), but unfortunately there aren't any better options (that I'm aware of) in the Swift standard library nor in Foundation.


¹ Be careful not to confuse this with the identically-named NSString method from Foundation. They functionally do the same thing, but return different types (Range vs NSRange). The compiler should be able to correctly infer which one you mean, when used like the above example.

² I have to say might because the documentation doesn't actually mention this. Someone will have to test that in a variety of circumstances (e.g. Japanese characters in both Japanese and non-Japanese locales).


P.S. It's better to include your code snippets as actual code, not images. That way others can easily copy-paste them to reproduce what you're seeing for themselves. You can use triple-backticks (```) around the code to have it properly formatted, e.g.:

```
print("Hello, world!")
```

6 Likes

So actually, there's a better solution… I ended up diving into this deeper, out of curiosity, and discovered that Swift's string comparison methods are even worse than I thought. I wrote about it, if you're interested in the gory details.

TL;DR, the better way to do this is:

let question = "Hello, how are you?"

if nil != question.range(of: "hello",
                         options: [.anchored,
                                   .caseInsensitive,
                                   .diacriticInsensitive,
                                   .numeric,
                                   .widthInsensitive]) {
    print("Why hello!")
    …
} else {
    …
}

With the specific example text here it doesn't make a functional difference - although it's certainly much more efficient - but with other text it might. localizedStandardRange(of:) is not width-insensitive (which is certainly a mistake) nor numeral- and baseline-insensitive (which is more debatable, but probably not the best default).

4 Likes

I personally make all strings lower case before comparing (parsing) them, such as:

let question = "Hello, how are you?".lowercased()

Great paper. One of those instances I regret I could only put a single like.

Out of curiosity tested this string of "A"'s:

and immediately found a glitch in Xcode's editor (without compiling, just searching) and TextEdit.app. Unicode is hard.

1 Like

Thanks for reaching out. I'm just trying to figure out how to just print "No questions today?"
The only way to print that line of code would be to place it beneath the second else.

//I finally figured it out.

let question = "No questions today?"
if question.hasPrefix("NO") {
      print("Why hello!")
  if question.hasSuffix("NO") {
     print("That's a good question!")
}else{
      print("No questions today?")
}
}else{
     print("We're all business today.")
I finally figured it out.

let question = "No questions today?"
if question.hasPrefix("NO") {
      print("Why hello!")
  if question.hasSuffix("NO") {
     print("That's a good question!")
}else{
      print("No questions today?")
}
}else{
     print("We're all business today.")

Ah yes, I was going to talk about this in my blog post, but forgot.

You should basically never use uppercased and lowercased for normalisation purposes, not only because that's only accounting for case (not diacritics, width, varying numeral systems, etc) but also because they just don't work in some languages. The textbook example is German, where "ß" is equivalent to "ss" but both are already lowercase so your example fails.

Correct case-insensitive comparison requires what Unicode calls "case folding", which is not lowercasing or uppercasing but rather normalisation of case to some canonical, consistent form, so that you can then do the comparison. Swift does not provide case-folding functionality directly (as opposed to e.g. Python which does), only indirectly as the caseInsensitive flag for certain comparison and range methods. Which are better anyway, for most purposes, since creating a copy of the string is wasteful when you merely need to compare it to another.

3 Likes

Fascinating. This works though: "ß".uppercased() == "ss".upercased().
Do you have another killer example where uppercasing doesn't work either?

I'm also curious do we have situations when, say, "A" and "Â" (picking those just for the sake of example) should be treated equal according to one language and/or locale rules and different according to another language and/or locale? Do you know a good example of this?

Right - and this leads folks to try things like a.uppercased().lowercased() == b.uppercased().lowercased(). Which handles more cases correctly, but still not necessarily all. And it's just plain inefficient.

Not off-hand, but I'd be amazed if there aren't some.

I don't know much about non-English locales (nor that specific example), which is why I cautioned in my blog post on this topic that insensitivity to these aspects might be wrong in some locales.

e.g. in Spanish it is wrong to treat "n" and "ñ" the same, because there are words which differ only in that respect but mean completely different things (and the difference can be very important, like coño vs cono - more examples).

And yet, lots of Spanish websites and applications do treat them equivalently in a lot of cases anyway, e.g. if you enter "nina" into spanishdict.com it will still match the actual word "niña".

Tip / side-rant

There's no such thing as "el nino" or "la nina" - it's "el niño" and "la niña", and they're pronounced differently because ñ is not an n, despite its visual similarities.

Similarly, it's a piña colada (pin-ya, not peen-a), because piña is pineapple in Spanish. "Pina" doesn't mean anything. The drink is literally named "strained pineapple" (strained in the kitchen sense, not the stressed sense :laughing:).

Maybe that's just it doing auto-correct, but for a random Swift app without access to an auto-correct dictionary, being diacritic-insensitive isn't the worst approximation thereof (especially if you take pains to prioritise exact matches over inexact ones).

It's hard to do context-unaware string handling - even just knowing a locale isn't enough to do it perfectly. e.g. in an English locale does "nina" mean:

  • A name (that you mistakenly didn't capitalise, "Nina").
  • A misspelling (e.g. "nine" or "nana").
  • An attempt to use the Spanish word "niña",
  • Something else?

Even a native speaker can have trouble deciding, even with a lot of context, and the decision is important because it determines whether that possibly-missing-˜ is important or not.

Which, again, is why I think being insensitive to all these aspects of characters is actually a really good general default. At least for searching (better to have false positives than false negatives) and security (better to avoid duplicates which might confuse a human).

Of course there are some use-cases where that insensitivity is inappropriate (e.g. showing changes in a document, where even if the semantics don't change you probably still want to see glyph changes).

1 Like

Folks, I think you can find a more appropriate place for long, rambling posts about Unicode and case-sensitivity than this thread, in which Geraldine is struggling to understand the control flow of an if statement.

Geraldine, I'm glad you found a solution. Please let us know if you have any more questions about why it works.

14 Likes