Ruby: how to sort array of string parsing the content -
here problem: have array of string contains data that:
array = ["{109}{08} ok", "{98} thx", "{108}{0.8}{908} aa", "{8}{51} lorem ipsum"] i sort array scanning "data inside": here integers in brace. so, final array should :
array.custom_sort! => ["{8}{51} lorem ipsum", "{98} thx", "{108}{0.8}{908} aa", "{109}{08} ok"] is there nice solution in ruby? or should recreate new array inserts each parsed elements?
edit:
i failed mention sort priorities: first, sorting based on number in braces, 3 groups, cannot absent.
["{5}something", "{61}{64}could", "{}be", #raise error or ignore "{54}{31.24}{0.2}write", "{11}{21}{87}{65}here", #raise error or ignore "[]or", #raise error or ignore "{31}not"] if first numbers equal, second ones should compared. examples:
"{15}" < "{151}" < "{151}{32}" < "{152}" "{1}" < "{012}" < "{12}{-1}{0}" < "{12.0}{0.2}" "{5}" < "{5}{0}" < "{5}{0}{1}" but if every numbers equals, string compares. character make problem space, must after every other "visible" characters. examples:
"{1}a" < "{1}aa" < "{1} a" < "{1} a" "{1}" < "{1}a " < "{1}a " < "{1}a a" "{1}a" < "{1}ba" < "{1}b " i can make doing somethign in custom class:
class customarray attr_accessor :one attr_accessor :two attr_accessor :three attr_accessor :text def <=>(other) if self.one.to_f < other.one.to_f return -1 elsif self.one.to_f > other.one.to_f return 1 elsif self.two.nil? if other.two.nil? min = [self.text, other.text].min = 0 until == min if self.text[i].chr == ' ' #.chr compatibility ruby 1.8.x if other.text[i].chr != ' ' return 1 end else if other.text[i].chr == ' ' return -1 #... self.text <=> other.text end end it works fine, frustrated coding in ruby code in c++ project. why know how use "custom sort in foreach method" more complexe sort way (require parsing, scanning, regexp) naive 1 based on attribute of content.
[edit: initial solution, follows edit, not work revised statement of question. leave it, however, might of interest regardless.
the following way perform sort under revised rules, understand them. if have misinterpreted rules, expect fix minor.
regex use
let's start regex i'll use:
r = / \{ # match char ( # begin capture group \d+ # match 1 or more digits (?: # begin non-capture group \. # match decimal \d+ # match 1 or more digits ) # end non-capture group | # or \d* # match 0 or more digits ) # match end capture group \} # match char /x examples:
a = ["{5}something", "{61}{64}could", "{}be", "{54}{31.24}{0.2}write", "{11}{21}{87}{65}here", "[]or", "{31}not", "{31} cat"] a.each_with_object({}) { |s,h| h[s] = s.scan(r).flatten } # => {"{5}something" =>["5"], # "{61}{64}could" =>["61", "64"], # "{}be" =>[""], # "{54}{31.24}{0.2}write"=>["54", "31.24", "0.2"], # "{11}{21}{87}{65}here" =>["11", "21", "87", "65"], # "[]or" =>[], # "{31}not" =>["31"] # "{31} cat" =>["31"]} custom_sort method
we can write method custom_sort follows (change sort_by sort_by! custom_sort!):
class array def custom_sort sort_by |s| = s.scan(r).flatten raise syntaxerror, "'#{s}' contains empty braces" if a.any?(&:empty?) raise syntaxerror, "'#{s}' contains 0 or > 3 pair of braces" if a.size.zero?||a.size > 3 a.map(&:to_f) << s[a.join.size+2*a.size..-1].tr(' ', 255.chr) end end end examples
let's try it:
a.custom_sort #=> syntaxerror: '{}be' contains empty braces remove "{}be" a:
a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write", "{11}{21}{87}{65}here", "[]or", "{31}not", "{31} cat"] a.custom_sort #syntaxerror: '{11}{21}{87}{65}here' contains > 3 pair of braces remove "{11}{21}{87}{65}here":
a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write", "[]or", "{31}not", "{31} cat"] a.custom_sort #=> syntaxerror: '[]or' contains 0 or > 3 pair of braces remove "[]or":
a = ["{5}something", "{61}{64}could", "{54}{31.24}{0.2}write", "{31}not", "{31} cat"] a.custom_sort #=> ["{5}something", # "{31}not", # "{31} cat", # "{54}{31.24}{0.2}write", "{61}{64}could"] explanation
suppose 1 of strings sorted was:
s = "{54}{31.24}{0.2}write letter" then in sort_by block, compute:
a = s.scan(r).flatten #=> ["54", "31.24", "0.2"] raise syntaxerror, "..." if a.any?(&:empty?) #=> raise syntaxerror, "..." if false raise syntaxerror, "..." if a.size.zero?||a.size > 3 #=> syntaxerror, "..." if false || false b = a.map(&:to_f) #=> [54.0, 31.24, 0.2] t = a.join #=> "5431.240.2" n = t.size + 2*a.size #=> 16 u = s[n..-1] #=> "wr te" v = u.tr(' ', 255.chr) #=> "wr\xffi\xffte" b << v #=> [54.0, 31.24, 0.2, "wr\xffi\xffte"] note use of string#tr (or use string#gsub) puts spaces @ end of sort order of ascii characters:
255.times.all? { |i| i.chr < 255.chr } #=> true tide]
i have assumed that, in sorting, pairs of strings compared in manner analogous array#<=>. first comparison considers strings of digits within the first pair of braces in each string (after conversion float). ties broken comparing strings of digits in second pairs of braces (converted floats). if there still tie, third pairs digits enclosed in braces compared, etc. if 1 string has n pairs of braces , has m > n pairs, , values within braces same first n pairs, assume first string precede second in sort.
code
r = / \{ # match char (\d+) # capture digits \} # match char + # capture 1 or more times /x class array def custom_sort! sort_by! { |s| s.scan(r).map { |e| e.first.to_f } } end end example
array = ["{109}{08} ok", "{109}{07} ok", "{98} thx", "{108}{0.8}{908} aa", "{108}{0.8}{907} aa", "{8}{51} lorem ipsum"] = array.custom_sort! #=> ["{8}{51} lorem ipsum", # "{98} thx", # "{108}{0.8}{907} aa", # "{108}{0.8}{908} aa", # "{109}{07} ok", # "{109}{08} ok"] array == #=> true explanation
let's calculate value in array#sort_by!'s block first element of array
s = "{109}{08} ok" = s.scan(r) #=> [["109"], ["08"]] b = a.map { |e| e.first.to_f } #=> [109.0, 8.0] let's same other strings , put results in array:
c = array.map { |s| [s, s.scan(r).map { |e| e.first.to_f }] } #=> [["{8}{51} lorem ipsum", [8.0, 51.0]], # ["{98} thx", [98.0]], # ["{108}{0.8}{907} aa", [108.0, 907.0]], # ["{108}{0.8}{908} aa", [108.0, 908.0]], # ["{109}{07} ok", [109.0, 7.0]], # ["{109}{08} ok", [109.0, 8.0]]] sort_by in custom_sort! therefore equivalent to:
c.sort_by(&:last).map(&:first) #=> ["{8}{51} lorem ipsum", # "{98} thx", # "{108}{0.8}{907} aa", # "{108}{0.8}{908} aa", # "{109}{07} ok", # "{109}{08} ok"]
Comments
Post a Comment