How to split a string like the following example in ruby? [closed] - ruby
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I want to convert an arbitrary string to an array of strings. The conversion is best explained with an example. If the string were "8737928" I would like to return the following array.
#=> ["8737928",
# "8.737928", "87.37928", "873.7928", "8737.928", "87379.28", "873792.8",
# "8.7.37928", "8.73.7928", "8.737.928", "8.7379.28", "8.73792.8",
# "87.3.7928", "87.37.928", "87.379.28", "87.3792.8", "873.7.928",
# "873.79.28", "873.792.8", "8737.9.28", "8737.92.8", "87379.2.8",
# "8.7.3.7928", "8.7.37.928", "8.7.379.28", "8.7.3792.8", "8.73.7.928",
# "8.73.79.28", "8.73.792.8", "8.737.9.28", "8.737.92.8", "8.7379.2.8",
# "87.3.7.928", "87.3.79.28", "87.3.792.8", "87.37.9.28", "87.37.92.8",
# "87.379.2.8", "873.7.9.28", "873.7.92.8", "873.79.2.8", "8737.9.2.8",
# "8.7.3.7.928", "8.7.3.79.28", "8.7.3.792.8", "8.7.37.9.28", "8.7.37.92.8",
# "8.7.379.2.8", "8.73.7.9.28", "8.73.7.92.8", "8.73.79.2.8", "8.737.9.2.8",
# "87.3.7.9.28", "87.3.7.92.8", "87.3.79.2.8", "87.37.9.2.8", "873.7.9.2.8",
# "8.7.3.7.9.28", "8.7.3.7.92.8", "8.7.3.79.2.8", "8.7.37.9.2.8",
# "8.73.7.9.2.8", "87.3.7.9.2.8",
# "8.7.3.7.9.2.8"]
As you see, between 0 and 6 ("8737928".size-1 #=> 6) decimal points are inserted in the string, at every combination of indices between 1 and 6. Since a decimal point may or may not follow each character other than the last, the array contains 2**6 #=> 64 elements.
How can I do this?
def doit(str)
indices = (1..str.size-1).to_a
indices.each_with_object([str]) { |ndots, combos|
indices.combination(ndots).each { |sub| combos << dotify(str, sub) } }
end
def dotify(str, indices)
indices.reverse.each_with_object(str.dup) { |i,s| s.insert(i,'.') }
end
doit("8737928").size
#=> 64
doit "8737928"
#=> ["8737928",
# "8.737928", "87.37928", "873.7928", "8737.928", "87379.28", "873792.8",
# "8.7.37928", "8.73.7928", "8.737.928", "8.7379.28", "8.73792.8",
# "87.3.7928", "87.37.928", "87.379.28", "87.3792.8", "873.7.928",
# "873.79.28", "873.792.8", "8737.9.28", "8737.92.8", "87379.2.8",
# "8.7.3.7928", "8.7.37.928", "8.7.379.28", "8.7.3792.8", "8.73.7.928",
# "8.73.79.28", "8.73.792.8", "8.737.9.28", "8.737.92.8", "8.7379.2.8",
# "87.3.7.928", "87.3.79.28", "87.3.792.8", "87.37.9.28", "87.37.92.8",
# "87.379.2.8", "873.7.9.28", "873.7.92.8", "873.79.2.8", "8737.9.2.8",
# "8.7.3.7.928", "8.7.3.79.28", "8.7.3.792.8", "8.7.37.9.28", "8.7.37.92.8",
# "8.7.379.2.8", "8.73.7.9.28", "8.73.7.92.8", "8.73.79.2.8", "8.737.9.2.8",
# "87.3.7.9.28", "87.3.7.92.8", "87.3.79.2.8", "87.37.9.2.8", "873.7.9.2.8",
# "8.7.3.7.9.28", "8.7.3.7.92.8", "8.7.3.79.2.8", "8.7.37.9.2.8",
# "8.73.7.9.2.8", "87.3.7.9.2.8",
# "8.7.3.7.9.2.8"]
Note:
dotify("8737928", [1,3,5])
#=> "8.73.79.28"
Solution 1
Upon further reflection (see original, probably incorrect solution below), it seems like what OP really wants to do is insert dots at every possible combination of positions in the string. Here's a method that does literally that:
def splits(str, prefix="")
c = str.size - 1
(0..c).flat_map do |m|
(0...c).to_a.combination(m).map do |n|
n.each_with_object(str.dup) {|i,s| s.insert(c-i, ?.) }
end
end
end
puts splits("8737928")
# => 8737928
# 873792.8
# 87379.28
# 87379.2.8
# 8737.928
# 8737.92.8
# 8737.9.28
# 8737.9.2.8
# 873.7928
# 873.792.8
# 873.79.28
# 873.79.2.8
# 873.7.928
# 873.7.92.8
# 873.7.9.28
# 873.7.9.2.8
# 87.37928
# 87.3792.8
# 87.379.28
# 87.379.2.8
# 87.37.928
# 87.37.92.8
# 87.37.9.28
# 87.37.9.2.8
# 87.3.7928
# 87.3.792.8
# 87.3.79.28
# 87.3.79.2.8
# 87.3.7.928
# 87.3.7.92.8
# 87.3.7.9.28
# 87.3.7.9.2.8
# 8.737928
# 8.73792.8
# 8.7379.28
# 8.7379.2.8
# 8.737.928
# 8.737.92.8
# 8.737.9.28
# 8.737.9.2.8
# 8.73.7928
# 8.73.792.8
# 8.73.79.28
# 8.73.79.2.8
# 8.73.7.928
# 8.73.7.92.8
# 8.73.7.9.28
# 8.73.7.9.2.8
# 8.7.37928
# 8.7.3792.8
# 8.7.379.28
# 8.7.379.2.8
# 8.7.37.928
# 8.7.37.92.8
# 8.7.37.9.28
# 8.7.37.9.2.8
# 8.7.3.7928
# 8.7.3.792.8
# 8.7.3.79.28
# 8.7.3.79.2.8
# 8.7.3.7.928
# 8.7.3.7.92.8
# 8.7.3.7.9.28
# 8.7.3.7.9.2.8
Solution 2
However, while #EliSadoff's solution wasn't generalized, I did like his "idea that each spot a period can be is a boolean decision." If we think of the positions in the string at which we could insert a period as bits in a binary number m with the same (base 2) length as the string less one, we can simply iterate from 0 to 2(c-1)-1 (where c is the length of the string) to get every possible such number. For example, if our string is "abcd" (c = 4), then we can iterate from 0 to 7 (2(4-1)-1) to find the positions of each period:
m₁₀ | m₂ 4 2 1 | 4 2 1 | result
─────┼────┴─┴─┴─┼───┴───┴───┴───┼─────────
0 │ 0 0 0 │ a b c d │ abcd
1 │ 0 0 1 │ a b c • d | abc.d
2 │ 0 1 0 │ a b • c d | ab.cd
3 │ 0 1 1 │ a b • c • d | ab.c.d
4 │ 1 0 0 │ a • b c d | a.bcd
5 │ 1 0 1 │ a • b c • d | a.bc.d
6 │ 1 1 0 │ a • b • c d | a.b.cd
7 │ 1 1 1 │ a • b • c • d | a.b.c.d
The only missing piece is inserting periods based on the bits in the second column. That's pretty easy: To figure out if we need to insert a period at position n, we test if the nth bit in m is 1. To do that we can use the bitwise operation m & (1 ≪ n).
Put it all together and we get the following:
def splits2(str)
c = str.size - 1
(0...2**c).map do |m|
0.upto(c).with_object(str.dup) do |i,s|
s.insert(c-i, ?.) if m & (1 << i) > 0
end
end
end
Solution 3
Just for fun, here's another solution that also uses the binary number approach, but in a different way. I'll leave it as an exercise to the reader to figure out how it works:
def splits3(str)
c = str.size - 1
(0...2**c).map do |m|
dots = ("%*b" % [c,m]).each_char.map(&{?1=>?.})
str.each_char.zip(dots).join
end
end
Original solution
Similar to #CarySwoveland's solution but, I think, a bit simpler:
def splits(str, pfx="")
return [] if str.empty?
(1...str.size).map {|i| pfx + str.dup.insert(i, ?.) } +
splits(str[1..-1], "#{pfx}#{str[0]}.")
end
p splits("8737928")
# => [ "8.737928", "87.37928", "873.7928", "8737.928", "87379.28", "873792.8",
# "8.7.37928", "8.73.7928", "8.737.928", "8.7379.28", "8.73792.8",
# "8.7.3.7928", "8.7.37.928", "8.7.379.28", "8.7.3792.8",
# "8.7.3.7.928", "8.7.3.79.28", "8.7.3.792.8",
# "8.7.3.7.9.28", "8.7.3.7.92.8",
# "8.7.3.7.9.2.8"
# ]
The requirements are unclear, and I came up with a result that differs from what both Cary and Jordan have:
def dot_it(prefix, suffix = nil)
return dot_it(prefix[0], prefix[1..-1]) if suffix.nil? # first call
(1...suffix.length).flat_map do |i|
sp, ss = "#{prefix}.#{suffix[0...i]}", suffix[i..-1]
["#{sp}.#{ss}", dot_it(sp, ss)].flatten.compact
end
end
dot_it("8737928")
#⇒ ["8.7.37928", "8.7.3.7928", "8.7.3.7.928", "8.7.3.7.9.28",
# "8.7.3.7.9.2.8", "8.7.3.7.92.8", "8.7.3.79.28", "8.7.3.79.2.8",
# "8.7.3.792.8", "8.7.37.928", "8.7.37.9.28", "8.7.37.9.2.8",
# "8.7.37.92.8", "8.7.379.28", "8.7.379.2.8", "8.7.3792.8",
# "8.73.7928", "8.73.7.928", "8.73.7.9.28", "8.73.7.9.2.8",
# "8.73.7.92.8", "8.73.79.28", "8.73.79.2.8", "8.73.792.8",
# "8.737.928", "8.737.9.28", "8.737.9.2.8", "8.737.92.8",
# "8.7379.28", "8.7379.2.8", "8.73792.8"]
My method gives:
dot_it("8737928").count
#⇒ 31
while both answers above give 21 results. Who is right?
Related
How to format Phone string in Ruby with hyphens?
I have a problem I can't solve. I need to write phone_format method that would accept any phone string and output it in groups of 3 digits with hyphens phone_format("555 123 1234") => "555-123-12-34" phone_format("(+1) 888 33x19") => "188-833-19" But if it ends with single digit like 999-9, change it to 99-99. Ideally it would be a one liner
R = / \d{2,3} # match 2 or 3 digits (greedily) (?= # begin positive lookahead \d{2,3} # match 2 or 3 digits | # or \z # match the end of the string ) # end positive lookahead /x # free-spacing regex definition mode Conventionally written R = /\d{2,3}(?=\d{2,3}|\z)/ def doit(str) s = str.gsub(/\D/,'') return s if s.size < 4 s.scan(R).join('-') end doit "555 123 123" #=> "555-123-123" doit "555 123 1234" #=> "555-123-12-34" doit "555 123 12345" #=> "555-123-123-45" doit "(+1) 888 33x19" #=> "188-833-19" doit "123" #=> "123" doit "1234" #=> "12-34"
Not really a one-liner: you need to handle the special cases. def cut_by(str, cut) str.each_char.each_slice(cut).map(&:join).join('-') end def phone_format(str) str = str.gsub(/\D/, '') # cleanup if str.size == 4 # special case 1 cut_by(str, 2) elsif str.size % 3 == 1 # special case 2 cut_by(str[0..-5], 3) + "-" + cut_by(str[-4..], 2) else # normal case cut_by(str, 3) end end
How to (easily) split string in Ruby with length and also delimiter
I need to split a string in Ruby which has the following format: [{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},{a:9,b:10,c:11,d:12},{a:13,b:14,c:15,d:16}] ie. it is a generated javascript array. Unfortunately the list is long and I would like to split it up on the array element separator comma after reaching a specific length suitable for editing it with a code editor, but in a way to keep the integrity of the elements. For example, the line above with a split width of 15 would become: [{a:1,b:2,c:3,d:4}, {a:5,b:6,c:7,d:8}, {a:9,b:10,c:11,d:12}, {a:13,b:14,c:15,d:16}] and with a width of 32 the text would be: [{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8}, {a:9,b:10,c:11,d:12},{a:13,b:14,c:15,d:16}] Beside of the classical "brute force" approach (loop through, check separator between } and { while increasing length, split if length greater than and valid separator found) is there a more "Rubyish" solution to the problem? Edit: Naive approach attached, definitely not Rubyiish as I don't have a very strong Ruby background: def split(what, length) result = [] clength = 0 flag = FALSE what_copy = what.to_s what_copy.to_s.each_char do |c| clength += 1 if clength > length flag = TRUE end if c == '}' and flag result << what[0 .. clength] what = what[clength+1 .. -1] clength = 0 flag = FALSE end end pp result sres = result.join("\n") sres end
You could use a regex with : a non-greedy repetition of at least width-2 characters followed by a } followed by a , or a ]. data = "[{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},{a:9,b:10,c:11,d:12},{a:13,b:14,c:15,d:16}]" def split_data_with_min_width(text, width) pattern = / ( # capturing group for split .{#{width-2},}? # at least width-2 characters, but not more than needed \} # closing curly brace [,\]] # a comma or a closing bracket ) /x # free spacing mode text.split(pattern).reject(&:empty?).join("\n") end puts split_data_with_min_width(data, 15) # [{a:1,b:2,c:3,d:4}, # {a:5,b:6,c:7,d:8}, # {a:9,b:10,c:11,d:12}, # {a:13,b:14,c:15,d:16}, # {a:17,b:18,c:19,d:20}] puts split_data_with_min_width(data, 32) # [{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8}, # {a:9,b:10,c:11,d:12},{a:13,b:14,c:15,d:16}, # {a:17,b:18,c:19,d:20}] The method uses split with a capturing group instead of scan because the last part of the string might not be long enough: "abcde".scan(/../) # ["ab", "cd"] "abcde".split(/(..)/).reject(&:empty?) # ["ab", "cd", "e"]
Code def doit(str, min_size) r = / (?: # begin non-capture group .{#{min_size},}? # match at least min_size characters, non-greedily (?=\{) # match '{' in a positive lookahead | # or .+\z # match one or more characters followed by end of string ) # close non-capture group /x # free-spacing regex definition mode str.scan(r) end Examples str = "[{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},{a:9,b:10,c:11,d:12},{a:13,b:14,c:15,d:16}]" doit(str, 18) # same for all min_size <= 18 #=> ["[{a:1,b:2,c:3,d:4},", # "{a:5,b:6,c:7,d:8},", # "{a:9,b:10,c:11,d:12},", # "{a:13,b:14,c:15,d:16}]"] doit(str, 19) #=> ["[{a:1,b:2,c:3,d:4},", # "{a:5,b:6,c:7,d:8},{a:9,b:10,c:11,d:12},", # "{a:13,b:14,c:15,d:16}]"] doit(str, 20) #=> ["[{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},", # "{a:9,b:10,c:11,d:12},", # "{a:13,b:14,c:15,d:16}]"] doit(str, 21) #=> ["[{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},", # "{a:9,b:10,c:11,d:12},", # "{a:13,b:14,c:15,d:16}]"] doit(str, 22) # same for 23 <= min_size <= 37 #=> ["[{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},", # "{a:9,b:10,c:11,d:12},{a:13,b:14,c:15,d:16}]"] doit(str, 38) # same for 39 <= min_size <= 58 #=> ["[{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},{a:9,b:10,c:11,d:12},", # "{a:13,b:14,c:15,d:16}]"] doit(str, 59) # same for min_size > 59 #=> ["[{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},{a:9,b:10,c:11,d:12},{a:13,b:14,c:15,d:16}]"]
Like this? 2.3.1 :007 > a => "[{a:1,b:2,c:3,d:4},{a:5,b:6,c:7,d:8},{a:9,b:10,c:11,d:12},{a:13,b:14,c:15,d:16}]" 2.3.1 :008 > q = a.gsub("},", "},\n") => "[{a:1,b:2,c:3,d:4},\n{a:5,b:6,c:7,d:8},\n{a:9,b:10,c:11,d:12},\n{a:13,b:14,c:15,d:16}]" 2.3.1 :009 > puts q [{a:1,b:2,c:3,d:4}, {a:5,b:6,c:7,d:8}, {a:9,b:10,c:11,d:12}, {a:13,b:14,c:15,d:16}] => nil 2.3.1 :010 >
Static variables in Ruby to make "one up counter function"?
I would like to make a function so each time it is called it return a value that is incremented by 1 starting from 1. I guess it could be made with a global variable, but not a pretty solution.
I'd probably do it this way: class EverreadyBunnyCounter def initialize #counter = 0 end def current #counter end def next #counter += 1 end end foo = EverreadyBunnyCounter.new bar = EverreadyBunnyCounter.new foo.next # => 1 bar.next # => 1 foo.next # => 2 bar.current # => 1 The current method isn't necessary but it's sometimes convenient to be able to peek at the current value without forcing it to increment. Alternately, this might do it: MAX_INT = (2**(0.size * 8 -2) -1) counter = (1..MAX_INT).to_enum # => #<Enumerator: 1..4611686018427387903:each> foo = counter.dup bar = counter.dup foo.next # => 1 foo.next # => 2 bar.next # => 1 bar.next # => 2 foo.next # => 3 Defining MAX_INT this way comes from "Ruby max integer". The downside is that you'll eventually run out of values because of the range being used to create the Enumerator, where the previous version using the EverreadyBunnyCounter class will keep on going. Changing MAX_INT to Float::INFINITY would be a way to fix that: counter = (1..Float::INFINITY).to_enum # => #<Enumerator: 1..Infinity:each> foo = counter.dup bar = counter.dup foo.next # => 1 foo.next # => 2 bar.next # => 1 bar.next # => 2 foo.next # => 3 The Enumerator documentation has more information.
Something like this? def incr #n ||= 0 #n += 1 end incr #=> 1 incr #=> 2
how to separate this text into a hash ruby
sorry my bad english, im new i have this document.txt paul gordon,jin kazama,1277,1268,21-12,21-19 yoshimistu,the rock,2020,2092,21-9,21-23,25-27 ... lot more i mean, how to strip each line, and comma sparator, into a hash like this result = { line_num: { name1: "paula wood", name2: "sarah carnley", m1: 1277, m2: 1268, sc1: 21, sc2: 12, sc3: 21, sc4: 19 } } i try to code like this im using text2re for regex here doc = File.read("doc.txt") lines = doc.split("\n") counts = 0 example = {} player1 = '((?:[a-z][a-z]+))(.)((?:[a-z][a-z]+))' player2 = '((?:[a-z][a-z]+))(.)((?:[a-z][a-z]+))' re = (player1 + player2 ) m = Regexp.new(re, Regexp::IGNORECASE) lines.each do |line| re1='((?:[a-z][a-z]+))' # Word 1 re2='(.)' # Any Single Character 1 re3='((?:[a-z][a-z]+))' # Word 2 re4='(.)' # Any Single Character 2 re5='((?:[a-z][a-z]+))' # Word 3 re6='(.)' # Any Single Character 3 re7='((?:[a-z][a-z]+))' # Word 4 re=(re1+re2+re3+re4+re5+re6+re7) m=Regexp.new(re,Regexp::IGNORECASE); if m.match(line) word1=m.match(line)[1]; c1=m.match(line)[2]; word2=m.match(line)[3]; c2=m.match(line)[4]; word3=m.match(line)[5]; c3=m.match(line)[6]; word4=m.match(line)[7]; counts += 1 example[counts] = word1+word2 puts example end end # (/[a-z].?/) but the output does not match my expectation 1=>"", 2=>"indahdelika", 3=>"masam", ..more
Your data is comma-separated, so use the CSV class instead of trying to roll your own parser. There are dragons waiting for you if you try to split simply using commas. I'd use: require 'csv' data = "paul gordon,jin kazama,1277,1268,21-12,21-19 yoshimistu,the rock,2020,2092,21-9,21-23,25-27 " hash = {} CSV.parse(data).each_with_index do |row, i| name1, name2, m1, m2, sc1_2, sc3_4 = row sc1, sc2 = sc1_2.split('-') sc3, sc4 = sc3_4.split('-') hash[i] = { name1: name1, name2: name2, m1: m1, m2: m2, sc1: sc1, sc2: sc2, sc3: sc3, sc4: sc4, } end Which results in: hash # => {0=> # {:name1=>"paul gordon", # :name2=>"jin kazama", # :m1=>"1277", # :m2=>"1268", # :sc1=>"21", # :sc2=>"12", # :sc3=>"21", # :sc4=>"19"}, # 1=> # {:name1=>"yoshimistu", # :name2=>"the rock", # :m1=>"2020", # :m2=>"2092", # :sc1=>"21", # :sc2=>"9", # :sc3=>"21", # :sc4=>"23"}} Since you're reading from a file, modify the above a bit using the "Reading from a file a line at a time" example in the documentation. If the numerics need to be integers, tweak the hash definition to: hash[i] = { name1: name1, name2: name2, m1: m1.to_i, m2: m2.to_i, sc1: sc1.to_i, sc2: sc2.to_i, sc3: sc3.to_i, sc4: sc4.to_i, } Which results in: # => {0=> # {:name1=>"paul gordon", # :name2=>"jin kazama", # :m1=>1277, # :m2=>1268, # :sc1=>21, # :sc2=>12, # :sc3=>21, # :sc4=>19}, # 1=> # {:name1=>"yoshimistu", # :name2=>"the rock", # :m1=>2020, # :m2=>2092, # :sc1=>21, # :sc2=>9, # :sc3=>21, # :sc4=>23}} # :sc4=>"23"}}
This is another way you could do it. I have made no assumptions about the number of items per line which are to be the values of :namex, :scx or :mx, or the order of those items. Code def hashify(str) str.lines.each_with_index.with_object({}) { |(s,i),h| h[i] = inner_hash(s) } end def inner_hash(s) n = m = sc = 0 s.split(',').each_with_object({}) do |f,g| case f when /[a-zA-Z].*/ g["name#{n += 1}".to_sym] = f when /\-/ g["sc#{sc += 1}".to_sym], g["sc#{sc += 1}".to_sym] = f.split('-').map(&:to_i) else g["m#{m += 1}".to_sym] = f.to_i end end end Example str = "paul gordon,jin kazama,1277,1268,21-12,21-19 yoshimistu,the rock,2020,2092,21-9,21-23,25-27" hashify(str) #=> {0=>{:name1=>"paul gordon", :name2=>"jin kazama", # :m1=>1277, :m2=>1268, # :sc1=>21, :sc2=>12, :sc3=>21, :sc4=>19}, # 1=>{:name1=>"yoshimistu", :name2=>"the rock", # :m1=>2020, :m2=>2092, # :sc1=>21, :sc2=>9, :sc3=>21, :sc4=>23, :sc5=>25, :sc6=>27} # }
How to crawl other hash in other hash loop
I have 2 hash in my Ruby code. I wanna get data of "d" hash in "c" loop. c = {"2"=>"20", "3"=>"30"} d = {"2"=>"Du", "3"=>"Bist"} c.each_with_index do |me,index| puts me end output is: 2 20 3 30 I wanna get this output instead: Du Bist
Do as below : c = {"2"=>"20", "3"=>"30"} d = {"2"=>"Du", "3"=>"Bist"} c.each_with_index do |(k,v),i| puts "#{d[k]} at index #{i}" end # >> Du at index 0 # >> Bist at index 1 # I don't know why you want each_with_index, instead of each here # But here is how you can do. c.each_with_index do |(k,v),i| puts d[k] end # >> Du # >> Bist c and d are Hash.c.each_with_index do |me,index| here, with each itration me first has the value as ["2","20"], then ["3","30"]. Thus puts me printing it as 2 20 3 30. You need to have a look at Hash#[].