I want to get the intersection of 2 arrays of strings. The first array has different upper and lower case. The resulting array I want should respect the first arrays casing, but the comparison between the 2 should ignore the upper/lower case. E.g.
letters = ['Aaa', 'BbB', 'CCC']
permitted = ['aaa', 'bbb']
The result should be:
['Aaa', 'BbB']
Im doing:
letters.map(&:downcase) & permitted.map(&:downcase)
But this returns ['aaa', 'bbb']
What's a neat way of doing this? The longer way of doing it is:
letters.each { |letter|
if permitted.include?(letter.downcase)
accepted.push(letter)
end
}
But is there a shorter/neater way?
You can use select:
search = permitted.map(&:downcase)
letters.select{|letter|
search.include?(letter.downcase)
}
Or even neater (imho):
-> search {
letters.select{|x| search.include?(x.downcase)}
}.call(permitted.map(&:downcase))
Demonstration
There's a method for comparing string in a case-insensitive manner, String#casecmp:
letters = ['Aaa', 'BbB', 'CCC']
permitted = ['aaa', 'bbb']
letters.select{|l| permitted.detect{|p| p.casecmp(l) == 0 } } # => ["Aaa", "BbB"]
You can also use regular expressions. :)
letters = ['Aaa', 'BbB', 'CCC']
permitted = ['aaa', 'bbb']
letters.grep(Regexp.new(permitted.join('|'), Regexp::IGNORECASE)) # => ["Aaa", "BbB"]
Related
I have an array ["Q10", "Q100", "Q1000", "Q1000a", "Q1001", "Q98"]. After sorting it, I get the following result:
['Q100', 'Q1000', 'Q1000a','Q98', 'Q10', 'Q1001'].sort
["Q10", "Q100", "Q1000", "Q1000a", "Q1001", "Q98"]
Because of this behaviour, I cannot sort my ActiveRecord objects correctly. I have a model of Question which has a field label. I need to sort it based on label. So Question with label Q1 would be first and the question with label Q1a would follow and so on. I get in a similar order with ActiveRecord described to the above example of array. I am using postgresql as my database.
Now I have 3 questions.
Why alphanumeric string sorting behave that way?
How can I achieve my required sorting without using the sort block?
How can I achieve that sorting in ActiveRecord?
If your array were
arr = ["Q10", "Q100", "Q1000", "Q8", "Q1001", "Q98"]
you could write
arr.sort_by { |s| s[/\d+/].to_i }
#=> ["Q8", "Q10", "Q98", "Q100", "Q1000", "Q1001"]
If
s = "Q1000"
then
s[/\d+/].to_i
#=> 1000
See Enumerable#sort_by and String#[].
The regular expression /\d+/ matches a substring of s that contains one or more digits.
If the array were
arr = ["Q10b", "Q100", "Q1000", "Q10a", "Q1001", "Q98", "Q10c"]
you could write
arr.sort_by { |s| [s[/\d+/].to_i, s[/\D+\z/]] }
#=> ["Q10a", "Q10b", "Q10c", "Q98", "Q100", "Q1000", "Q1001"]
If
s = "Q10b"
then
[s[/\d+/].to_i, s[/\D+\z/]]
#=> [10, "b"]
The regular expression /\D+\z/ matches a substring of s that contains one or more non-digits at the end (\z) of the string.
See Array#<=>, specifically the third paragraph, for an explanation of how arrays are ordered when sorting.
If the array were
arr = ["Q10b", "P100", "Q1000", "PQ10a", "Q1001", "Q98", "Q10c"]
you could write
arr.sort_by { |s| [s[/\A\D+/], s[/\d+/].to_i, s[/\D+\z/]] }
#=> ["P100", "PQ10a", "Q10b", "Q10c", "Q98", "Q1000", "Q1001"]
If
s = "PQ10a"
then
[s[/\A\D+/], s[/\d+/].to_i, s[/\D+\z/]]
#=> ["PQ", 10, "a"]
The regular expression /\A\D+/ matches a substring of s that contains one or more non-digits at the beginning (\A) of the string.
This should do the trick for you, casting them to numbers before sorting.
['100', '1000', '98', '10', '1001'].map(&:to_i).sort
This strange map(&:to_i) is shorthand for map { |x| x.to_i }
Edit:
You could do this with AR. This will throw an error if the column doesn't contain a number disguised as a string.
Model.order("some_column::integer")
Edit II:
Try this if it contains strings as well.
Model.order("cast(some_column as integer))"
While importing from an excel file to a database, I need to format a hierarchy so it appears with leading zeros:
10.1.1.4 must be transformed into 1.010.001.001.004
I tried to iterate through and concatenate the elements:
record.hierarchy = spreadsheet.cell(i,2).split('.').each do |t|
index = index || '1.'
index = index + '.' + (((t.to_i + 1000).to_s).last(3))
end
which actually returns and array of ["10", "1", "1", "4"], not computed. I would expect this to return the last evaluated value: index
I tried to compute it directly inside the array:
record.hierarchy = '1.' + (((spreadsheet.cell(i,2).split('.').each).to_i + 1000).to_s).last(3).join('.')
which raises an undefined method to_i for enumerator.
Can someone explain me how to structure and solve this computation?
Thanks
Use #rjust.
'10.1.1.4'.split('.').map { |l| l.rjust(3, '0') }.join('.')
Your first solution uses assignment with #each. #each will not return modified array.
It is not necessary to convert the string to an array, modify the elements of the array and then join the array back into a string. The string can be modified directly using String#gsub.
str = '10.1.1.4'
('1.' + str).gsub(/(?<=\.)\d+/) { |s| sprintf("%03d", s.to_i) }
#=> "1.010.001.001.004"
See Kernel#sprintf.
(?<=\.) is positive lookbehind that requires the matched digits to be preceded by a period. I've assumed the string is known to contain between one and three digits before and after each period.
You can try different function for leading zeroes and inject to not set default value inside the loop
record.hierarchy = spreadsheet.cell(i,2).split('.').inject('1') do |result, t|
result + '.' + t.rjust(3, '0')
end
I need trim all elements in a list in groovy or grails?
what is the best solution
Assuming it is a list of strings and you want to trim each string, you can do it using the spread operator (*.)
list = [" abc ", " xyz "]
list*.trim()
You can use the collect method or the spread operator to create a new list with the trimmed elements:
def strs = ['a', ' b', ' ']
assert strs.collect { it.trim() } == ['a', 'b', '']
assert strs*.trim() == ['a', 'b', '']
In those cases, the original list isn't modified. If you want to trim the strings in place, you'll need to iterate through the list with an index:
for (i in 0..<strs.size()) {
strs[i] = strs[i].trim()
}
Could this code be optimised with a regex?
search = search.split.delete_if{|s|s.length==1}.map{|s|s="%#{s}%"}
It takes a string, splits it into an array and removes any elements with a length of 1 and wraps each string in percent symbols ready for SQL.
IN: "abc d efg" OUT: ["%abc%","%efg%"]
This works, but it benchmarks slower, I don't think a regex based solution is going to be faster.
string.scan(/\S{2,}/).map { |word| "%#{word}%" }
Avoiding duplicating arrays seems to be the fastest I can think of:
words = string.split
words.reject! { |s| s.length == 1 }
words.map! { |s| "%#{s}%" }
words
I have an array of string which contains the "firstname.lastname?some.xx" format strings:
customers = ["aaa.bbb?q21.dd", "ccc.ddd?ew3.yt", "www.uuu?nbg.xcv", ...]
Now, I would like to use this array to produce two arrays, with:
the element of the 1st array has only the string before "?" and replace the "." to a space.
the element of the 2nd array is the string after "?" and include "?"
That's I want to produce the following two arrays from the customers array:
1st_arr = ["aaa bbb", "ccc ddd", "www uuu", ...]
2nd_arr = ["?q21.dd", "?ew3.yt", "?nbg.xcv", ...]
What is the most efficient way to do it if I use customers array as an argument of a method?
def produce_two_arr customers
#What is the most efficient way to produce the two arrays
#What I did:
1st_arr = Array.new
2nd_arr = Array.new
customers.each do |el|
1st_Str, 2nd_Str=el.split('?')
1st_arr << 1st_str.gsub(/\./, " ")
2nd_arr << "?"+2nd_str
end
p 1st_arr
p 2nd_arr
end
Functional approach: when you are generating results inside a loop but you want them to be split in different arrays, Array#transpose comes handy:
ary1, ary2 = customers.map do |customer|
a, b = customer.split("?", 2)
[a.gsub(".", " "), "?" + b]
end.transpose
Anytime you're building an array from another, reduce (a.k.a. inject) is a great help:
But sometimes, a good ol' map is all you need (in this case, either one works because you're building an array of the same size):
a, b = customers.map do |customer|
a, b = customer.split('?')
[a.tr('.', ' '), "?#{b}"]
end.transpose
This is very efficient since you're only iterating through customers a single time and you are making efficient use of memory by not creating lots of extraneous strings and arrays through the + method.
Array#collect is good for this type of thing:
arr1 = customers.collect{ |c| c.split("?").first.sub( ".", "" ) }
arr2 = customers.collect{ |c| "?" + c.split("?").last }
But, you have to do the initial c.split("?") twice. So, it's effecient from an amount of code point of view, but more CPU intensive.
1st_arr = customers.collect{ |name| name.gsub(/\?.*\z/,'').gsub(/\./,' ') }
2nd_arr = customers.collect{ |name| name.match(/\?.*\z/)[0] }
array1, array2 = customers.map{|el| el.sub('.', ' ').split /(?:\?)/}.transpose
Based on #Tokland 's code, but it avoids the extra variables (by using 'sub' instead of 'gsub') and the re-attaching of '?' (by using a non-capturing regex).