witheve / Eve

Better tools for thought

Home Page:http://witheve.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Subset of array

chadrien opened this issue · comments

Hi, sorry if the question has already been asked, I tried to search through the issues but could not find it.

I have these 2 blocks in a small app I'm writing to test eve:

commit @http
  [ #request #fetch-beers url: "https://api.punkapi.com/v2/beers" ]
search @http
  [ #request #fetch-beers response: [ json: [ #array ] ] ]
  lookup[ record: json, attribute, value: [ id, name, description, image_url ] ]

Now this API return an array of objects ([{}, {}, …]) and I only want to pick 3 of the objects in the array (randomly, but that would be another thing).
How can I achieve that? Did I miss something from the documentation :s?

I think the docs are slightly lacking on "thinking in Eve" at the moment. At the moment the best way is to read through examples and figure out the techniques being used. An Eve search block is kind of like SQL. I think to solve this type of problem there are two strategies you can use:

  1. Write it like a loop in an imperative language, reading and committing to a loop counter variable stored in some record.
  2. Write it like SQL, and use a join.

The second usually works out working better when it's possible. There are some times when it's not possible, eg getting the transitive closure of a graph, in which case you have to use the first.

In your specific example, first I think you could rewrite the second block like so:

search @http
  [ #request #fetch-beers response: [ json] ]
  lookup[ record: json, attribute, value: [ id, name, description, image_url ] ]
bind @browser
  [#div text: "{{id}} {{name}} {{description}} {{image_url}}"]

To select the first three beers, just join with a range. Add the following line to your second block:

    attribute = range[from: 1 to: 3]

To select 3 beers at random, at first it seems like you could also use a join, like so:

search @http
  [ #request #fetch-beers response: [ json: beers-all ]]
  [ #request #fetch-beers response: [ json: beers-rand ]]
  lookup[ record: beers-all, attribute: attr-all]
  lookup[ record: beers-rand, attribute: attr-rand, value: [ id, name, description, image_url ] ]
  num-beers = max[value: attr-all given: attr-all]
  attr-rand = floor[value: random[seed: range[from: 1 to: 3]] * num-beers] + 1
bind @browser
  [#div text: "{{id}} {{name}} {{description}} {{image_url}}"]

Unfortunately, while this will work most of the time, but random[] can return the same output for different inputs, so this could return less than 3 records. So we have to write a loop. I had a little try at doing this for your example, but didn't quite manage. But basically you can do it the same way as I do in the "Place mines" section of my Minesweeper example. See here: http://localhost:8080/#/examples/minesweeper.eve

Very interesting read, thanks!

Also: "of course the range as seed for the rand, damn". I didn't think of it, but now that you said it, it sound so obvious. "thinking in Eve" is really something one have to get used to :)

Just as an addendum to @frankier's suggestion for picking randomly, you can make the collision chance essentially nil (if you trust the uniformity of our distribution, anyway) for this many samples by using the full resolution of each random sample like so:

search @http
  [ #request #fetch-beers response: [json: beers]]
  lookup[ record: beers, attribute: index, value: [id, name, description, image_url]]
  sort[value: random[seed: index], given: index] < 4
bind @browser
  [#div text: "{{id}} {{name}} {{description}} {{image_url}}"]

It has the added benefit of dropping the extra cross and aggregate, which'd get expensive with a million beers.

This works by using the actual indexes of array values themselves as the seed for a new random value. We then sort the beers using that new random value (in essence, shuffling them) and take the first 3.

You'll notice that you get the same random values until you refresh -- that's because Eve's random function is referentially transparent. If you like, you can get newly shuffled beers for every request by using the record ids for the array values (which are different each time) instead of their indexes (which will be the same).