views:

56

answers:

3

What is the fastest way to read from STDIN a number of 1000000 characters (integers), and split it into an array of one character integers (not strings) ?

123456 > [1,2,3,4,5,6]
+1  A: 

This should be reasonably fast:

a = []
STDIN.each_char do |c|
  a << c.to_i
end

although some rough benchmarking shows this hackish version is considerably faster:

a = STDIN.bytes.map { |c| c-48 }
Mladen Jablanović
Nice, much faster than the gets method, thanks
astropanic
Nice too, but how do you suppress the -38 on the end ?
astropanic
You can perform `[0...-1]` on the resulting array, but generally, unless you absolutely need it to be as fast as possible, I'd advise you to use some validation, using `select` or `reject`, for example. If the speed is critical, you shouldn't be using Ruby to begin with.
Mladen Jablanović
A: 
scan(/\d/).map(&:to_i)

This will split any string into an array of integers, ignoring any non-numeric characters. If you want to grab user input from STDIN add gets:

gets.scan(/\d/).map(&:to_i)
Teoulas
+3  A: 

The quickest method I have found so far is as follows :-

  gets.unpack("c*").map { |c| c-48}

Here are some results from benchmarking most of the provided solutions. These tests were run with a 100,000 digit file but with 10 reps for each test.


                                  user     system      total        real
each_char_full_array:         1.780000   0.010000   1.790000 (  1.788893)
each_char_empty_array:        1.560000   0.010000   1.570000 (  1.572162)
map_byte:                     0.760000   0.010000   0.770000 (  0.773848)
gets_scan                     2.220000   0.030000   2.250000 (  2.250076)
unpack:                       0.510000   0.020000   0.530000 (  0.529376)

And here is the code that produced them

#!/usr/bin/env ruby

require "benchmark"

MAX_ITERATIONS = 100000
FILE_NAME = "1_million_digits"

def build_test_file
  File.open(FILE_NAME, "w") do |f|
    MAX_ITERATIONS.times {|x| f.syswrite rand(10)}
  end
end

def each_char_empty_array
  STDIN.reopen(FILE_NAME)
  a = []
  STDIN.each_char do |c|
    a << c.to_i
  end
  a
end

def each_char_full_array
  STDIN.reopen(FILE_NAME)
  a = Array.new(MAX_ITERATIONS)
  idx = 0
  STDIN.each_char do |c|
    a[idx] = c.to_i
    idx += 1
  end
  a
end

def map_byte()
  STDIN.reopen(FILE_NAME)
  a = STDIN.bytes.map { |c| c-48 }
  a[-1] == -38 && a.pop
  a
end

def gets_scan
  STDIN.reopen(FILE_NAME)
  gets.scan(/\d/).map(&:to_i)
end


def unpack
  STDIN.reopen(FILE_NAME)
  gets.unpack("c*").map { |c| c-48}
end

reps = 10
build_test_file
Benchmark.bm(10) do |x|
  x.report("each_char_full_array: ") { reps.times {|y| each_char_full_array}}
  x.report("each_char_empty_array:") { reps.times {|y| each_char_empty_array}}
  x.report("map_byte:             ") { reps.times {|y| map_byte}}
  x.report("gets_scan             ") { reps.times {|y| gets_scan}}
  x.report("unpack:               ") { reps.times {|y| unpack}}
end
Steve Weet