user7055375
user7055375

Reputation:

How do I find the first string of differing case in an array?

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.

Upvotes: 2

Views: 62

Answers (4)

Cary Swoveland
Cary Swoveland

Reputation: 110685

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

Upvotes: 0

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230346

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

Upvotes: 2

Eric Duminil
Eric Duminil

Reputation: 54223

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

Upvotes: 1

tadman
tadman

Reputation: 211590

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).

Upvotes: 1

Related Questions