Reputation:
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
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
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
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
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