Price Ranges (#164)

Apologies for the late quiz… been rather busy today. Here’s another
simple
one, but practical. I didn’t get specific about input
formats/parameters/etc, I leave that to you this week.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Q. 2:

  1. Please do not post any solutions or spoiler discussion for this
    quiz until 48 hours have passed from the time on this message.

  2. Support Ruby Q. 2 by submitting ideas as often as you can! (A
    permanent, new website is in the works for Ruby Q. 2. Until then,
    please visit the temporary website at

    <http://splatbang.com/rubyquiz/http://matthew.moss.googlepages.com/home>.

  3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby T. follow the discussion. Please reply to
the original quiz message, if you can.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Price Ranges

Quiz description by James Edward G. II

You have a list of price ranges from different vendors like:

Company A:  $1,000 - $3,000
Company B:  $6,000 - $10,000
Company C:  $500 - $2,500

Given such a list and the desired price range a shopper wishes to pay,
return the companies the shopper should examine. For example, if the
shopper’s price range was:

Low:   $2,500
High:  $5,000

the companies returned would be:

Company A
Company C

The shopper should also be allowed to provide just a low or just a high
value instead of both, should they prefer to do so.

  1. Support Ruby Q. 2 by submitting ideas as often as you can! (A
    permanent, new website is in the works for Ruby Q. 2. Until then,
    please visit the temporary website at

Why does typing a simple email have to be so difficult? sheesh The
correct, unmanged web address is:

http://splatbang.com/rubyquiz/

Why does typing a simple email have to be so difficult? sheesh The
correct, unmanged web address is:

Unmanged?

smack head

I swear, my I.Q. must be dropping 10 points daily.

On Sat, May 31, 2008 at 3:50 AM, Matthew M. [email protected]
wrote:

Why does typing a simple email have to be so difficult? sheesh The
correct, unmanged web address is:

Unmanged?

smack head

I swear, my I.Q. must be dropping 10 points daily.

Cheer up Matt I am trying to put my gmail into TWI*M mode for years
now and I cannot figure out how. Hugh.
R.


http://ruby-smalltalk.blogspot.com/


Whereof one cannot speak, thereof one must be silent.
Ludwig Wittgenstein

On May 30, 6:27 pm, Matthew M. [email protected] wrote:

  1. Please do not post any solutions or spoiler discussion for thisquizuntil 48 hours have passed from the time on this message.
    helps everyone on Ruby T. follow the discussion. Please reply to
    Company B: $6,000 - $10,000

    Company A
    Company C

The shopper should also be allowed to provide just a low or just a high
value instead of both, should they prefer to do so.

You weren’t looking for a user interface, were you? :wink:

http://pastie.textmate.org/207034

Chris

My simple solution:

http://pastie.caboo.se/207066

  • donald

Matthew M. ha scritto:

quiz until 48 hours have passed from the time on this message.
Suggestion: A [QUIZ] in the subject of emails about the problem
Company A: $1,000 - $3,000
the companies returned would be:

Company A
Company C

The shopper should also be allowed to provide just a low or just a high
value instead of both, should they prefer to do so.

Here my solution (with specs):

http://pastie.caboo.se/207093
http://pastie.caboo.se/207095

Bye.
Andrea

Matthew M. wrote:

Price Ranges

Here it is:
http://pastebin.ca/1036722

I had not created a SAX listener before in Ruby. So, my solution reads
the vendor list in from XML. I noticed others were using this pastie
thingy… I don’t know what that is. Besides, pasties always seemed to
be kind of pointless to me. They leave precious little to the
imagination. So, below is the ruby file followed by the xml file:


#!/usr/local/bin/ruby -w
require ‘rexml/parsers/sax2parser’
require ‘rexml/sax2listener’

class VendorListener
include REXML::SAX2Listener

def initialize(low, high)
begin
@low = Float(low)
rescue
@low = 0
end

begin
  @high = Float(high)
rescue
  #if someone can spend more than this then
  #they can afford a better program
  @high = 10**15
end

end

def start_element(uri, localname, tag_name, attrs)
if tag_name == ‘Vendor’
@vendorName = attrs[‘name’]
end
end

def end_element(uri, localname, tag_name)
if tag_name == ‘Vendor’
if in_range?
puts @vendorName
end
elsif tag_name == ‘LowPrice’
@lowPrice = Float(@data)
elsif tag_name == ‘HighPrice’
@highPrice = Float(@data)
end
end

def characters(value)
@data = value
end

def in_range?
(@low >= @lowPrice and @low <= @highPrice) or
(@high >= @lowPrice and @high <= @highPrice) or
(@low < @lowPrice and @high > @highPrice)
end
end

puts “Enter a price range.”
print "Enter low value: "
low = gets
print "Enter high value: "
high = gets

parser = REXML::Parsers::SAX2Parser.new(File.new(‘vendors.xml’ ))
parser.listen(VendorListener.new(low, high))
parser.parse

puts “Program terminated.”

<?xml version="1.0" encoding="UTF-8"?> 1000 3000 6000 10000 500 2500

On Sat, May 31, 2008 at 2:27 AM, Matthew M. [email protected]
wrote:

Given such a list and the desired price range a shopper wishes to pay,

The shopper should also be allowed to provide just a low or just a high
value instead of both, should they prefer to do so.

Here’s my solution. I use codeforpeople’s main, so the program accepts
a --help that explains the params. I expected a file in CSV format for
the company data:

company1, low, high
company2, low, high

and the shopper’s low and high are passed as options to the program:

require ‘main’
require ‘fastercsv’

Main {
argument(‘file’)

option(‘low’, ‘l’) {
argument_required
cast :int
default 0
}

option(‘high’, ‘g’) {
argument_required
cast :int
}

def run
file = params[:file].value
low = params[:low].value
have_high = params[:high].given?
high = params[:high].value if have_high
values = []
FasterCSV.foreach file do |line|
cmp_values = [line[0]]
cmp_values << line[1].to_i
cmp_values << line[2].to_i
values << cmp_values
end

unless have_high
  companies = values.select {|x| x[2] >= low}
else
  companies = values.reject {|x| (x[1]  > high) || (x[2] < low)}
end
companies.map! {|x| x[0]}
puts companies

end
}

For example for this company_data.csv file:

company1, 1000, 3000
company2, 6000, 10000
company3, 500, 2500

these are some runs:

$ ruby quiz164.rb company_data.csv
company1
company2
company3

$ ruby quiz164.rb company_data.csv -l 4000
company2

$ ruby quiz164.rb company_data.csv -l 4000 -g 5000

$ ruby quiz164.rb company_data.csv -g 1500
company1
company3

$ ruby quiz164.rb company_data.csv -g 750
company3

$ ruby quiz164.rb --help
NAME
quiz164.rb

SYNOPSIS
quiz164.rb file [options]+

PARAMETERS
file (1 → file)
–low=low, -l (0 ~> int(low=0))
–high=high, -g (0 ~> int(high))
–help, -h

Thanks for the quiz.

Jesus.

A simple solution to my first Ruby Q.

class SupplierFinder
attr_accessor :companies
def initialize(suppliers=’’)
@companies = Hash.new
File.open(suppliers).each do |line|
range = line.scan(/$[\d,]+/)
companies.store( Range::new(parse(range[0]),parse(range[1])),
line.match(/^[\w\s]*/))
end
end
def search(switch=nil, low=0, high=0)
my_range = Range::new(low, high) if switch == ‘-r’
companies.each_pair do |range, name|
puts name if ( !my_range.nil? and (my_range.include? range.first
or my_range.include? range.last or range.include? low or
range.include? high )) or (switch == ‘-h’ and range.first <= low) or
(switch == ‘-l’ and range.last >= low)
end
end
private
def parse(price)
price.gsub( ‘$’, ‘’).gsub(’,’,’’).to_i
end
end
SupplierFinder.new(ARGV[0]).search(ARGV[1], ARGV[2].to_i,
ARGV[3].to_i) if FILE == $0

Cheers,

Toby.

=begin

Here is my solution.
It accepts input like this:
2500_5000 #lower_upper
2500_ #lower only
_5000 #upper only

=end

min,max = ARGV[0].split(/_/)
a,b,c = (1000…3000).to_a,(6000…10000).to_a,(500…2500).to_a
l,u = min.to_i,max.to_i
u = [a,b,c].flatten.max if max == nil
l = 0 if min == “”
cust = (l…u).to_a
p “a” unless (cust & a).empty?
p “b” unless (cust & b).empty?
p “c” unless (cust & c).empty?

Harry

I hadn’t planned on making another submission, but after tinkering with
it a bit I realized a few things:

  1. A simple GUI would be nice. I used FXRuby.
  2. The SAX Listener was barfing when the number of vendors was six or
    more. Funny thing was that it only had a problem when the XML was
    formatted. If I removed the whitespace it did just fine. In any case,
    I switched to using a StreamListener instead.
  3. There was an inconsistency in the logic in my original in_range?
    method.

Anyway, here is the Ruby code followed by sample XML:

#!/usr/local/bin/ruby -w
require ‘rexml/document’
require ‘rexml/streamlistener’
require ‘fox16’
include Fox

class VendorListener
include REXML::StreamListener
def initialize(low_price, high_price, vendor_search_window)
begin
@low_price = Float(low_price)
rescue
@low_price = 0
end

begin
  @high_price = Float(high_price)
rescue
  #if someone can spend more than this then
  #she or he can afford a better program
  @high_price = 10**15
end

@vdr_srch_wdw = vendor_search_window

end

def tag_start(name, attrs)
if name == ‘Vendor’
@vendor_name = attrs[‘name’]
end
end

def tag_end(name)
if name == ‘Vendor’ and
if in_range?
@vdr_srch_wdw.add_vendor(@vendor_name)
end
elsif name == ‘LowPrice’
@vendor_low_price = Float(@data)
elsif name == ‘HighPrice’
@vendor_high_price = Float(@data)
end
end

def text(text)
@data = text
end

def in_range?
@low_price <= @high_price and
(@low_price >= @vendor_low_price or @high_price >=
@vendor_low_price) and
(@low_price <= @vendor_high_price or @high_price <=
@vendor_high_price)
end
end

class VendorSearchWindow < FXMainWindow
def initialize(app)
# Invoke base class initialize first
super(app, “Ruby Q. #164: Vendor Search”, nil, nil, DECOR_TITLE |
DECOR_CLOSE)

#Add text field frame at the top
textfields = FXHorizontalFrame.new(self, 

LAYOUT_SIDE_TOP|LAYOUT_CENTER_X)
FXLabel.new(textfields, “Enter a range:”, nil, JUSTIFY_LEFT)
FXLabel.new(textfields, “low:”, nil, JUSTIFY_RIGHT)
@low_field = FXTextField.new(textfields, 10, nil, 0,
JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK|LAYOUT_SIDE_TOP)
FXLabel.new(textfields, “high:”, nil, JUSTIFY_RIGHT)
@high_field = FXTextField.new(textfields, 10, nil, 0,
JUSTIFY_RIGHT|FRAME_SUNKEN|FRAME_THICK|LAYOUT_SIDE_TOP)

#add button frame at the bottom
buttons = FXHorizontalFrame.new(self, 

LAYOUT_SIDE_BOTTOM|LAYOUT_CENTER_X|PACK_UNIFORM_WIDTH)
show_button = FXButton.new(buttons, “Show Vendors”)
show_button.connect(SEL_COMMAND, method(:on_show_vendors))
exit_button = FXButton.new(buttons, “Exit”)
exit_button.connect(SEL_COMMAND, method(:on_exit))

#Place the list in a sunken frame
sunken_frame = FXHorizontalFrame.new(self,
        LAYOUT_FILL_X|LAYOUT_FILL_Y|FRAME_SUNKEN|FRAME_THICK, 

:padding => 0)
@vendor_list = FXList.new(sunken_frame, :opts =>
LIST_SINGLESELECT|LAYOUT_FILL_X|LAYOUT_FILL_Y)
end

def on_exit(sender, sel, ptr)
getApp().exit
end

def on_show_vendors(sender, sel, ptr)
@vendor_list.clearItems(false)
REXML::Document.parse_stream(File.new(‘vendors.xml’ ),
VendorListener.new(@low_field.text, @high_field.text, self))
end

def add_vendor(vndr_name)
@vendor_list.appendItem(vndr_name)
end

def create
super
show(PLACEMENT_SCREEN)
end
end

application = FXApp.new
VendorSearchWindow.new(application)
application.create
application.run

<?xml version="1.0" encoding="UTF-8"?> 1000 3000 6000 10000 500 2500

Here’s my quick attempt

Cheers,
Dave

def select_shops shops, wanted_min, wanted_max
shops.select do |shop|
(wanted_max ? wanted_max>= shop[:prices].min : true) &&
(wanted_min ? wanted_min<= shop[:prices].max : true)
end
end

shops= [{:name=> ‘Company A’, :prices=> 1_000… 3_000},
{:name=> ‘Company B’, :prices=> 6_000…10_000},
{:name=> ‘Company C’, :prices=> 500… 2_500}]

select_shops(shops, 2_500, 4_000).each do |shop|
puts shop[:name]
end

Sorry for the double post.
Short, boring reason why :slight_smile:

I assumed that input had to be taken in the way outlined in the
challenge.
So most of my code is about parsing that to create the required ranges.

=========================

class String
def better_to_f
str = match(/\D*([0-9,.]+)/)[1]
str.gsub!(’,’,’_’)
str.to_f
end
end

$vendors = <<-STRING
Company A: $1,000 - $3,000
Company B: $6,000 - $10,000
Company C: $500 - $2,500
STRING

vendor_regexp = /(\w.\w):\s$([0-9,.]+) - $([0-9,.]+)/

$shopper = <<-STRING
Low: $3,500
High: $7,000
STRING

require ‘yaml’
parsed = YAML.parse($shopper)
low = parsed[‘Low’] && parsed[‘Low’].value.better_to_f
high = parsed[‘High’] && parsed[‘High’].value.better_to_f

vendors = $vendors.scan(vendor_regexp)
result = []
vendors.each do |name, min, max|
min = min.better_to_f
max = max.better_to_f

if low && low > max
next
elsif high && high < min
next
else
result << name
end
end