2

I have an array of strings, and all contain at least one letter:

["abc", "FFF", "EEE"]

How do I find the index of the first string that is of a different case than any previous string in the array? The function should give 1 for the above since:

FFF".eql?("FFF".upcase)

and that condition isn't true for any previous string in the array, whereas:

["P", "P2F", "ccc", "DDD"]

should yield 2 since "ccc" is not capitalized and all its predecessors are.

I know how to find the first string that is capitalized using

string_tokens.find_index { |w| w == w.upcase }

but I can't figure out how to adjust the above to account for differing case.

4
  • ["abc", "FFF", "EEE"] should return 1 or 0? Commented Jun 20, 2017 at 19:39
  • Should return "1" since "FFF" is the first string whose case differs from a previous element's. Commented Jun 20, 2017 at 19:45
  • Yes, now I've realized of it. Commented Jun 20, 2017 at 19:47
  • What if the array were ["666", "ccc", "DDD"]? Commented Jun 21, 2017 at 5:50

4 Answers 4

2

You could take each consecutive pair of elements and compare their upcase-ness. When they differ, you return the index.

def detect_case_change(ary)
  ary.each_cons(2).with_index(1) do |(a, b), idx|
    return idx if (a == a.upcase) != (b == b.upcase)
  end
  nil
end

detect_case_change ["abc", "FFF", "EEE"] # => 1
detect_case_change ["P", "P2F", "ccc", "DDD"] # => 2
Sign up to request clarification or add additional context in comments.

4 Comments

You could replace each_cons(2) + logic + return by a chunk_while
Consider ...with_index(1).
@EricDuminil: could you elaborate on how can we use chunk_while, given that the requirement seems to be to find the index of first element with different case.
@CarySwoveland: oooohh, didn't know about this one, thanks!
1

This makes some assumptions about your data being composed entirely of 'A'..'Z' and 'a'..'z':

def find_case_mismatch(list)
  index = list.each_cons(2).to_a.index do |(a,b)|
    a[0].ord & 32 != b[0].ord & 32
  end

  index && index + 1
end

This compares the character values. 'A' differs from 'a' by one bit, and that bit is always in the same place (0x20).

4 Comments

"your data being composed entirely of 'A'..'Z' and 'a'..'z'" - actually, sample data in the question has digits in it. Although not at the first position.
Would the above still work for non A-Z characters, like a or e with the accent mark on them?
Yeah, this might be needlessly specific, but it might also be sufficient. Just putting it out there.
@Natalia Not all accented characters are actually the same letter. In some languages å and a are two totally different things, while in others é and e are roughly equivalent. This will not work on UTF-8 data, only 7-bit ASCII and only letters. If you're going into accented letters, then ß and æ are possibilities as well, so Æ might end up involved. This rabbit hole is super deep.
1

Enumerable#chunk helps a lot for this task.

Enumerates over the items, chunking them together based on the return value of the block. Consecutive elements which return the same block value are chunked together.

l1 = ["abc", "FFF", "EEE"]
l2 = ["P", "P2F", "ccc", "DDD"]
p l1.chunk{|s| s == s.upcase }.to_a
# [[false, ["abc"]], [true, ["FFF", "EEE"]]]
p l2.chunk{|s| s == s.upcase }.to_a
# [[true, ["P", "P2F"]], [false, ["ccc"]], [true, ["DDD"]]]

The fact that you need an index makes it a bit less readable, but here's an example. The desired index (if it exists) is the size of the first chunk:

p l1.chunk{|s| s == s.upcase }.first.last.size
# 1
p l2.chunk{|s| s == s.upcase }.first.last.size
# 2

If the case doesn't change at all, it returns the length of the whole array:

p %w(aaa bbb ccc ddd).chunk{|s| s == s.upcase }.first.last.size
# 4

1 Comment

Ah, I see. Clever!
0

I assume that each element (string) of the array contains at least one letter and only letters of the same case.

def first_case_change_index(arr)
  s = arr.map { |s| s[/[[:alpha:]]/] }.join
  (s[0] == s[0].upcase ? s.swapcase : s) =~ /[[:upper:]]/
end

first_case_change_index ["abc", "FFF", "EEE"] #=> 1
first_case_change_index ["P", "P2F", "ccc"]   #=> 2
first_case_change_index ["P", "P2F", "DDD"]   #=> nil

The steps are as follows.

arr = ["P", "2PF", "ccc"]

a = arr.map { |s| s[/[[:alpha:]]/] }
  #=> ["P", "P", "c"]
s = a.join
  #=> "PPc"
s[0] == s[0].upcase
  #=> "P" == "P"
  #=> true
t = s.swapcase
  #=> "ppC"
t =~ /[[:upper:]]/
  #=> 2

Here is another way.

def first_case_change_index(arr)
  look_for_upcase = (arr[0] == arr[0].downcase)
  arr.each_index.drop(1).find do |i|
    (arr[i] == arr[i].upcase) == look_for_upcase
  end
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.