Price list behind API: Difference between revisions

Content added Content deleted
m (syntax highlighting fixup automation)
Line 202: Line 202:
From 90121 to 95102 with 4998 items
From 90121 to 95102 with 4998 items
From 95103 to 99999 with 4809 items
From 95103 to 99999 with 4809 items
</pre>

=={{header|jq}}==
'''Works with jq and gojq, the C and Go implementations of jq'''

In this entry, the API is envisioned as the interface between a server and a client,
where the server is modeled by a database together with a program that reads the database and responds to client commands;
and the client is modeled as an indefinitely long stream of API calls of the form specified in the task description.

Our server will respond to both well-formed and ill-formed API calls with JSON messages.

Since jq does not itself include a built-in PRNG, we will model the database as a file of prices generated by a jq program that uses /dev/urandom as a source of entropy, and that is separate from a second jq program (server.jq) that reads the data (once) and responds to API calls.

The driver program (server.jq) is loosely adapted from the [[#Wren|Wren]] entry. It is left as an exercise to sort the prices for more efficient computation of
the get_prange_count() API calls.

===The Server (server.sh)===
<pre>
#!/bin/bash

ApproxNumPrices=100000
maxPrice=100000
JQ=jq

function prices {
< /dev/urandom tr -cd '0-9' | fold -w 1 |
$JQ -Rrnc --argjson ApproxNumPrices $ApproxNumPrices --argjson maxPrice $maxPrice '

# Output: a PRN in range(0;$n) where $n is `.`
def prn:
if . == 1 then 0
else . as $n
| ([1, (($n-1)|tostring|length)]|max) as $w
| [limit($w; inputs)] | join("") | tonumber
| if . < $n then . else ($n | prn) end
end;

# Output: PRN in range($m;$n) if $n > $m
def rand($m; $n):
if $m > $n then "rand\($m);\($n))" | error
else (($n-$m)|prn) + $m
end ;

range(0; $ApproxNumPrices - 50 + rand(0; 100))
| rand(0; 1+$maxPrice)'
}

# API commands:
# get_max_price()
# get_prange_count(pricemin, pricemax)

jq -nrcR --slurpfile prices <(prices) -f server.jq
</pre>
===The Server Program (server.jq)===
<syntaxhighlight lang=jq>
def count(s): reduce s as $_ (0; .+1);

def minDelta: 1;

def getMaxPrice: $prices | max;

def getPrangeCount($min; $max):
count( $prices[] | select($min <= . and . <= $max) ) ;

def get5000($prices; $min; $max; $n):
{ count: getPrangeCount($min; $max),
delta: (($max - $min) / 2),
$max }
| until(.count == $n or .delta < minDelta/2;
.max = (( if .count > $n then .max - .delta else .max + .delta end)|floor)
| .count = getPrangeCount($min; .max)
| .delta /= 2 )
| [.max, .count] ;

def getAll5000($min; $max; $n):
{ mc: get5000($prices; $min; $max; $n)}
| .pmax = .mc[0]
| .pcount = .mc[1]
| .res = [[$min, .pmax, .pcount]]
| until(.pmax >= $max;
.pmin = .pmax + 1
| .mc = get5000($prices; .pmin; $max; $n)
| .pmax = .mc[0]
| .pcount = .mc[1]
| if .pcount == 0 then "Price list from \(.pmin) has too many with same price." | error
else .res += [[.pmin, .pmax, .pcount]]
end )
| .res ;

def trim: sub("^ +";"") | sub(" +$";"");

def display($res; $numPrices; $actualMax; $approxBinSize):
"Using \($numPrices) items with prices from 0 to \($actualMax):",
"Split into \($res|length) bins of approx \($approxBinSize) elements:",
($res[] as [$min, $max, $n]
| (if $max > $actualMax then $actualMax else $max end) as $mx
| " From \($min) to \($mx) with \($n) items" ) ;

def repl(approxBinSize):
($prices|length) as $numPrices
| getMaxPrice as $actualMax
| getAll5000(0; $actualMax; approxBinSize) as $res
| def r:
trim
| . as $line
| if startswith("get_max_price") then {getMaxPrice: getMaxPrice}
else (scan(" *get_prange_count *[(]([0-9]+) *, *([0-9]+) *[)]")
| map(tonumber) as [$min, $max]
| {"getPrangeCount": {$min, $max, count: getPrangeCount($min; $max)}})
// {error: $line}
end;
display($res; $numPrices; $actualMax; approxBinSize),
(inputs | r);

# The approximate bin size
repl(5000)
</syntaxhighlight>
===Sample Client Calls (api.txt)===
<pre>
get_max_price()
junk
get_prange_count(1000, 2000)
</pre>
{{output}}
Invocation: server.sh < api.txt
<pre>
Using 99989 items with prices from 0 to 99999:
Split into 21 bins of approx 5000 elements:
From 0 to 4996 with 5000 items
From 4997 to 9993 with 4999 items
From 9994 to 15031 with 4996 items
From 15032 to 20063 with 4999 items
From 20064 to 24992 with 5000 items
From 24993 to 30001 with 5000 items
From 30002 to 35032 with 4999 items
From 35033 to 40131 with 5000 items
From 40132 to 44960 with 4998 items
From 44961 to 49951 with 4998 items
From 49952 to 54930 with 4997 items
From 54931 to 59996 with 4998 items
From 59997 to 65055 with 5000 items
From 65056 to 69923 with 5000 items
From 69924 to 74834 with 4997 items
From 74835 to 79779 with 4998 items
From 79780 to 84865 with 5000 items
From 84866 to 89811 with 5000 items
From 89812 to 94818 with 5000 items
From 94819 to 99985 with 4998 items
From 99986 to 99999 with 12 items
{"getMaxPrice":99999}
{"getPrangeCount":{"min":0,"max":10,"count":17}}
{"error":"junk"}
{"getPrangeCount":{"min":1000,"max":2000,"count":1006}}
</pre>
</pre>